From db553ad00d8046ffc75b821c12a3e3ab5cea38d0 Mon Sep 17 00:00:00 2001 From: Evgeny Poberezkin <2769109+epoberezkin@users.noreply.github.com> Date: Sun, 7 Mar 2021 09:12:47 +0000 Subject: [PATCH] fix type inference for JTDSchemaType in compileParser/Serializer --- docs/guide/typescript.md | 4 +-- lib/core.ts | 7 ++++-- lib/jtd.ts | 12 +++++++-- spec/types/jtd-schema.spec.ts | 46 +++++++++++++++++++++++++++++++++++ 4 files changed, 63 insertions(+), 6 deletions(-) diff --git a/docs/guide/typescript.md b/docs/guide/typescript.md index 249afce34..c3e2385ba 100644 --- a/docs/guide/typescript.md +++ b/docs/guide/typescript.md @@ -123,8 +123,8 @@ const schema = { type MyData = JTDDataType -// validate is a type guard for MyData - type is inferred from schema type -const validate = ajv.compile(schema) +// type inference is not supported for JTDDataType yet +const validate = ajv.compile(schema) const validData = { foo: 1, diff --git a/lib/core.ts b/lib/core.ts index 32666bd44..d85844559 100644 --- a/lib/core.ts +++ b/lib/core.ts @@ -299,7 +299,7 @@ export default class Ajv { validate(schema: Schema | string, data: unknown): boolean validate(schemaKeyRef: AnySchema | string, data: unknown): boolean | Promise validate(schema: Schema | JSONSchemaType | string, data: unknown): data is T - // This is separated to help typescript with inference + // Separated for type inference to work // eslint-disable-next-line @typescript-eslint/unified-signatures validate(schema: JTDSchemaType, data: unknown): data is T validate(schema: AsyncSchema, data: unknown | T): Promise @@ -324,7 +324,7 @@ export default class Ajv { // Create validation function for passed schema // _meta: true if schema is a meta-schema. Used internally to compile meta schemas of user-defined keywords. compile(schema: Schema | JSONSchemaType, _meta?: boolean): ValidateFunction - // This is separated to help typescript with inference + // Separated for type inference to work // eslint-disable-next-line @typescript-eslint/unified-signatures compile(schema: JTDSchemaType, _meta?: boolean): ValidateFunction compile(schema: AsyncSchema, _meta?: boolean): AsyncValidateFunction @@ -342,6 +342,9 @@ export default class Ajv { schema: SchemaObject | JSONSchemaType, _meta?: boolean ): Promise> + // Separated for type inference to work + // eslint-disable-next-line @typescript-eslint/unified-signatures + compileAsync(schema: JTDSchemaType, _meta?: boolean): Promise> compileAsync(schema: AsyncSchema, meta?: boolean): Promise> // eslint-disable-next-line @typescript-eslint/unified-signatures compileAsync( diff --git a/lib/jtd.ts b/lib/jtd.ts index 9ab13479c..0df1ce204 100644 --- a/lib/jtd.ts +++ b/lib/jtd.ts @@ -94,12 +94,20 @@ export default class Ajv extends AjvCore { super.defaultMeta() || (this.getSchema(META_SCHEMA_ID) ? META_SCHEMA_ID : undefined)) } - compileSerializer(schema: SchemaObject | JTDSchemaType): (data: T) => string { + compileSerializer(schema: SchemaObject): (data: T) => string + // Separated for type inference to work + // eslint-disable-next-line @typescript-eslint/unified-signatures + compileSerializer(schema: JTDSchemaType): (data: T) => string + compileSerializer(schema: SchemaObject): (data: T) => string { const sch = this._addSchema(schema) return sch.serialize || this._compileSerializer(sch) } - compileParser(schema: SchemaObject | JTDSchemaType): JTDParser { + compileParser(schema: SchemaObject): JTDParser + // Separated for type inference to work + // eslint-disable-next-line @typescript-eslint/unified-signatures + compileParser(schema: JTDSchemaType): JTDParser + compileParser(schema: SchemaObject): JTDParser { const sch = this._addSchema(schema) return (sch.parse || this._compileParser(sch)) as JTDParser } diff --git a/spec/types/jtd-schema.spec.ts b/spec/types/jtd-schema.spec.ts index 7dbce6aeb..69b16ff0e 100644 --- a/spec/types/jtd-schema.spec.ts +++ b/spec/types/jtd-schema.spec.ts @@ -48,6 +48,27 @@ describe("JTDSchemaType", () => { should.not.exist(ajv.errors) }) + it("parser should return correct data type", () => { + const ajv = new _Ajv() + const parse = ajv.compileParser(mySchema) + const validJson = '{"type": "a", "a": 1}' + const data = parse(validJson) + if (data !== undefined && data.type === "a") { + data.a.should.equal(1) + } + should.not.exist(parse.message) + }) + + it("serializer should only accept correct data type", () => { + const ajv = new _Ajv() + const serialize = ajv.compileSerializer(mySchema) + const validData = {type: "a" as const, a: 1} + serialize(validData).should.equal('{"type":"a","a":1}') + const invalidData = {type: "a" as const, b: "test"} + // @ts-expect-error + serialize(invalidData) + }) + it("should typecheck number schemas", () => { const numf: JTDSchemaType = {type: "float64"} const numi: JTDSchemaType = {type: "int32"} @@ -322,6 +343,31 @@ describe("JTDSchemaType", () => { }) describe("JTDDataType", () => { + it("validation should prove the data type", () => { + const ajv = new _Ajv() + const mySchema1 = { + discriminator: "type", + mapping: { + a: {properties: {a: {type: "float64"}}}, + b: {optionalProperties: {b: {type: "string"}}}, + }, + } as const + + type MyData1 = JTDDataType + + const validate = ajv.compile(mySchema1) + const validData: unknown = {type: "a", a: 1} + if (validate(validData) && validData.type === "a") { + validData.a.should.equal(1) + } + should.not.exist(validate.errors) + + if (ajv.validate(mySchema1, validData) && validData.type === "a") { + validData.a.should.equal(1) + } + should.not.exist(ajv.errors) + }) + it("should typecheck number schemas", () => { const numSchema = {type: "float64"} as const const num: TypeEquality, number> = true