Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New helper methods #34

Merged
merged 9 commits into from
Aug 26, 2024
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions .changeset/clean-toys-roll.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
---
"@nrfcloud/ts-json-schema-transformer": major
---

Added several new helper methods

* `assert` - Asserts that a value is valid while returning the value
* `createAssertFn` - Creates a function that asserts that a value is valid
* `parse` - Parses a value and return undefined if it is invalid
* `createParseFn` - Creates a function that parses a value and returns undefined if it is invalid
* `assertParse` - Parses a value and asserts that it is valid
* `createAssertParseFn` - Creates a function that parses a value and asserts that it is valid
* `createMockFn` - Creates a function that generates a mock value
* `createAssertGuardFn` - Creates a function that asserts that a value is valid, narrowing the type
* `guard` - Validates a value and narrows the type (type guard)
* `validate` - Validates a value and returns the value if it is valid or undefined if it is invalid
* `createValidateFn` - Creates a function that validates a value and returns the value if it is valid or undefined if it is invalid

A few methods have been renamed, and their previous names are now deprecated:
* `assertValid` -> `assertGuard`
* `getMockObject` -> `mock`
* `getValidator` -> `createGuardFn`
5 changes: 5 additions & 0 deletions .changeset/grumpy-ants-know.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"@nrfcloud/ts-json-schema-transformer": minor
---

