Skip to content

Commit

Permalink
Add checks for strict null checks
Browse files Browse the repository at this point in the history
  • Loading branch information
erikbrinkman committed Apr 29, 2021
1 parent 2123bf2 commit 5cccbdf
Showing 1 changed file with 63 additions and 40 deletions.
103 changes: 63 additions & 40 deletions lib/types/json-schema.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
/* eslint-disable @typescript-eslint/no-empty-interface */
export type SomeJSONSchema = JSONSchemaType<Known, true>
type StrictNullChecksWrapper<Name extends string, Type> = undefined extends null

This comment has been minimized.

Copy link
@JamesJansson

JamesJansson Jun 14, 2021

Contributor

Can I ask, is there a strong need for this? This wrapper prevents use in the case that we are not ready to fix the often thousands of strict null errors in a typical project, and hence not able to set strictNullChecks: true.

This comment has been minimized.

Copy link
@erikbrinkman

erikbrinkman Jun 14, 2021

Author Collaborator

The rational is that these classes don't work if strict null checks isn't turned on. It can't determine which members are required, and the result is many people submit issues like "why is this not work working" and the answer is they don't have strict null checks.

See #1583 for more details.

This comment has been minimized.

Copy link
@JamesJansson

JamesJansson Jun 15, 2021

Contributor

Thanks!

? `strictNullChecks must be true in tsconfig to use ${Name}`
: Type

export type PartialSchema<T> = Partial<JSONSchemaType<T, true>>
export type SomeJSONSchema = UncheckedJSONSchemaType<Known, true>

type JSONType<T extends string, _partial extends boolean> = _partial extends true
type UncheckedPartialSchema<T> = Partial<UncheckedJSONSchemaType<T, true>>

export type PartialSchema<T> = StrictNullChecksWrapper<"PartialSchema", UncheckedPartialSchema<T>>

type JSONType<T extends string, IsPartial extends boolean> = IsPartial extends true
? T | undefined
: T

Expand All @@ -23,22 +29,22 @@ interface StringKeywords {
format?: string
}

