diff --git a/lib/types/json-schema.ts b/lib/types/json-schema.ts index d80738247..24cce491c 100644 --- a/lib/types/json-schema.ts +++ b/lib/types/json-schema.ts @@ -1,9 +1,15 @@ /* eslint-disable @typescript-eslint/no-empty-interface */ -export type SomeJSONSchema = JSONSchemaType +type StrictNullChecksWrapper = undefined extends null + ? `strictNullChecks must be true in tsconfig to use ${Name}` + : Type -export type PartialSchema = Partial> +export type SomeJSONSchema = UncheckedJSONSchemaType -type JSONType = _partial extends true +type UncheckedPartialSchema = Partial> + +export type PartialSchema = StrictNullChecksWrapper<"PartialSchema", UncheckedPartialSchema> + +type JSONType = IsPartial extends true ? T | undefined : T @@ -23,22 +29,22 @@ interface StringKeywords { format?: string } -export type JSONSchemaType = ( +type UncheckedJSONSchemaType = ( | // these two unions allow arbitrary unions of types { - anyOf: readonly JSONSchemaType[] + anyOf: readonly UncheckedJSONSchemaType[] } | { - oneOf: readonly JSONSchemaType[] + oneOf: readonly UncheckedJSONSchemaType[] } // this union allows for { type: (primitive)[] } style schemas | ({ type: (T extends number - ? JSONType<"number" | "integer", _partial> + ? JSONType<"number" | "integer", IsPartial> : T extends string - ? JSONType<"string", _partial> + ? JSONType<"string", IsPartial> : T extends boolean - ? JSONType<"boolean", _partial> + ? JSONType<"boolean", IsPartial> : never)[] } & (T extends number ? NumberKeywords @@ -50,11 +56,11 @@ export type JSONSchemaType = ( // this covers "normal" types; it's last so typescript looks to it first for errors | ((T extends number ? { - type: JSONType<"number" | "integer", _partial> + type: JSONType<"number" | "integer", IsPartial> } & NumberKeywords : T extends string ? { - type: JSONType<"string", _partial> + type: JSONType<"string", IsPartial> } & StringKeywords : T extends boolean ? { @@ -63,17 +69,17 @@ export type JSONSchemaType = ( : T extends [any, ...any[]] ? { // JSON AnySchema for tuple - type: JSONType<"array", _partial> + type: JSONType<"array", IsPartial> items: { - readonly [K in keyof T]-?: JSONSchemaType & Nullable + readonly [K in keyof T]-?: UncheckedJSONSchemaType & Nullable } & {length: T["length"]} minItems: T["length"] } & ({maxItems: T["length"]} | {additionalItems: false}) : T extends readonly any[] ? { - type: JSONType<"array", _partial> - items: JSONSchemaType - contains?: PartialSchema + type: JSONType<"array", IsPartial> + items: UncheckedJSONSchemaType + contains?: UncheckedPartialSchema minItems?: number maxItems?: number minContains?: number @@ -87,62 +93,79 @@ export type JSONSchemaType = ( // "required" is not optional because it is often forgotten // "properties" are optional for more concise dictionary schemas // "patternProperties" and can be only used with interfaces that have string index - type: JSONType<"object", _partial> - additionalProperties?: boolean | JSONSchemaType - unevaluatedProperties?: boolean | JSONSchemaType - properties?: _partial extends true ? Partial> : PropertiesSchema - patternProperties?: {[Pattern in string]?: JSONSchemaType} - propertyNames?: Omit, "type"> & {type?: "string"} - dependencies?: {[K in keyof T]?: Readonly<(keyof T)[]> | PartialSchema} + type: JSONType<"object", IsPartial> + additionalProperties?: boolean | UncheckedJSONSchemaType + unevaluatedProperties?: boolean | UncheckedJSONSchemaType + properties?: IsPartial extends true + ? Partial> + : UncheckedPropertiesSchema + patternProperties?: {[Pattern in string]?: UncheckedJSONSchemaType} + propertyNames?: Omit, "type"> & {type?: "string"} + dependencies?: {[K in keyof T]?: Readonly<(keyof T)[]> | UncheckedPartialSchema} dependentRequired?: {[K in keyof T]?: Readonly<(keyof T)[]>} - dependentSchemas?: {[K in keyof T]?: PartialSchema} + dependentSchemas?: {[K in keyof T]?: UncheckedPartialSchema} minProperties?: number maxProperties?: number } & (// "required" type does not guarantee that all required properties // are listed it only asserts that optional cannot be listed. // "required" is not necessary if it's a non-partial type with no required keys - _partial extends true + IsPartial extends true ? {required: Readonly<(keyof T)[]>} - : [RequiredMembers] extends [never] - ? {required?: Readonly[]>} - : {required: Readonly[]>}) + : [UncheckedRequiredMembers] extends [never] + ? {required?: Readonly[]>} + : {required: Readonly[]>}) : T extends null ? { nullable: true } : never) & { - allOf?: Readonly[]> - anyOf?: Readonly[]> - oneOf?: Readonly[]> - if?: PartialSchema - then?: PartialSchema - else?: PartialSchema - not?: PartialSchema + allOf?: Readonly[]> + anyOf?: Readonly[]> + oneOf?: Readonly[]> + if?: UncheckedPartialSchema + then?: UncheckedPartialSchema + else?: UncheckedPartialSchema + not?: UncheckedPartialSchema }) ) & { [keyword: string]: any $id?: string $ref?: string $defs?: { - [Key in string]?: JSONSchemaType + [Key in string]?: UncheckedJSONSchemaType } definitions?: { - [Key in string]?: JSONSchemaType + [Key in string]?: UncheckedJSONSchemaType } } +export type JSONSchemaType = StrictNullChecksWrapper< + "JSONSchemaType", + UncheckedJSONSchemaType +> + type Known = KnownRecord | [Known, ...Known[]] | Known[] | number | string | boolean | null interface KnownRecord extends Record {} -export type PropertiesSchema = { - [K in keyof T]-?: (JSONSchemaType & Nullable) | {$ref: string} +type UncheckedPropertiesSchema = { + [K in keyof T]-?: (UncheckedJSONSchemaType & Nullable) | {$ref: string} } -export type RequiredMembers = { +export type PropertiesSchema = StrictNullChecksWrapper< + "PropertiesSchema", + UncheckedPropertiesSchema +> + +type UncheckedRequiredMembers = { [K in keyof T]-?: undefined extends T[K] ? never : K }[keyof T] +export type RequiredMembers = StrictNullChecksWrapper< + "RequiredMembers", + UncheckedRequiredMembers +> + type Nullable = undefined extends T ? { nullable: true