seeded mock
112 changes: 103 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ It uses [ts-json-schema-generator](https://github.com/vega/ts-json-schema-genera
[ajv](https://github.com/ajv-validator/ajv) to generate validator functions. Functions and schema are generated inline
at compile time using a custom typescript transformer.

You can also generate mock objects (using [json-schema-faker](https://github.com/json-schema-faker/json-schema-faker)) and safely parse JSON strings into the given types.

## Installation

#### Requirements
Expand Down Expand Up @@ -61,7 +63,7 @@ pnpm add -D ttypescript
### General Usage

```typescript
import { getSchema, getValidator } from "@nrfcloud/ts-json-schema-transformer";
import { getSchema, createValidateFn } from "@nrfcloud/ts-json-schema-transformer";
import { getMockObject } from "./index";

export interface InputEvent {
Expand Down Expand Up @@ -101,7 +103,7 @@ type union = Type1 | Type2;
const schema = getSchema<InputEvent>();

// Generate an AJV validator function
const validator = getValidator<InputEvent>();
const validator = createValidateFn<InputEvent>();

// Run the validator
validator({});
Expand Down Expand Up @@ -135,25 +137,70 @@ Generates a JSON schema for the given type.
The generic type parameter is the type you want to generate a schema for, and the single input to the function.
This function call is replaced by the generated schema at compile time.

#### `getValidator<T>(): ValidateFunction<T>`
#### `validate<T>(obj: unknown): T | undefined`

Validates an object against the schema for the given type
Returns either the validated object or undefined if validation fails.

#### `createValidateFn<T>(): (obj: unknown) => T | undefined`

Creates a validator function for the given type.
Returns either the validated object or undefined if validation fails.

#### `guard<T>(obj: unknown): obj is T`

Validates an object against the schema for the given type.
Returns a boolean indicating whether the object is valid, acting as a type guard.

#### `createGuardFn<T>(): ValidateFunction<T>`

Generates an AJV validator function for the given type.
The generic type parameter is the type you want to generate a validator for, and the single input to the function.
This function call is replaced by the generated validator at compile time.

#### `getMockObject<T, Seed>(): T`
#### `mock<T, Seed>(): T`

Generate a mock object for the given type.
Should support all formats as well as other constraints.
You can optionally specify a seed for the random number generator as the second parameter.

#### `assertValid<T>(obj: unknown): asserts obj is T`
#### `createMockFn<T, Seed>(): () => T`

Generates a reusable mock function for the given type.

#### `assertGuard<T>(obj: unknown): asserts obj is T`

Validates that a given object satisfies the constraints defined in the given generic type parameter's schema. The method will throw an error if validation fails. This function call is replaced a wrapped validator method at compile time.

#### `createAssertGuardFn<T>(): (obj: unknown) => asserts obj is T`

Generates a reusable assertGuard function for the given type. The function returned by this method can be called with an object to validate it against the schema for the given type. The function will throw an error if validation fails.

#### `assert<T>(obj: unknown): T`

Very similar to `assertValid` but returns the passed object instead of narrowing the type.
Very similar to `assertGuard` but returns the passed object instead of narrowing the type.

#### `createAssertFn<T>(): (obj: unknown) => T`

Generates a reusable assert function for the given type. The function returned by this method can be called with an object to validate it against the schema for the given type. The function will throw an error if validation fails.

#### `parse<T>(input: string): T`

Parses a JSON string into the given type.
Returns the parsed object if successful, otherwise undefined.

#### `assertParse<T>(input: string): T`

Parses a JSON string into the given type.
Throws an error if the input is invalid.

#### `createParseFn<T>(): (input: string) => T`

Generates a reusable parse function for the given type.

#### `createAssertParseFn<T>(): (input: string) => T`

Generates a reusable assertParse function for the given type.

### JSDoc Tags

Expand Down Expand Up @@ -426,11 +473,57 @@ Simply call `getSchema` and run the output through `JSON.stringify` and save it
The validator function returns a boolean, and sets the `errors` property on the function to an array of errors.
[AJV Docs](https://ajv.js.org/api.html#validation-errors)

#### assertValid versus getValidator?
#### Why would I use `create<Method>Fn` instead of the normal method?

Large schemas can generate a substantial amount of code, so creating a reusable function can help reduce the size of the generated code.
This can be important in cases where the size of the final bundle is a concern.

#### How does this compare to similar libraries such as `typia`?

The big difference between this library and `typia` is that it uses AJV and other off the shelf libraries (ts-json-schema-generator in particular) to generate schemas and code.
This means that the individual components can have separate maintainers with a wider base of support (along with a wider support and feature set).

For our specific use case, `typia` lacks support for type aliases and, consequently, nominal types.

For example, we use something like the following to define safe nominal types:
```typescript
export declare class Tagged<N extends string> {
protected _nominal_: N;
}

// The extra parameter "E" is for creating basic inheritance
export type Nominal<T, N extends string, E extends T & Tagged<string> = T & Tagged<N>> = (T & Tagged<N>) | E;

// 0..255 regex is [0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5]
// 0..31 regex is [0-9]|[12][0-9]|3[012]
// CIDR v4 is [0..255].[0..255].[0..255].[0..255]/[0..32]
/**
* @pattern ^([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])(\.([0-9]|[1-9][0-9]|1[0-9][0-9]|2[0-4][0-9]|25[0-5])){3}/([0-9]|[12][0-9]|3[012])$
* @minLength 12
* @maxLength 21
* @example "86.255.0.199/24"
*/
export type CidrV4 = Nominal<string, "CidrV4">;
```

We can then use this type to validate the input to functions:
```typescript
function checkIpInCidrBlock(ip: IPv4, cidr: CidrV4): boolean {
...
}

const testCidr = "192.168.1.0/24"

// This will fail since the string type is too broad
checkIpInCidrBlock(testCidr)

The key difference here from a function-standpoint is that `assertValid` is a method that will throw an error upon validation failure, and the `getValidator` method returns an AJV validator. By using `getValidator`, you can utilize and access all of the AJV validator's properties (e.g. errors, schema, etc), which may be useful for some usecases.
// This narrows the string type to the nominal one
assertGuard<CidrV4>(testCidr)

checkIpInCidrBlock(testCidr);
```

Frequent usage of `assertValid` may contribute to a spike in filesize in the transpiled file, this is more apparent for larger schemas.
Typia is a fantastic library and was a big inspiration for this project.

### Contributing

Expand All @@ -439,5 +532,6 @@ Contributions are welcome!
Please follow the guidelines:

- Use conventional style commit messages
- Submit a changeset with your PR `pnpm changeset`
- Don't introduce any new runtime dependencies either through the index file or generated code
- Run lint and fix before committing
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@
"types": "./dist/transform.d.ts",
"require": "./dist/transform.js"
},
"./dist/jsf": {
"types": "./dist/json-schema-faker-proxy.d.ts",
"require": "./dist/json-schema-faker-proxy.js"
},
"./transform": {
"types": "./dist/transform.d.ts",
"require": "./dist/transform.js"
Expand Down
Loading
Loading