export type JSONSchemaType<T, _partial extends boolean = false> = (
type UncheckedJSONSchemaType<T, IsPartial extends boolean> = (
| // these two unions allow arbitrary unions of types
{
anyOf: readonly JSONSchemaType<T, _partial>[]
anyOf: readonly UncheckedJSONSchemaType<T, IsPartial>[]
}
| {
oneOf: readonly JSONSchemaType<T, _partial>[]
oneOf: readonly UncheckedJSONSchemaType<T, IsPartial>[]
}
// 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
Expand All @@ -50,11 +56,11 @@ export type JSONSchemaType<T, _partial extends boolean = false> = (
// 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
? {
Expand All @@ -63,17 +69,17 @@ export type JSONSchemaType<T, _partial extends boolean = false> = (
: T extends [any, ...any[]]
? {
// JSON AnySchema for tuple
type: JSONType<"array", _partial>
type: JSONType<"array", IsPartial>
items: {
readonly [K in keyof T]-?: JSONSchemaType<T[K]> & Nullable<T[K]>
readonly [K in keyof T]-?: UncheckedJSONSchemaType<T[K], false> & Nullable<T[K]>
} & {length: T["length"]}
minItems: T["length"]
} & ({maxItems: T["length"]} | {additionalItems: false})
: T extends readonly any[]
? {
type: JSONType<"array", _partial>
items: JSONSchemaType<T[0]>
contains?: PartialSchema<T[0]>
type: JSONType<"array", IsPartial>
items: UncheckedJSONSchemaType<T[0], false>
contains?: UncheckedPartialSchema<T[0]>
minItems?: number
maxItems?: number
minContains?: number
Expand All @@ -87,62 +93,79 @@ export type JSONSchemaType<T, _partial extends boolean = false> = (
// "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<T[string]>
unevaluatedProperties?: boolean | JSONSchemaType<T[string]>
properties?: _partial extends true ? Partial<PropertiesSchema<T>> : PropertiesSchema<T>
patternProperties?: {[Pattern in string]?: JSONSchemaType<T[string]>}
propertyNames?: Omit<JSONSchemaType<string>, "type"> & {type?: "string"}
dependencies?: {[K in keyof T]?: Readonly<(keyof T)[]> | PartialSchema<T>}
type: JSONType<"object", IsPartial>
additionalProperties?: boolean | UncheckedJSONSchemaType<T[string], false>
unevaluatedProperties?: boolean | UncheckedJSONSchemaType<T[string], false>
properties?: IsPartial extends true
? Partial<UncheckedPropertiesSchema<T>>
: UncheckedPropertiesSchema<T>
patternProperties?: {[Pattern in string]?: UncheckedJSONSchemaType<T[string], false>}
propertyNames?: Omit<UncheckedJSONSchemaType<string, false>, "type"> & {type?: "string"}
dependencies?: {[K in keyof T]?: Readonly<(keyof T)[]> | UncheckedPartialSchema<T>}
dependentRequired?: {[K in keyof T]?: Readonly<(keyof T)[]>}
dependentSchemas?: {[K in keyof T]?: PartialSchema<T>}
dependentSchemas?: {[K in keyof T]?: UncheckedPartialSchema<T>}
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<T>] extends [never]
? {required?: Readonly<RequiredMembers<T>[]>}
: {required: Readonly<RequiredMembers<T>[]>})
: [UncheckedRequiredMembers<T>] extends [never]
? {required?: Readonly<UncheckedRequiredMembers<T>[]>}
: {required: Readonly<UncheckedRequiredMembers<T>[]>})
: T extends null
? {
nullable: true
}
: never) & {
allOf?: Readonly<PartialSchema<T>[]>
anyOf?: Readonly<PartialSchema<T>[]>
oneOf?: Readonly<PartialSchema<T>[]>
if?: PartialSchema<T>
then?: PartialSchema<T>
else?: PartialSchema<T>
not?: PartialSchema<T>
allOf?: Readonly<UncheckedPartialSchema<T>[]>
anyOf?: Readonly<UncheckedPartialSchema<T>[]>
oneOf?: Readonly<UncheckedPartialSchema<T>[]>
if?: UncheckedPartialSchema<T>
then?: UncheckedPartialSchema<T>
else?: UncheckedPartialSchema<T>
not?: UncheckedPartialSchema<T>
})
) & {
[keyword: string]: any
$id?: string
$ref?: string
$defs?: {
[Key in string]?: JSONSchemaType<Known, true>
[Key in string]?: UncheckedJSONSchemaType<Known, true>
}
definitions?: {
[Key in string]?: JSONSchemaType<Known, true>
[Key in string]?: UncheckedJSONSchemaType<Known, true>
}
}

export type JSONSchemaType<T> = StrictNullChecksWrapper<
"JSONSchemaType",
UncheckedJSONSchemaType<T, false>
>

type Known = KnownRecord | [Known, ...Known[]] | Known[] | number | string | boolean | null

interface KnownRecord extends Record<string, Known> {}

export type PropertiesSchema<T> = {
[K in keyof T]-?: (JSONSchemaType<T[K]> & Nullable<T[K]>) | {$ref: string}
type UncheckedPropertiesSchema<T> = {
[K in keyof T]-?: (UncheckedJSONSchemaType<T[K], false> & Nullable<T[K]>) | {$ref: string}
}

export type RequiredMembers<T> = {
export type PropertiesSchema<T> = StrictNullChecksWrapper<
"PropertiesSchema",
UncheckedPropertiesSchema<T>
>

type UncheckedRequiredMembers<T> = {
[K in keyof T]-?: undefined extends T[K] ? never : K
}[keyof T]

export type RequiredMembers<T> = StrictNullChecksWrapper<
"RequiredMembers",
UncheckedRequiredMembers<T>
>

type Nullable<T> = undefined extends T
? {
nullable: true
Expand Down

0 comments on commit 5cccbdf

Please sign in to comment.