Mirror of https://github.com/roostorg/coop
github.com/roostorg/coop
1/**
2 * @fileoverview These types help with typing JSON schemas. They're all copied
3 * from AJV v7 (https://github.com/ajv-validator/ajv/blob/master/lib/types/json-schema.ts),
4 * but then **modified for JSON Schema draft 4's format** where needed.
5 */
6
7// These types mostly come from the Ajv package and are just meant to be flexible
8// to generate TS types from JSON Schema values, so it's fine for them to have `any`s.
9/* eslint-disable @typescript-eslint/no-explicit-any */
10
11type UnionToIntersection<U> = (U extends any ? (_: U) => void : never) extends (
12 _: infer I,
13) => void
14 ? I
15 : never;
16
17export type SomeJSONSchema = UncheckedJSONSchemaType<JSON, true>;
18
19export type PartialSchema<T> = Partial<UncheckedJSONSchemaType<T, true>>;
20
21type JSONType<
22 T extends string,
23 IsPartial extends boolean,
24> = IsPartial extends true ? T | undefined : T;
25
26interface NumberKeywords {
27 minimum?: number;
28 maximum?: number;
29 // In JSON Schema draft 6, exclusiveMinimum and exclusiveMaximum are numbers.
30 // But, for draft 4, they are bools that change the interpretation of max/min.
31 exclusiveMinimum?: boolean;
32 exclusiveMaximum?: boolean;
33 multipleOf?: number;
34 format?: string;
35}
36
37interface StringKeywords {
38 minLength?: number;
39 maxLength?: number;
40 pattern?: string;
41 format?: string;
42}
43
44type UncheckedJSONSchemaType<T, IsPartial extends boolean> = (
45 | // these two unions allow arbitrary unions of types
46 {
47 anyOf: readonly UncheckedJSONSchemaType<T, IsPartial>[];
48 }
49 | {
50 oneOf: readonly UncheckedJSONSchemaType<T, IsPartial>[];
51 }
52 // this union allows for { type: (primitive)[] } style schemas
53 | ({
54 type: readonly (T extends number
55 ? JSONType<'number' | 'integer', IsPartial>
56 : T extends string
57 ? JSONType<'string', IsPartial>
58 : T extends boolean
59 ? JSONType<'boolean', IsPartial>
60 : T extends null
61 ? JSONType<'null', IsPartial>
62 : never)[];
63 } & UnionToIntersection<
64 T extends number
65 ? NumberKeywords
66 : T extends string
67 ? StringKeywords
68 : T extends boolean
69 ? // eslint-disable-next-line @typescript-eslint/no-restricted-types
70 {}
71 : never
72 >)
73 // this covers "normal" types; it's last so typescript looks to it first for errors
74 | ((T extends number
75 ? {
76 type: JSONType<'number' | 'integer', IsPartial>;
77 } & NumberKeywords
78 : T extends string
79 ? {
80 type: JSONType<'string', IsPartial>;
81 } & StringKeywords
82 : T extends boolean
83 ? {
84 type: JSONType<'boolean', IsPartial>;
85 }
86 : T extends readonly [any, ...any[]]
87 ? {
88 // JSON AnySchema for tuple
89 type: JSONType<'array', IsPartial>;
90 items: {
91 readonly [K in keyof T]-?: UncheckedJSONSchemaType<T[K], false> &
92 Nullable<T[K]>;
93 } & { length: T['length'] };
94 minItems: T['length'];
95 } & ({ maxItems: T['length'] } | { additionalItems: false })
96 : T extends readonly any[]
97 ? {
98 type: JSONType<'array', IsPartial>;
99 items: UncheckedJSONSchemaType<T[0], false>;
100 minItems?: number;
101 maxItems?: number;
102 uniqueItems?: true;
103 additionalItems?: never;
104 }
105 : T extends Record<string, any>
106 ? {
107 // JSON AnySchema for records and dictionaries
108 // "required" is not optional because it is often forgotten
109 // "properties" are optional for more concise dictionary schemas
110 // "patternProperties" and can be only used with interfaces that have string index
111 type: JSONType<'object', IsPartial>;
112 additionalProperties?:
113 | boolean
114 | UncheckedJSONSchemaType<T[string], false>;
115 properties?: IsPartial extends true
116 ? Partial<PropertiesSchema<T>>
117 : PropertiesSchema<T>;
118 patternProperties?: Record<
119 string,
120 UncheckedJSONSchemaType<T[string], false>
121 >;
122 dependencies?: {
123 [K in keyof T]?: Readonly<(keyof T)[]> | PartialSchema<T>;
124 };
125 dependentSchemas?: { [K in keyof T]?: PartialSchema<T> };
126 minProperties?: number;
127 maxProperties?: number;
128 } & (IsPartial extends true // "required" is not necessary if it's a non-partial type with no required keys // are listed it only asserts that optional cannot be listed. // "required" type does not guarantee that all required properties
129 ? { required: Readonly<(keyof T)[]> }
130 : [RequiredMembers<T>] extends [never]
131 ? { required?: Readonly<RequiredMembers<T>[]> }
132 : { required: Readonly<RequiredMembers<T>[]> })
133 : T extends null
134 ? { type: JSONType<'null', IsPartial> }
135 : never) & {
136 allOf?: Readonly<PartialSchema<T>[]>;
137 anyOf?: Readonly<PartialSchema<T>[]>;
138 oneOf?: Readonly<PartialSchema<T>[]>;
139 not?: PartialSchema<T>;
140 })
141) & {
142 [keyword: string]: any;
143 id?: string; // was not $id until draft 6.
144 $ref?: string;
145 definitions?: Record<string, UncheckedJSONSchemaType<JSON, true>>;
146};
147
148export type JSONSchemaV4<T> = UncheckedJSONSchemaType<T, false>;
149
150export type JSON =
151 | { [key: string]: JSON }
152 | JSON[]
153 | number
154 | string
155 | boolean
156 | null;
157
158export type PropertiesSchema<T> = {
159 [K in keyof T]-?:
160 | (UncheckedJSONSchemaType<T[K], false> & Nullable<T[K]>)
161 | { $ref: string };
162};
163
164export type RequiredMembers<T> = {
165 [K in keyof T]-?: undefined extends T[K] ? never : K;
166}[keyof T];
167
168type Nullable<T> = undefined extends T
169 ? {
170 enum?: Readonly<(T | null)[]>; // `null` must be explicitly included in "enum" for `null` to pass
171 default?: T | null;
172 }
173 : {
174 enum?: Readonly<T[]>;
175 default?: T;
176 };
177
178/* eslint-enable @typescript-eslint/no-explicit-any */