@finnair/v-validation
Advanced tools
Comparing version 7.0.0-alpha.3 to 7.0.0-alpha.4
@@ -6,2 +6,8 @@ # Change Log | ||
# [7.0.0-alpha.4](https://github.com/finnair/v-validation/compare/v7.0.0-alpha.3...v7.0.0-alpha.4) (2024-11-14) | ||
### Features | ||
- type guards to verify validator and existing type equality ([#119](https://github.com/finnair/v-validation/issues/119)) ([92699e7](https://github.com/finnair/v-validation/commit/92699e7c87e89246f92b24627278dfc57b9a128d)) | ||
# [7.0.0-alpha.3](https://github.com/finnair/v-validation/compare/v7.0.0-alpha.2...v7.0.0-alpha.3) (2024-11-11) | ||
@@ -8,0 +14,0 @@ |
@@ -5,1 +5,2 @@ export * from './V.js'; | ||
export * from './objectValidatorBuilder.js'; | ||
export * from './typing.js'; |
@@ -21,1 +21,2 @@ "use strict"; | ||
__exportStar(require("./objectValidatorBuilder.js"), exports); | ||
__exportStar(require("./typing.js"), exports); |
@@ -17,2 +17,3 @@ import { Path } from "@finnair/path"; | ||
} | ||
export type VInheritableType<V extends ObjectValidator<any, any>> = V extends ObjectValidator<any, infer Out> ? Out : unknown; | ||
export interface ObjectModel<LocalType = unknown, InheritableType = unknown> { | ||
@@ -47,3 +48,3 @@ /** | ||
} | ||
export declare class ObjectValidator<LocalType = unknown, InheritableType = unknown> extends Validator<LocalType, unknown> { | ||
export declare class ObjectValidator<LocalType = unknown, InheritableType = LocalType> extends Validator<LocalType, unknown> { | ||
readonly model: ObjectModel<LocalType, InheritableType>; | ||
@@ -50,0 +51,0 @@ readonly properties: Properties; |
@@ -39,3 +39,6 @@ "use strict"; | ||
this.localProperties = getPropertyValidators(model.localProperties); | ||
this.nextValidator = nextValidators.length > 0 ? (0, validators_1.maybeCompositionOf)(...nextValidators) : undefined; | ||
const next = nextValidators.length > 0 ? (0, validators_1.maybeCompositionOf)(...nextValidators) : undefined; | ||
if (next) { | ||
this.nextValidator = next; | ||
} | ||
if (model.localNext) { | ||
@@ -42,0 +45,0 @@ if (!Array.isArray(model.localNext)) { |
import { Validator } from "./validators.js"; | ||
import { ObjectValidator } from "./objectValidator.js"; | ||
type KeysOfType<T, SelectedType> = { | ||
[key in keyof T]: SelectedType extends T[key] ? key : never; | ||
}[keyof T]; | ||
type OptionalProperties<T> = Partial<Pick<T, KeysOfType<T, undefined>>>; | ||
type RequiredProperties<T> = Omit<T, KeysOfType<T, undefined>>; | ||
type UndefinedAsOptionalProperties<T> = RequiredProperties<T> & OptionalProperties<T>; | ||
import { UndefinedAsOptionalProperties } from "./typing.js"; | ||
export declare class ObjectValidatorBuilder<Props, Next, LocalProps, LocalNext> { | ||
@@ -24,8 +19,11 @@ private _extends; | ||
}): ObjectValidatorBuilder<Props, Next, LocalProps & UndefinedAsOptionalProperties<X>, LocalNext>; | ||
allowAdditionalProperties(allow: boolean): ObjectValidatorBuilder<Props & Record<string | number | symbol, unknown>, Next, LocalProps, LocalNext>; | ||
additionalProperties<K extends keyof any, V>(keys: Validator<K>, values: Validator<V>): ObjectValidatorBuilder<Props & Record<K, V>, Next, LocalProps, LocalNext>; | ||
next<NextOut>(validator: Validator<NextOut, Next extends {} ? Next : Props>): ObjectValidatorBuilder<Props, NextOut, LocalProps, LocalNext>; | ||
localNext<NextOut>(validator: Validator<NextOut, LocalNext extends {} ? LocalNext : Next extends {} ? Next : Props & LocalProps>): ObjectValidatorBuilder<Props, Next, LocalProps, NextOut>; | ||
build(): ObjectValidator<LocalNext extends {} ? LocalNext : Next extends {} ? Next : Props & LocalProps, Next extends {} ? Next : Props>; | ||
allowAdditionalProperties(allow: boolean): ObjectValidatorBuilder<Props & { | ||
[x: string]: unknown; | ||
[x: number]: unknown; | ||
[x: symbol]: unknown; | ||
}, Next, LocalProps, LocalNext>; | ||
additionalProperties<K extends keyof any, V>(keys: Validator<K>, values: Validator<V>): ObjectValidatorBuilder<Props & { [key in K]?: V; }, Next, LocalProps, LocalNext>; | ||
next<NextOut extends {}>(validator: Validator<NextOut, Next extends {} ? Next : Props>): ObjectValidatorBuilder<Props, NextOut, LocalProps, LocalNext>; | ||
localNext<NextOut extends {}>(validator: Validator<NextOut, LocalNext extends {} ? LocalNext : Next extends {} ? Next : Props & LocalProps>): ObjectValidatorBuilder<Props, Next, LocalProps, NextOut>; | ||
build(): ObjectValidator<(Next extends {} ? Next : Props) & (LocalNext extends {} ? LocalNext : LocalProps), Next extends {} ? Next : Props>; | ||
} | ||
export {}; |
@@ -75,3 +75,3 @@ import { SchemaValidator, SchemaModel } from './schema.js'; | ||
}>(min: number, max: number) => SizeValidator<T>; | ||
properties: <Key extends keyof any, Value>(keys: Validator<Key>, values: Validator<Value>) => ObjectValidator<Record<Key, Value>, unknown>; | ||
properties: <Key extends keyof any, Value>(keys: Validator<Key>, values: Validator<Value>) => ObjectValidator<{ [key in Key]?: Value | undefined; }, { [key in Key]?: Value | undefined; }>; | ||
allOf: AllOfParameters; | ||
@@ -78,0 +78,0 @@ anyOf: <V extends [Validator<any>, ...Validator<any>[]]>(...validators: V) => AnyOfValidator<VType<V[any]>, unknown>; |
@@ -5,1 +5,2 @@ export * from './V.js'; | ||
export * from './objectValidatorBuilder.js'; | ||
export * from './typing.js'; |
@@ -5,1 +5,2 @@ export * from './V.js'; | ||
export * from './objectValidatorBuilder.js'; | ||
export * from './typing.js'; |
@@ -17,2 +17,3 @@ import { Path } from "@finnair/path"; | ||
} | ||
export type VInheritableType<V extends ObjectValidator<any, any>> = V extends ObjectValidator<any, infer Out> ? Out : unknown; | ||
export interface ObjectModel<LocalType = unknown, InheritableType = unknown> { | ||
@@ -47,3 +48,3 @@ /** | ||
} | ||
export declare class ObjectValidator<LocalType = unknown, InheritableType = unknown> extends Validator<LocalType, unknown> { | ||
export declare class ObjectValidator<LocalType = unknown, InheritableType = LocalType> extends Validator<LocalType, unknown> { | ||
readonly model: ObjectModel<LocalType, InheritableType>; | ||
@@ -50,0 +51,0 @@ readonly properties: Properties; |
@@ -35,3 +35,6 @@ import { AnyValidator, defaultViolations, HasValueValidator, isNullOrUndefined, isNumber, isString, maybeAllOfValidator, maybeCompositionOf, Validator, ValidatorFnWrapper, violationsOf, } from "./validators"; | ||
this.localProperties = getPropertyValidators(model.localProperties); | ||
this.nextValidator = nextValidators.length > 0 ? maybeCompositionOf(...nextValidators) : undefined; | ||
const next = nextValidators.length > 0 ? maybeCompositionOf(...nextValidators) : undefined; | ||
if (next) { | ||
this.nextValidator = next; | ||
} | ||
if (model.localNext) { | ||
@@ -38,0 +41,0 @@ if (!Array.isArray(model.localNext)) { |
import { Validator } from "./validators.js"; | ||
import { ObjectValidator } from "./objectValidator.js"; | ||
type KeysOfType<T, SelectedType> = { | ||
[key in keyof T]: SelectedType extends T[key] ? key : never; | ||
}[keyof T]; | ||
type OptionalProperties<T> = Partial<Pick<T, KeysOfType<T, undefined>>>; | ||
type RequiredProperties<T> = Omit<T, KeysOfType<T, undefined>>; | ||
type UndefinedAsOptionalProperties<T> = RequiredProperties<T> & OptionalProperties<T>; | ||
import { UndefinedAsOptionalProperties } from "./typing.js"; | ||
export declare class ObjectValidatorBuilder<Props, Next, LocalProps, LocalNext> { | ||
@@ -24,8 +19,11 @@ private _extends; | ||
}): ObjectValidatorBuilder<Props, Next, LocalProps & UndefinedAsOptionalProperties<X>, LocalNext>; | ||
allowAdditionalProperties(allow: boolean): ObjectValidatorBuilder<Props & Record<string | number | symbol, unknown>, Next, LocalProps, LocalNext>; | ||
additionalProperties<K extends keyof any, V>(keys: Validator<K>, values: Validator<V>): ObjectValidatorBuilder<Props & Record<K, V>, Next, LocalProps, LocalNext>; | ||
next<NextOut>(validator: Validator<NextOut, Next extends {} ? Next : Props>): ObjectValidatorBuilder<Props, NextOut, LocalProps, LocalNext>; | ||
localNext<NextOut>(validator: Validator<NextOut, LocalNext extends {} ? LocalNext : Next extends {} ? Next : Props & LocalProps>): ObjectValidatorBuilder<Props, Next, LocalProps, NextOut>; | ||
build(): ObjectValidator<LocalNext extends {} ? LocalNext : Next extends {} ? Next : Props & LocalProps, Next extends {} ? Next : Props>; | ||
allowAdditionalProperties(allow: boolean): ObjectValidatorBuilder<Props & { | ||
[x: string]: unknown; | ||
[x: number]: unknown; | ||
[x: symbol]: unknown; | ||
}, Next, LocalProps, LocalNext>; | ||
additionalProperties<K extends keyof any, V>(keys: Validator<K>, values: Validator<V>): ObjectValidatorBuilder<Props & { [key in K]?: V; }, Next, LocalProps, LocalNext>; | ||
next<NextOut extends {}>(validator: Validator<NextOut, Next extends {} ? Next : Props>): ObjectValidatorBuilder<Props, NextOut, LocalProps, LocalNext>; | ||
localNext<NextOut extends {}>(validator: Validator<NextOut, LocalNext extends {} ? LocalNext : Next extends {} ? Next : Props & LocalProps>): ObjectValidatorBuilder<Props, Next, LocalProps, NextOut>; | ||
build(): ObjectValidator<(Next extends {} ? Next : Props) & (LocalNext extends {} ? LocalNext : LocalProps), Next extends {} ? Next : Props>; | ||
} | ||
export {}; |
@@ -75,3 +75,3 @@ import { SchemaValidator, SchemaModel } from './schema.js'; | ||
}>(min: number, max: number) => SizeValidator<T>; | ||
properties: <Key extends keyof any, Value>(keys: Validator<Key>, values: Validator<Value>) => ObjectValidator<Record<Key, Value>, unknown>; | ||
properties: <Key extends keyof any, Value>(keys: Validator<Key>, values: Validator<Value>) => ObjectValidator<{ [key in Key]?: Value | undefined; }, { [key in Key]?: Value | undefined; }>; | ||
allOf: AllOfParameters; | ||
@@ -78,0 +78,0 @@ anyOf: <V extends [Validator<any>, ...Validator<any>[]]>(...validators: V) => AnyOfValidator<VType<V[any]>, unknown>; |
{ | ||
"name": "@finnair/v-validation", | ||
"version": "7.0.0-alpha.3", | ||
"version": "7.0.0-alpha.4", | ||
"private": false, | ||
@@ -51,5 +51,5 @@ "description": "V-validation core package", | ||
"devDependencies": { | ||
"@finnair/path": "^7.0.0-alpha.3" | ||
"@finnair/path": "^7.0.0-alpha.4" | ||
}, | ||
"gitHead": "6bb252ef025e936a14bc9f71e78804b61ea212e8" | ||
"gitHead": "b0f6d3c6a456e19bcb50a78ccd6001661e46188b" | ||
} |
@@ -210,2 +210,3 @@ ![CI](https://github.com/finnair/v-validation/workflows/CI/badge.svg?branch=master) | ||
- TypeScript native implementation | ||
- Supports type inference that can be mixed with better readable custom types/interfaces | ||
@@ -243,4 +244,40 @@ ## Pure Validation? | ||
``` | ||
## Typing in Version >= 7 | ||
All built-in validators have input and output types. Typed ObjectValidators can be built with `V.objectType()`. | ||
Since inferred types tend to get quite long and hard to read, you can also combine them with hand-written types. | ||
### Validator Type | ||
Use `VType<typeof validator>` to get the result type of `validator`. | ||
Use `VInheritableType<typeof objectValidator> to get the inheritable type of `objectValidator: ObjectValidator<LocalType, Inheritabletype>`. | ||
### Type Guards | ||
When using custom interfaces it's good to verify that the validator is in sync with the interface. The | ||
challenge is that TypeScript generic `extends` only verifies type compatibility, optional | ||
properties do not count unless they are of conflicting type. For type-validator compatibility | ||
we need to also consider optional properties and nested structure. For this there is two helper | ||
types: | ||
1. `ComparableType<T>` converts all optional properties to mandatory `Optional<T>` recursively. | ||
2. `EqualTypes<A, B>` verifies that `A extends B` and `B extends A` and resolves to `true` if there's no error. | ||
These can be used with `assertType` to verify type equality: | ||
```typescript | ||
interface MyInterface{ | ||
//... | ||
} | ||
const myInterfaceValidator = V.objectType() | ||
.properties({ | ||
//... | ||
}) | ||
.build(); | ||
// Use assertType function with EqualTypes and ComparableType to verify that myInterfaceValidator type is equal to MyInterface | ||
assertType<EqualTypes<ComparableType<VType<typeof myInterfaceValidator>>, ComparableType<MyInterface>>>(true); | ||
``` | ||
Why `assertType`? EqualTypes can also be used directly, but it needs to be tied to something (e.g. `type verified = Equaltypes<...>`), | ||
but that something may then cause "is declared but never used" -error. | ||
## Combining Validators | ||
@@ -294,3 +331,3 @@ | ||
.localProperties({ | ||
type: V.hasValue('Bike' as const), | ||
type: V.hasValue<'Bike'>('Bike'), // Another way of enforcing literal type | ||
}) | ||
@@ -399,4 +436,3 @@ .build(); | ||
Polymorphic schemas are recursive in nature: 1) a child needs to know it's parents so that it may extend them and 2) unless the type information is natively bound to | ||
the object being validated, the parent needs to know it's children so that it may dispatch the validation to the correct child. As (direct) cyclic references are not possible, SchemaValidator is created with a callback function that supports referencing other models within the schema by name | ||
even before they are defined: | ||
the object being validated, the parent needs to know it's children so that it may dispatch the validation to the correct child. As (direct) cyclic references are not possible, SchemaValidator is created with a callback function that supports referencing other models within the schema by name even before they are defined: | ||
@@ -468,14 +504,24 @@ 1. An object may extend other models by simply referencing them by name. | ||
Recursive model has a cyclic reference to itself. While a model cannot reference itself | ||
before it's declared, we can wrap the call within a validator function: | ||
Recursive model has a cyclic reference to itself. While a model cannot reference itself before it's declared, | ||
we can wrap the call within a validator function. Type inference also cannot infer type from itself so we need | ||
to define the target interface separately. | ||
```typescript | ||
// { head: 'first value', tail: { head: 'second value', tail: { head: 'last value' } } } | ||
const list = V.object({ | ||
properties: { | ||
first: V.any(), | ||
// Instead of recursive model, we have recursive call | ||
next: V.optional(V.fn((value: any, path: Path, ctx: ValidationContext) => list.validatePath(value, path, ctx))), | ||
}, | ||
}); | ||
interface RecursiveModel { | ||
first: string; | ||
next?: RecursiveModel; | ||
} | ||
// Typed placeholder for the recursive validator | ||
let recursion: ObjectValidator<RecursiveModel, RecursiveModel>; | ||
const validator = V.objectType() | ||
.properties({ | ||
first: V.string(), | ||
next: V.optionalStrict(V.fn((value: any, path: Path, ctx: ValidationContext) => recursion.validatePath(value, path, ctx))), | ||
}) | ||
.build(); | ||
recursion = validator; | ||
// Verify that validator matches RecursiveModel | ||
assertType<EqualTypes<ComparableType<VType<typeof validator>>, ComparableType<RecursiveModel>>>(true); | ||
``` | ||
@@ -482,0 +528,0 @@ |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
308121
36
5452
724