import _Ajv from "../ajv" ; import type { JSONSchemaType } from "../.." ; import type { SchemaObject } from "../.." ; import chai from "../chai" ; const should = chai . should ( ) ; interface MyData { foo : string ; bar ? : number ; baz : { quux : "quux" ; [ x ] : string ; } ; boo ? : true ; tuple ? : readonly [ number , string ] ; arr : { id : number ; } [ ] ; map : { [ K in string ] ? : number ; } ; notBoo ? : string ; negativeIfBoo ? : number ; } const arrSchema = { type : "array" , items : { type : "object" , properties : { id : { type : "integer" , } , } , additionalProperties : false , required : [ "id" ] , } , uniqueItems : true , } as const ; const mySchema = { type : "object" , definitions : { baz : { type : "object" , properties : { quux : { type : "string" , const : "quux" } , } , patternProperties : { abc : { type : "string" } , } , additionalProperties : false , required : [ ] , } , tuple : { type : "array" , items : [ { type : "number" } , { type : "string" } ] , minItems : 2 , additionalItems : false , nullable : true , } , } , dependencies : { bar : [ "boo" ] , boo : { not : { required : [ "notBoo" ] } , required : [ "negativeIfBoo" ] , properties : { negativeIfBoo : { type : "number" , nullable : true , exclusiveMaximum : 0 } , } , } , } , properties : { foo : { type : "string" } , bar : { type : "number" , nullable : true } , baz : { $ref : "#/definitions/baz" } , boo : { type : "boolean" , nullable : true , enum : [ true , null ] , } , tuple : { $ref : "#/definitions/tuple" } , arr : arrSchema , map : { type : "object" , required : [ ] , additionalProperties : { type : "number" } , } , notBoo : { type : "string" , nullable : true } , negativeIfBoo : { type : "number" , nullable : true } , } , additionalProperties : false , required : [ "foo" , "baz" , "arr" , "map" ] , } as const ; type MyUnionData = { a : boolean ; } | string | number ; const myUnionSchema = { anyOf : [ { type : "object" , properties : { a : { type : "boolean" } , } , required : [ "a" ] , } , { type : [ "string" , "number" ] , minimum : 0 , minLength : 1 , } , ] , } as const ; const myNestedUnionSchema = { anyOf : [ { oneOf : [ { type : "object" , properties : { a : { type : "boolean" } , } , required : [ "a" ] , } , { type : "string" , } , ] , } , { type : "number" , } , ] , } as const ; const emptyType = { type : [ ] , } as const ; type MyEnumRecord = Record < "a" | "b" | "c" | "d" , number | undefined > ; describe ( "JSONSchemaType type and validation as a type guard" , ( ) => { const ajv = new _Ajv ( { allowUnionTypes : true } ) ; const validData = { foo : "foo" , bar : 1 , baz : { quux : "quux" , abc : "abc" , } , boo : true , arr : [ { id : 1 } , { id : 2 } ] , tuple : [ 1 , "abc" ] , map : { a : 1 , b : 2 , } , negativeIfBoo : - 1 , } ; describe ( "schema has type JSONSchemaType<MyData>" , ( ) => { it ( "should prove the type of validated data" , ( ) => { const validate = ajv . compile ( mySchema ) ; if ( validate ( validData ) ) { validData . foo . should . equal ( "foo" ) ; } should . not . exist ( validate . errors ) ; if ( ajv . validate ( mySchema , validData ) ) { validData . foo . should . equal ( "foo" ) ; } should . not . exist ( ajv . errors ) ; } ) ; } ) ; const validUnionData = { a : true , } ; describe ( "schema has type JSONSchemaType<MyUnionData>" , ( ) => { it ( "should prove the type of validated data" , ( ) => { const validate = ajv . compile ( myUnionSchema ) ; if ( validate ( validUnionData ) ) { if ( typeof validUnionData === "string" ) { should . fail ( "not a string" ) ; } else if ( typeof validUnionData === "number" ) { should . fail ( "not a number" ) ; } else { validUnionData . a . should . equal ( true ) ; } } else { should . fail ( "is valid" ) ; } should . not . exist ( validate . errors ) ; if ( ajv . validate ( myUnionSchema , validUnionData ) ) { if ( typeof validUnionData === "string" ) { should . fail ( "not a string" ) ; } else if ( typeof validUnionData === "number" ) { should . fail ( "not a number" ) ; } else { validUnionData . a . should . equal ( true ) ; } } else { should . fail ( "is valid" ) ; } should . not . exist ( ajv . errors ) ; } ) ; it ( "should prove the type of validated nested data" , ( ) => { const validate = ajv . compile ( myNestedUnionSchema ) ; if ( validate ( validUnionData ) ) { if ( typeof validUnionData === "string" ) { should . fail ( "not a string" ) ; } else if ( typeof validUnionData === "number" ) { should . fail ( "not a number" ) ; } else { validUnionData . a . should . equal ( true ) ; } } else { should . fail ( "is valid" ) ; } should . not . exist ( validate . errors ) ; if ( ajv . validate ( myNestedUnionSchema , validUnionData ) ) { if ( typeof validUnionData === "string" ) { should . fail ( "not a string" ) ; } else if ( typeof validUnionData === "number" ) { should . fail ( "not a number" ) ; } else { validUnionData . a . should . equal ( true ) ; } } else { should . fail ( "is valid" ) ; } should . not . exist ( ajv . errors ) ; } ) ; it ( "should fail for invalid unions" , ( ) => { const extraSchema = { type : [ "string" , "number" , "boolean" ] , } as const ; const extraProps = { type : [ "string" , "boolean" ] , maximum : 5 , } as const ; void [ extraSchema , extraProps ] ; } ) ; } ) ; describe ( "schema has type SchemaObject" , ( ) => { it ( "should prove the type of validated data" , ( ) => { const schema = mySchema as SchemaObject ; const validate = ajv . compile < MyData > ( schema ) ; if ( validate ( validData ) ) { validData . foo . should . equal ( "foo" ) ; } should . not . exist ( validate . errors ) ; if ( ajv . validate < MyData > ( schema , validData ) ) { validData . foo . should . equal ( "foo" ) ; } should . not . exist ( ajv . errors ) ; } ) ; } ) ; describe ( "schema should be simple for record types" , ( ) => { it ( "typechecks a valid type without required" , ( ) => { const myEnumRecordSchema = { type : "object" , propertyNames : { enum : [ "a" , "b" , "c" , "d" ] } , additionalProperty : { type : "number" } , } ; void myEnumRecordSchema ; } ) ; it ( "requires required for non-optional types" , ( ) => { const requiredSchema = { type : "object" , } ; void requiredSchema ; } ) ; it ( "doesn't require required for optional types" , ( ) => { const optionalSchema = { type : "object" , } ; void optionalSchema ; } ) ; } ) ; describe ( "schema works for primitives" , ( ) => { it ( "allows partial boolean sub schemas" , ( ) => { const trueSchema = { type : "boolean" , not : { const : false } , } as const ; void trueSchema ; } ) ; it ( "validates simple null" , ( ) => { const nullSchema = { type : "null" , nullable : true , const : null , enum : [ null ] , } as const ; const validate = ajv . compile ( nullSchema ) ; validate ( null ) . should . be . true ; } ) ; } ) ; } ) ; void emptyType ;