Skip to content

Commit

Permalink
add JTDDataType type
Browse files Browse the repository at this point in the history
  • Loading branch information
erikbrinkman committed Mar 5, 2021
1 parent 3a8c461 commit 73a8e7b
Show file tree
Hide file tree
Showing 3 changed files with 175 additions and 3 deletions.
4 changes: 2 additions & 2 deletions lib/jtd.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,8 @@ export {KeywordCxt}
export {_, str, stringify, nil, Name, Code, CodeGen, CodeGenOptions} from "./compile/codegen"

import type {AnySchemaObject, SchemaObject, JTDParser} from "./types"
import type {JTDSchemaType} from "./types/jtd-schema"
export {JTDSchemaType}
import type {JTDSchemaType, JTDDataType} from "./types/jtd-schema"
export {JTDSchemaType, JTDDataType}
import AjvCore, {CurrentOptions} from "./core"
import jtdVocabulary from "./vocabularies/jtd"
import jtdMetaSchema from "./refs/jtd-schema"
Expand Down
64 changes: 64 additions & 0 deletions lib/types/jtd-schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -149,3 +149,67 @@ export type JTDSchemaType<T, D extends Record<string, unknown> = Record<string,
// TODO these should only be allowed at the top level
definitions?: {[K in keyof D]: JTDSchemaType<D[K], D>}
}

type JTDDataDef<S, D extends Record<string, unknown>> =
| (// ref
S extends {ref: string}
? JTDDataDef<D[S["ref"]], D>
: // type
S extends {type: NumberType}
? number
: S extends {type: "string"}
? string
: S extends {type: "timestamp"}
? string | Date
: // enum
S extends {enum: readonly (infer E)[]}
? string extends E
? never
: [E] extends [string]
? E
: never
: // elements
S extends {elements: infer E}
? JTDDataDef<E, D>[]
: // properties
S extends {
properties: Record<string, unknown>
optionalProperties?: Record<string, unknown>
additionalProperties?: boolean
}
? {-readonly [K in keyof S["properties"]]-?: JTDDataDef<S["properties"][K], D>} &
{
-readonly [K in keyof S["optionalProperties"]]+?: JTDDataDef<
S["optionalProperties"][K],
D
>
}
: S extends {
properties?: Record<string, unknown>
optionalProperties: Record<string, unknown>
additionalProperties?: boolean
}
? {-readonly [K in keyof S["properties"]]-?: JTDDataDef<S["properties"][K], D>} &
{
-readonly [K in keyof S["optionalProperties"]]+?: JTDDataDef<
S["optionalProperties"][K],
D
>
}
: // values
S extends {values: infer V}
? Record<string, JTDDataDef<V, D>>
: // discriminator
S extends {discriminator: infer M; mapping: Record<string, unknown>}
? [M] extends [string]
? {
[K in keyof S["mapping"]]: JTDDataDef<S["mapping"][K], D> & {[KM in M]: K}
}[keyof S["mapping"]]
: never
: // empty
unknown)
| (S extends {nullable: true} ? null : never)

export type JTDDataType<S> = S extends {definitions: Record<string, unknown>}
? JTDDataDef<S, S["definitions"]>
: JTDDataDef<S, Record<string, never>>
110 changes: 109 additions & 1 deletion spec/types/jtd-schema.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/* eslint-disable @typescript-eslint/no-empty-interface,no-void */
import _Ajv from "../ajv_jtd"
import type {JTDSchemaType} from "../../dist/jtd"
import type {JTDSchemaType, JTDDataType} from "../../dist/jtd"
import chai from "../chai"
const should = chai.should()

Expand All @@ -19,6 +19,11 @@ interface B {

type MyData = A | B

interface LinkedList {
val: number
next?: LinkedList
}

const mySchema: JTDSchemaType<MyData> = {
discriminator: "type",
mapping: {
Expand Down Expand Up @@ -325,3 +330,106 @@ describe("JTDSchemaType", () => {
void [isNull, numNotNull]
})
})

describe("JTDDataType typechecks", () => {
it("should typecheck number schemas", () => {
const numSchema = {type: "float64"} as const
const num: TypeEquality<JTDDataType<typeof numSchema>, number> = true

void [num]
})

it("should typecheck string schemas", () => {
const strSchema = {type: "string"} as const
const str: TypeEquality<JTDDataType<typeof strSchema>, string> = true

void [str]
})

it("should typecheck timestamp schemas", () => {
const timeSchema = {type: "timestamp"} as const
const time: TypeEquality<JTDDataType<typeof timeSchema>, string | Date> = true

void [time]
})

it("should typecheck enum schemas", () => {
const enumSchema = {enum: ["a", "b"]} as const
const enumerated: TypeEquality<JTDDataType<typeof enumSchema>, "a" | "b"> = true

void [enumerated]
})

it("should typecheck elements schemas", () => {
const elementsSchema = {elements: {type: "float64"}} as const
const elem: TypeEquality<JTDDataType<typeof elementsSchema>, number[]> = true

void [elem]
})

it("should typecheck properties schemas", () => {
const bothPropsSchema = {
properties: {a: {type: "float64"}},
optionalProperties: {b: {type: "string"}},
} as const
const both: TypeEquality<JTDDataType<typeof bothPropsSchema>, {a: number; b?: string}> = true

const reqPropsSchema = {properties: {a: {type: "float64"}}} as const
const req: TypeEquality<JTDDataType<typeof reqPropsSchema>, {a: number}> = true

const optPropsSchema = {optionalProperties: {b: {type: "string"}}} as const
const opt: TypeEquality<JTDDataType<typeof optPropsSchema>, {b?: string}> = true

void [both, req, opt]
})

it("should typecheck values schemas", () => {
const valuesSchema = {values: {type: "float64"}} as const
const values: TypeEquality<JTDDataType<typeof valuesSchema>, Record<string, number>> = true

void [values]
})

it("should typecheck discriminator schemas", () => {
const discriminatorSchema = {
discriminator: "type",
mapping: {
a: {properties: {a: {type: "float64"}}},
b: {optionalProperties: {b: {type: "string"}}},
},
} as const
const disc: TypeEquality<JTDDataType<typeof discriminatorSchema>, A | B> = true

void [disc]
})

it("should typecheck ref schemas", () => {
const refSchema = {
definitions: {num: {type: "float64", nullable: true}},
ref: "num",
nullable: true,
} as const
const ref: TypeEquality<JTDDataType<typeof refSchema>, number | null> = true

// works for recursive schemas
const llSchema = {
definitions: {
node: {
properties: {val: {type: "float64"}},
optionalProperties: {next: {ref: "node"}},
},
},
ref: "node",
} as const
const list: TypeEquality<JTDDataType<typeof llSchema>, LinkedList> = true

void [ref, list]
})

it("should typecheck empty schemas", () => {
const emptySchema = {metadata: {}} as const
const empty: TypeEquality<JTDDataType<typeof emptySchema>, unknown> = true

void [empty]
})
})

0 comments on commit 73a8e7b

Please sign in to comment.