-
Notifications
You must be signed in to change notification settings - Fork 408
/
Copy pathtypesOfSchema.ts
149 lines (144 loc) · 3.9 KB
/
typesOfSchema.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
import {isPlainObject} from 'lodash'
import {isCompound, JSONSchema, SchemaType} from './types/JSONSchema'
/**
* Duck types a JSONSchema schema or property to determine which kind of AST node to parse it into.
*
* Due to what some might say is an oversight in the JSON-Schema spec, a given schema may
* implicitly be an *intersection* of multiple JSON-Schema directives (ie. multiple TypeScript
* types). The spec leaves it up to implementations to decide what to do with this
* loosely-defined behavior.
*/
export function typesOfSchema(schema: JSONSchema): readonly [SchemaType, ...SchemaType[]] {
// tsType is an escape hatch that supercedes all other directives
if (schema.tsType) {
return ['CUSTOM_TYPE']
}
// Collect matched types
const matchedTypes: SchemaType[] = []
for (const [schemaType, f] of Object.entries(matchers)) {
if (f(schema)) {
matchedTypes.push(schemaType as SchemaType)
}
}
// Default to an unnamed schema
if (!matchedTypes.length) {
return ['UNNAMED_SCHEMA']
}
return matchedTypes as [SchemaType, ...SchemaType[]]
}
const matchers: Record<SchemaType, (schema: JSONSchema) => boolean> = {
ALL_OF(schema) {
return 'allOf' in schema
},
ANY(schema) {
if (Object.keys(schema).length === 0) {
// The empty schema {} validates any value
// @see https://json-schema.org/draft-07/json-schema-core.html#rfc.section.4.3.1
return true
}
return schema.type === 'any'
},
ANY_OF(schema) {
return 'anyOf' in schema
},
BOOLEAN(schema) {
if ('enum' in schema) {
return false
}
if (schema.type === 'boolean') {
return true
}
if (!isCompound(schema) && typeof schema.default === 'boolean') {
return true
}
return false
},
CUSTOM_TYPE() {
return false // Explicitly handled before we try to match
},
NAMED_ENUM(schema) {
return 'enum' in schema && 'tsEnumNames' in schema
},
NAMED_SCHEMA(schema) {
// 8.2.1. The presence of "$id" in a subschema indicates that the subschema constitutes a distinct schema resource within a single schema document.
return '$id' in schema && ('patternProperties' in schema || 'properties' in schema)
},
NEVER(schema: JSONSchema | boolean) {
return schema === false
},
NULL(schema) {
return schema.type === 'null'
},
NUMBER(schema) {
if ('enum' in schema) {
return false
}
if (schema.type === 'integer' || schema.type === 'number') {
return true
}
if (!isCompound(schema) && typeof schema.default === 'number') {
return true
}
return false
},
OBJECT(schema) {
return (
schema.type === 'object' &&
!isPlainObject(schema.additionalProperties) &&
!schema.allOf &&
!schema.anyOf &&
!schema.oneOf &&
!schema.patternProperties &&
!schema.properties &&
!schema.required
)
},
ONE_OF(schema) {
return 'oneOf' in schema
},
REFERENCE(schema) {
return '$ref' in schema
},
STRING(schema) {
if ('enum' in schema) {
return false
}
if (schema.type === 'string') {
return true
}
if (!isCompound(schema) && typeof schema.default === 'string') {
return true
}
return false
},
TYPED_ARRAY(schema) {
if (schema.type && schema.type !== 'array') {
return false
}
return 'items' in schema
},
UNION(schema) {
return Array.isArray(schema.type)
},
UNNAMED_ENUM(schema) {
if ('tsEnumNames' in schema) {
return false
}
if (
schema.type &&
schema.type !== 'boolean' &&
schema.type !== 'integer' &&
schema.type !== 'number' &&
schema.type !== 'string'
) {
return false
}
return 'enum' in schema
},
UNNAMED_SCHEMA(schema) {
return !('$id' in schema) && ('patternProperties' in schema || 'properties' in schema)
},
UNTYPED_ARRAY(schema) {
return schema.type === 'array' && !('items' in schema)
},
}