diff --git a/README.md b/README.md index eece60f..6a3c408 100644 --- a/README.md +++ b/README.md @@ -88,7 +88,7 @@ const data = ... // get data from somewhere, e.g. as a TypeScript unknown ## Standard validator -The default behaviour of `compile` is to return a validator function returning decorated Ajv output. +The default behaviour of `compile` is to return a validator function returning extended Ajv output. ```ts const userValidator = compile( userSchema ); @@ -147,28 +147,47 @@ const user = ensureUser< User >( data ); ``` -# Decorating schemas +## Raw JSON Schema validator -You can decorate a validator schema using `suretype()`. The return value is still a validator schema, but when exporting it, the decorations will be included. +Sometimes it's handy to not describe the validator schema programmatically, but rather use a raw JSON Schema. There will be no type deduction, so the corresponding interface must be provided explicitly. Only use this if you know the JSON Schema maps to the interface! `raw` works just like the `v.*` functions and returns a validator schema. It can also be annotated. ```ts -import { suretype, v } from "suretype" +import { raw, compile } from 'suretype' + +type User = ...; // Get this type from somewhere +const userSchema = raw< User >( { type: 'object', properties: { /* ... */ } } ); + +// Compile as usual +const ensureUser = compile( userSchema, { ensure: true } ); +``` + + +# Annotating schemas + +You can annotate a validator schema using `suretype()` or `annotate()`. The return value is still a validator schema, but when exporting it, the annotations will be included. + +The difference between `suretype()` and `annotate()` is that `suretype()` requires the `name` property, where as it's optional in `annotate()`. Use `suretype()` to annotate top-level schemas so that they have proper names in the corresponding JSON Schema. + +Annotations are useful when exporting the schema to other formats (e.g. JSON Schema or pretty TypeScript interfaces). + +```ts +import { suretype, annotate, v } from "suretype" const cartItemSchema = suretype( - // Decorations + // Annotations { name: "CartItem" }, // The validator schema v.object( { - productId: v.string( ), + productId: annotate( { title: "The product id string" }, v.string( ) ), // ... } ) ); ``` -The decorator interface (i.e. the fields you can decorate) is: +The interface (i.e. the fields you can use) is called `Annotations`: ```ts -interface Decorations { +interface Annotations { name: string; title?: string; description?: string; @@ -183,7 +202,7 @@ where only the `name` is required. The following are two types, one using (or *depending on*) the other. They are *named*, which will be reflected in the JSON schema, shown below. -The `userSchema` is the same as in the above example, although it's wrapped in `suretype()` which decorates it with a name and other attributes. +The `userSchema` is the same as in the above example, although it's wrapped in `suretype()` which annotates it with a name and other attributes.
Given these validation schemas: diff --git a/lib/annotations.ts b/lib/annotations.ts new file mode 100644 index 0000000..e64b78b --- /dev/null +++ b/lib/annotations.ts @@ -0,0 +1,38 @@ +import { BaseValidator } from "./validators/base/validator" + +export interface Annotations +{ + name?: string; + title?: string; + description?: string; + examples?: Array< string >; +} + +export type TopLevelAnnotations = + Omit< Annotations, 'name' > & + Required< Pick< Annotations, 'name' > >; + +export class AnnotationsHolder +{ + constructor( public options: Annotations ) + { } +} + +type AnnotatedValidator< T extends BaseValidator< unknown, any > > = + T & { _annotations: AnnotationsHolder }; + +export function annotateValidator< T extends BaseValidator< unknown > >( + validator: T, + annotations: AnnotationsHolder +): T +{ + ( validator as AnnotatedValidator< T > )._annotations = annotations; + return validator; +} + +export function getAnnotations< T extends BaseValidator< unknown > >( + validator: T +): Annotations | undefined +{ + return ( validator as AnnotatedValidator< T > )._annotations?.options; +} diff --git a/lib/api/index.spec.ts b/lib/api/index.spec.ts index ac9c4cd..00705df 100644 --- a/lib/api/index.spec.ts +++ b/lib/api/index.spec.ts @@ -1,6 +1,6 @@ -import { suretype, v, ensureNamed } from './index' +import { suretype, annotate, v, ensureNamed } from './index' import { compile } from '..' -import { getDecorations } from '../validation' +import { getAnnotations } from '../annotations' describe( "suretype", ( ) => @@ -34,6 +34,36 @@ describe( "suretype", ( ) => } ); } ); +describe( "annotate", ( ) => +{ + it( "should validate a annotate()'d schema", ( ) => + { + const inner = v.object( { + foo: v.string( ).const( "bar" ), + bar: v.number( ).gt( 17 ).required( ), + } ); + const schema = annotate( + { + description: "Description", + title: "Title", + examples: [ "Example" ], + }, + inner + ); + + const innerValidator = compile( inner ); + const outerValidator = compile( schema ); + + const valid = { bar: 20 }; + const invalid = { foo: 30, bar: 20 }; + + expect( innerValidator( valid ).ok ).toBe( true ); + expect( innerValidator( invalid ).ok ).toBe( false ); + expect( outerValidator( valid ).ok ).toBe( true ); + expect( outerValidator( invalid ).ok ).toBe( false ); + } ); +} ); + describe( "v", ( ) => { it( "should compile and validate a validator schema", ( ) => @@ -47,23 +77,22 @@ describe( "v", ( ) => } ); } ); - describe( "ensureNamed", ( ) => { - it( "should not change name of decorated validator", ( ) => + it( "should not change name of annotated validator", ( ) => { const schema = suretype( { name: 'Goodname' }, v.object( { foo: v.string( ) } ) ); const validator = ensureNamed( 'Badname', schema ); - expect( getDecorations( validator )?.options.name ).toBe( 'Goodname' ); + expect( getAnnotations( validator )?.name ).toBe( 'Goodname' ); } ); - it( "should change name of non-decorated validator", ( ) => + it( "should change name of non-annotated validator", ( ) => { const schema = v.object( { foo: v.string( ) } ); const validator = ensureNamed( 'Goodname', schema ); - expect( getDecorations( validator )?.options.name ).toBe( 'Goodname' ); + expect( getAnnotations( validator )?.name ).toBe( 'Goodname' ); } ); } ); diff --git a/lib/api/index.ts b/lib/api/index.ts index 8c4b9e5..51571d6 100644 --- a/lib/api/index.ts +++ b/lib/api/index.ts @@ -10,15 +10,20 @@ import { TupleValidator } from "../validators/tuple/validator" import { AnyOfValidator } from "../validators/or/validator" import { AllOfValidator } from "../validators/all-of/validator" import { IfValidator } from "../validators/if/validator" +import { RawValidator } from "../validators/raw/validator" +import { RecursiveValidator } from "../validators/recursive/validator" import { TypeOf } from "../validators/functional" -import { - cloneValidator, - decorateValidator, - getDecorations, -} from "../validation" +import { cloneValidator } from "../validation" import { ArrayFunction, TupleFunction } from "../validators/array-types" import { ExtractObject } from "../validators/object-types" -import { DecorationsHolder, Decorations } from "../validators/decorations" +import { + AnnotationsHolder, + Annotations, + TopLevelAnnotations, + annotateValidator, + getAnnotations, +} from "../annotations" +import { RecursiveValue } from "../validators/types" const string = ( ) => new StringValidator( ); @@ -61,6 +66,8 @@ const any = ( ) => new AnyValidator( ); const _if = < T extends BaseValidator< unknown > >( validator: T ) => new IfValidator< TypeOf< T > >( validator ); +const recursive = ( ) => new RecursiveValidator( ); + export const v = { string, number, @@ -72,34 +79,60 @@ export const v = { allOf, if: _if, any, + recursive, }; /** - * Decorate a validator with a name and other annotations + * Cast a recursive value (a value in a recursive type) + */ +export const recursiveCast = < T >( value: RecursiveValue ): T => value as any; + +/** + * Cast a value into a recursive value (inversion of recursiveCast) + */ +export const recursiveUnCast = < T >( value: T ) => value as RecursiveValue; + +export const raw = < T = unknown >( jsonSchema: any ) => + new RawValidator( jsonSchema ) as BaseValidator< T >; + +/** + * Annotate a validator with a name and other decorations * - * @param decorations Decorations - * @param validator Target validator to decorate - * @returns Decorated validator + * @param annotations Annotations + * @param validator Target validator to annotate + * @returns Annotated validator */ export function suretype< T extends BaseValidator< unknown, any > >( - decorations: Decorations, + annotations: TopLevelAnnotations, + validator: T +) +: T +{ + return annotateValidator( + cloneValidator( validator, false ), + new AnnotationsHolder( annotations ) + ); +} + +export function annotate< T extends BaseValidator< unknown, any > >( + annotations: Partial< Annotations >, validator: T ) : T { - return decorateValidator( + return annotateValidator( cloneValidator( validator, false ), - new DecorationsHolder( decorations ) + new AnnotationsHolder( annotations ) ); } /** - * Ensures a validator is decorated with a name. This will not overwrite the + * Ensures a validator is annotated with a name. This will not overwrite the * name of a validator, only ensure it has one. * - * @param name The name to decorate with, unless already decorated + * @param name The name to annotate with, unless already annotated * @param validator The target validator - * @returns Decorated validator + * @returns Annotated validator */ export function ensureNamed< T extends BaseValidator< unknown, any > >( name: string, @@ -107,11 +140,12 @@ export function ensureNamed< T extends BaseValidator< unknown, any > >( ) : T { - if ( getDecorations( validator )?.options.name ) + const annotations = getAnnotations( validator ); + if ( annotations?.name ) return validator; - return decorateValidator( + return annotateValidator( cloneValidator( validator, false ), - new DecorationsHolder( { name } ) + new AnnotationsHolder( { ...annotations, name } ) ); } diff --git a/lib/extract-json-schema.spec.ts b/lib/extract-json-schema.spec.ts index c222407..bb90c2d 100644 --- a/lib/extract-json-schema.spec.ts +++ b/lib/extract-json-schema.spec.ts @@ -110,7 +110,7 @@ describe( "extract-json-schema", ( ) => const schema2 = v.string( ); expect( ( ) => extractJsonSchema( [ schema1, schema2 ] ) ) - .toThrowError( /undecorated/ ); + .toThrowError( /unnamed/ ); } ); it( "should ignore non-decorated validator schemas", ( ) => diff --git a/lib/extract-json-schema.ts b/lib/extract-json-schema.ts index 52e1844..0fae525 100644 --- a/lib/extract-json-schema.ts +++ b/lib/extract-json-schema.ts @@ -5,7 +5,7 @@ import type { } from "./types" import { DuplicateError } from "./errors" import { BaseValidator } from "./validators/base/validator" -import { getDecorations } from "./validation" +import { getAnnotations } from "./annotations" import { TreeTraverserImpl } from "./tree-traverser" @@ -40,14 +40,14 @@ export function extractJsonSchema( if ( onNonSuretypeValidator === 'ignore' ) { validators = validators - .filter( validator => getDecorations( validator ) ); + .filter( validator => getAnnotations( validator )?.name ); } else if ( onNonSuretypeValidator === 'error' ) { validators.forEach( validator => { - if ( !getDecorations( validator ) ) - throw new TypeError( "Got undecorated validator" ); + if ( !getAnnotations( validator )?.name ) + throw new TypeError( "Got unnamed validator" ); } ); } @@ -55,9 +55,9 @@ export function extractJsonSchema( { const nameSet = new Set< string >( ); validators - .map( validator => getDecorations( validator ) ) + .map( validator => getAnnotations( validator )?.name ) .filter( < T >( t: T ): t is NonNullable< T > => !!t ) - .forEach( ( { options: { name } } ) => + .forEach( name => { if ( nameSet.has( name ) ) throw new DuplicateError( diff --git a/lib/index.spec.ts b/lib/index.spec.ts index 41a90c6..245e120 100644 --- a/lib/index.spec.ts +++ b/lib/index.spec.ts @@ -18,12 +18,19 @@ describe( "index", ( ) => allOf: expect.any( Function ), if: expect.any( Function ), any: expect.any( Function ), + recursive: expect.any( Function ), }, + raw: expect.any( Function ), suretype: expect.any( Function ), + annotate: expect.any( Function ), + recursiveCast: expect.any( Function ), + recursiveUnCast: expect.any( Function ), // JSON Schema extraction extractJsonSchema: expect.any( Function ), extractSingleJsonSchema: expect.any( Function ), + // Annotations + getAnnotations: expect.any( Function ), // Errors DuplicateConstraintError: expect.any( Function ), diff --git a/lib/index.ts b/lib/index.ts index bf8da28..903c0e0 100644 --- a/lib/index.ts +++ b/lib/index.ts @@ -16,5 +16,11 @@ export { export { getValidatorSchema } from "./validation" +export { + Annotations, + TopLevelAnnotations, + getAnnotations, +} from "./annotations" + import type { TypeOf } from "./validators/functional" export type { TypeOf } diff --git a/lib/tree-traverser.ts b/lib/tree-traverser.ts index d4a3140..ee86b42 100644 --- a/lib/tree-traverser.ts +++ b/lib/tree-traverser.ts @@ -1,6 +1,7 @@ import { TreeTraverser, BaseValidator } from "./validators/base/validator" import type { ExportRefMethod } from "./types" -import { validatorToSchema, getDecorations } from "./validation" +import { validatorToSchema } from "./validation" +import { getAnnotations } from "./annotations" export class TreeTraverserImpl implements TreeTraverser @@ -13,6 +14,8 @@ export class TreeTraverserImpl implements TreeTraverser private definitions: { [ name: string ]: any; } = { }; private duplicates = new Map< string, number >( ); + public currentSchemaName: string | undefined = undefined; + public constructor( initialValidators: Array< BaseValidator< unknown, any > >, private refMethod: ExportRefMethod @@ -47,8 +50,8 @@ export class TreeTraverserImpl implements TreeTraverser if ( this.refMethod === 'no-refs' ) return undefined; - const decorations = getDecorations( validator ); - if ( !decorations ) + const decorations = getAnnotations( validator ); + if ( !decorations?.name ) return undefined; const nameIfInitial = this.initialValidators.get( validator ); @@ -71,15 +74,17 @@ export class TreeTraverserImpl implements TreeTraverser { name: string; validator: BaseValidator< unknown, any >; } ) { + this.currentSchemaName = name; this.definitions[ name ] = validatorToSchema( validator, this ); + this.currentSchemaName = undefined; return name; } private makeRef( validator: BaseValidator< unknown, any >, extra: boolean ) { - const decorations = getDecorations( validator ); + const decorations = getAnnotations( validator ); - const name = this.getNextName( decorations?.options.name ); + const name = this.getNextName( decorations?.name ); if ( extra ) this.extraValidators.set( validator, name ); diff --git a/lib/validation.ts b/lib/validation.ts index 1fdedab..a1a792d 100644 --- a/lib/validation.ts +++ b/lib/validation.ts @@ -1,5 +1,4 @@ import type { AnyType } from "./validators/types" -import type { DecorationsHolder } from "./validators/decorations" import { BaseValidator, TreeTraverser } from "./validators/base/validator" @@ -33,22 +32,6 @@ export function cloneValidator< T extends BaseValidator< unknown > >( return ( validator as any ).clone( clean ); } -export function decorateValidator< T extends BaseValidator< unknown > >( - validator: T, - decorations: DecorationsHolder -): T -{ - ( validator as any )._decorations = decorations; - return validator; -} - -export function getDecorations< T extends BaseValidator< unknown > >( - validator: T -): DecorationsHolder | undefined -{ - return ( validator as any )._decorations; -} - const schemaLookup = new WeakMap< Function, BaseValidator< unknown > >( ); export function attachSchemaToValidator< Fn extends Function >( diff --git a/lib/validators/base/validator.ts b/lib/validators/base/validator.ts index 332f84f..37fb6bb 100644 --- a/lib/validators/base/validator.ts +++ b/lib/validators/base/validator.ts @@ -1,11 +1,12 @@ import { AnyType } from "../types" -import { DecorationsHolder } from "../decorations" +import { AnnotationsHolder } from "../../annotations" export interface TreeTraverser { visit( validator: BaseValidator< unknown, any > ): any; getSchema( ): { schema: any; duplicates: Map< string, number >; }; + currentSchemaName: string | undefined; } export abstract class BaseValidator< @@ -15,7 +16,7 @@ export abstract class BaseValidator< { protected _parent: this | undefined = undefined; - protected _decorations: DecorationsHolder | undefined = undefined; + protected _annotations: AnnotationsHolder | undefined = undefined; protected abstract type: AnyType; @@ -33,10 +34,10 @@ export abstract class BaseValidator< protected getJsonSchemaObject( traverser: TreeTraverser ) { - if ( !this._decorations ) + if ( !this._annotations ) return { }; - const { title, description, examples } = this._decorations.options; + const { title, description, examples } = this._annotations.options; return { ...( title ? { title } : { } ), diff --git a/lib/validators/decorations.ts b/lib/validators/decorations.ts deleted file mode 100644 index f0b2b37..0000000 --- a/lib/validators/decorations.ts +++ /dev/null @@ -1,14 +0,0 @@ - -export interface Decorations -{ - name: string; - title?: string; - description?: string; - examples?: Array< string >; -} - -export class DecorationsHolder -{ - constructor( public options: Decorations ) - { } -} diff --git a/lib/validators/functional.ts b/lib/validators/functional.ts index 74bfc4c..9cf3e07 100644 --- a/lib/validators/functional.ts +++ b/lib/validators/functional.ts @@ -12,6 +12,9 @@ import { AnyOfValidator } from "./or/validator" import { AllOfValidator } from "./all-of/validator" import { IfValidator, ThenValidator, ElseValidator } from "./if/validator" import { RequiredValidator } from "./required/validator" +import { RawValidator } from "./raw/validator" +import { RecursiveValidator } from "./recursive/validator" +import { RecursiveValue } from "./types" export type IsRequired< T > = @@ -42,6 +45,10 @@ export type TypeOf< T, InclRequired = false > = ? U : T extends AnyValidator ? any + : T extends RecursiveValidator + ? RecursiveValue + : T extends RawValidator + ? unknown : T extends AnyOfValidator< infer U > ? U : T extends AllOfValidator< infer U > diff --git a/lib/validators/object/validator.ts b/lib/validators/object/validator.ts index f410b8b..23016ef 100644 --- a/lib/validators/object/validator.ts +++ b/lib/validators/object/validator.ts @@ -1,7 +1,8 @@ import { Type } from "../types" import { BaseValidator, TreeTraverser } from "../base/validator" import { validatorType } from "../../validation" -import { ValueValidator, isRequired } from "../value/validator" +import { ValueValidator } from "../value/validator" +import { isRequired } from "../required/validator" import { TypeOf } from "../functional" import { AnyValidator } from "../any/validator" diff --git a/lib/validators/raw/validator.spec.ts b/lib/validators/raw/validator.spec.ts new file mode 100644 index 0000000..c64263c --- /dev/null +++ b/lib/validators/raw/validator.spec.ts @@ -0,0 +1,29 @@ +import { RawValidator } from "./validator" +import { validatorType } from "../../validation" +import { validateJsonSchema, validate } from "../../json-schema" +import { extractSingleJsonSchema } from "../../extract-json-schema" + + +describe( "RawValidator", ( ) => +{ + it( "Correct type", ( ) => + { + expect( validatorType( new RawValidator( { } ) ) ).toEqual( "any" ); + } ); + + it( "Valid basic schema", ( ) => + { + const validator = new RawValidator( { type: 'string' } ); + const schema = extractSingleJsonSchema( validator ); + expect( schema ).toEqual( { type: "string" } ); + + expect( validateJsonSchema( schema ).ok ).toEqual( true ); + expect( validate( validator, true ).ok ).toEqual( false ); + expect( validate( validator, [ ] ).ok ).toEqual( false ); + expect( validate( validator, 3.14 ).ok ).toEqual( false ); + expect( validate( validator, { } ).ok ).toEqual( false ); + expect( validate( validator, null ).ok ).toEqual( false ); + expect( validate( validator, "3.14" ).ok ).toEqual( true ); + expect( validate( validator, "foo" ).ok ).toEqual( true ); + } ); +} ); diff --git a/lib/validators/raw/validator.ts b/lib/validators/raw/validator.ts new file mode 100644 index 0000000..b2672b9 --- /dev/null +++ b/lib/validators/raw/validator.ts @@ -0,0 +1,28 @@ +import { AnyType } from "../types" +import { BaseValidator } from "../base/validator" + + +export class RawValidator extends BaseValidator< unknown, RawValidator > +{ + protected type: AnyType = 'any'; + + public constructor( private jsonSchema: any ) + { + super( ); + } + + protected toSchema( ) + { + return this.jsonSchema; + } + + protected clone( clean: boolean = false ): this + { + return this.setupClone( + clean, + new RawValidator( + JSON.parse( JSON.stringify( this.jsonSchema ) ) + ) + ); + } +} diff --git a/lib/validators/recursive/validator.spec.ts b/lib/validators/recursive/validator.spec.ts new file mode 100644 index 0000000..ed7011f --- /dev/null +++ b/lib/validators/recursive/validator.spec.ts @@ -0,0 +1,88 @@ +import { RecursiveValidator } from "./validator" +import { validatorType } from "../../validation" +import { suretype, v } from "../../api" +import { + extractSingleJsonSchema, + extractJsonSchema, +} from "../../extract-json-schema" + + +describe( "RecursiveValidator", ( ) => +{ + it( "Correct type", ( ) => + { + expect( validatorType( new RecursiveValidator( ) ) ) + .toEqual( "recursive" ); + } ); + + it( "Valid basic schema", ( ) => + { + expect( extractSingleJsonSchema( new RecursiveValidator( ) ) ) + .toEqual( { $ref: "#/definitions/Unknown_1" } ); + } ); + + it( "Valid basic suretype'd schema", ( ) => + { + const schema = suretype( + { name: 'Foo' }, + new RecursiveValidator( ) + ); + expect( extractSingleJsonSchema( schema ) ) + .toStrictEqual( { $ref: "#/definitions/Foo" } ); + } ); + + it( "Single complex suretype'd schema", ( ) => + { + const schema = suretype( + { name: 'Foo' }, + v.object( { + foo: v.number( ), + bar: v.recursive( ), + } ) + ); + expect( extractSingleJsonSchema( schema ) ).toStrictEqual( { + type: "object", + properties: { + foo: { type: "number" }, + bar: { $ref: "#/definitions/Foo" }, + } + } ); + } ); + + it( "Multiple complex suretype'd schemas", ( ) => + { + const schema1 = suretype( + { name: 'Foo' }, + v.object( { + foo: v.number( ), + bar: v.recursive( ), + } ) + ); + const schema2 = suretype( + { name: 'Bar' }, + v.object( { + foo2: schema1, + bar2: v.recursive( ), + } ) + ); + const { schema } = extractJsonSchema( [ schema1, schema2 ] ); + expect( schema ).toStrictEqual( { + definitions: { + Foo: { + type: "object", + properties: { + foo: { type: "number" }, + bar: { $ref: "#/definitions/Foo" }, + } + }, + Bar: { + type: "object", + properties: { + foo2: { $ref: "#/definitions/Foo" }, + bar2: { $ref: "#/definitions/Bar" }, + } + }, + } + } ); + } ); +} ); diff --git a/lib/validators/recursive/validator.ts b/lib/validators/recursive/validator.ts new file mode 100644 index 0000000..8ee3016 --- /dev/null +++ b/lib/validators/recursive/validator.ts @@ -0,0 +1,28 @@ +import { AnyType } from "../types" +import { BaseValidator, TreeTraverser } from "../base/validator" +import { RequiredValidator } from "../required/validator" + + +export class RecursiveValidator + extends BaseValidator< unknown, RecursiveValidator > +{ + protected type: AnyType = 'recursive'; + + public required( ): RequiredValidator< RecursiveValidator, this > + { + return new RequiredValidator( this ); + } + + protected toSchema( traverser: TreeTraverser ) + { + return { + $ref: `#/definitions/${traverser.currentSchemaName}`, + ...this.getJsonSchemaObject( traverser ), + }; + } + + protected clone( clean: boolean = false ): this + { + return super.setupClone( clean, new RecursiveValidator( ) ); + } +} diff --git a/lib/validators/required/validator.ts b/lib/validators/required/validator.ts index a62603b..7219f8c 100644 --- a/lib/validators/required/validator.ts +++ b/lib/validators/required/validator.ts @@ -30,3 +30,8 @@ export class RequiredValidator< T, U extends BaseValidator< T > > return new RequiredValidator< T, U >( clonedInner ) as this; } } + +export function isRequired( validator: BaseValidator< unknown > ) +{ + return validator instanceof RequiredValidator; +} diff --git a/lib/validators/tuple/validator.ts b/lib/validators/tuple/validator.ts index dd577d4..b474a25 100644 --- a/lib/validators/tuple/validator.ts +++ b/lib/validators/tuple/validator.ts @@ -1,5 +1,6 @@ import { Type } from "../types" -import { ValueValidator, isRequired } from "../value/validator" +import { ValueValidator } from "../value/validator" +import { isRequired } from "../required/validator" import { BaseValidator, TreeTraverser } from "../base/validator" import { AnyValidator } from "../any/validator" import { DuplicateConstraintError } from "../../errors" diff --git a/lib/validators/types.ts b/lib/validators/types.ts index f13dfca..a2525bb 100644 --- a/lib/validators/types.ts +++ b/lib/validators/types.ts @@ -13,7 +13,8 @@ export type AnyType = | "any" | "any-of" | "all-of" - | "if"; + | "if" + | "recursive"; export type FilterProperties< T, Cond > = { @@ -26,3 +27,5 @@ export type SubType< T, Cond, Invert = false > = Invert extends true ? Omit< T, FilterNames< T, Cond > > : Pick< T, FilterNames< T, Cond > >; + +export abstract class RecursiveValue { } diff --git a/lib/validators/value/validator.spec.ts b/lib/validators/value/validator.spec.ts index d85f3df..04c58e4 100644 --- a/lib/validators/value/validator.spec.ts +++ b/lib/validators/value/validator.spec.ts @@ -1,8 +1,8 @@ import { Type } from "../types" -import { ValueValidator, isRequired } from "./validator" +import { ValueValidator } from "./validator" import { TreeTraverser } from "../base/validator" import { validatorType } from "../../validation" -import { RequiredValidator } from "../required/validator" +import { RequiredValidator, isRequired } from "../required/validator" import { DuplicateConstraintError } from "../../errors" import { StringValidator } from "../string/validator" import { extractSingleJsonSchema } from "../../extract-json-schema" diff --git a/lib/validators/value/validator.ts b/lib/validators/value/validator.ts index 5ac47a9..57f342a 100644 --- a/lib/validators/value/validator.ts +++ b/lib/validators/value/validator.ts @@ -214,8 +214,3 @@ function cleanFromTypeProperty< T extends { type: any } >( t: T ) const { type, ...ret } = t; return ret; } - -export function isRequired( validator: BaseValidator< unknown > ) -{ - return validator instanceof RequiredValidator; -}