@amritk/generate-parsers 0.7.2 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -1
- package/dist/generators/build-schema.d.ts +0 -1
- package/dist/generators/generate-default-from-pattern.d.ts +0 -1
- package/dist/generators/generate-discriminated-union-validation.d.ts +0 -1
- package/dist/generators/generate-enum-check.d.ts +0 -1
- package/dist/generators/generate-files.d.ts +0 -1
- package/dist/generators/generate-non-discriminated-union-validation.d.ts +0 -1
- package/dist/generators/generate-parser-function.d.ts +3 -3
- package/dist/generators/generate-parser-function.js +164 -29
- package/dist/generators/generate-schema-checks.d.ts +0 -1
- package/dist/generators/generate-strict-assertion.d.ts +0 -1
- package/dist/generators/generate-validation-expression.d.ts +0 -1
- package/dist/helpers/apply-schema-extensions.d.ts +0 -1
- package/dist/helpers/collect-helpers.d.ts +0 -1
- package/dist/helpers/collect-imports.d.ts +0 -1
- package/dist/helpers/find-discriminator.d.ts +0 -1
- package/dist/helpers/get-default-value.d.ts +0 -1
- package/dist/helpers/get-discriminator-value.d.ts +0 -1
- package/dist/index.d.ts +0 -1
- package/dist/types/schema-extensions.d.ts +0 -1
- package/package.json +3 -5
- package/dist/generators/build-schema.d.ts.map +0 -1
- package/dist/generators/generate-default-from-pattern.d.ts.map +0 -1
- package/dist/generators/generate-discriminated-union-validation.d.ts.map +0 -1
- package/dist/generators/generate-enum-check.d.ts.map +0 -1
- package/dist/generators/generate-files.d.ts.map +0 -1
- package/dist/generators/generate-non-discriminated-union-validation.d.ts.map +0 -1
- package/dist/generators/generate-parser-function.d.ts.map +0 -1
- package/dist/generators/generate-schema-checks.d.ts.map +0 -1
- package/dist/generators/generate-strict-assertion.d.ts.map +0 -1
- package/dist/generators/generate-validation-expression.d.ts.map +0 -1
- package/dist/helpers/apply-schema-extensions.d.ts.map +0 -1
- package/dist/helpers/collect-helpers.d.ts.map +0 -1
- package/dist/helpers/collect-imports.d.ts.map +0 -1
- package/dist/helpers/find-discriminator.d.ts.map +0 -1
- package/dist/helpers/get-default-value.d.ts.map +0 -1
- package/dist/helpers/get-discriminator-value.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/dist/types/schema-extensions.d.ts.map +0 -1
- package/src/generators/build-schema.test.ts +0 -854
- package/src/generators/build-schema.ts +0 -147
- package/src/generators/generate-default-from-pattern.test.ts +0 -81
- package/src/generators/generate-default-from-pattern.ts +0 -39
- package/src/generators/generate-discriminated-union-validation.test.ts +0 -84
- package/src/generators/generate-discriminated-union-validation.ts +0 -54
- package/src/generators/generate-enum-check.test.ts +0 -45
- package/src/generators/generate-enum-check.ts +0 -8
- package/src/generators/generate-files.test.ts +0 -448
- package/src/generators/generate-files.ts +0 -155
- package/src/generators/generate-non-discriminated-union-validation.test.ts +0 -74
- package/src/generators/generate-non-discriminated-union-validation.ts +0 -40
- package/src/generators/generate-parser-function.test.ts +0 -1794
- package/src/generators/generate-parser-function.ts +0 -1340
- package/src/generators/generate-schema-checks.test.ts +0 -330
- package/src/generators/generate-schema-checks.ts +0 -128
- package/src/generators/generate-strict-assertion.test.ts +0 -67
- package/src/generators/generate-strict-assertion.ts +0 -243
- package/src/generators/generate-validation-expression.test.ts +0 -831
- package/src/generators/generate-validation-expression.ts +0 -327
- package/src/helpers/apply-schema-extensions.test.ts +0 -194
- package/src/helpers/apply-schema-extensions.ts +0 -63
- package/src/helpers/collect-helpers.test.ts +0 -115
- package/src/helpers/collect-helpers.ts +0 -79
- package/src/helpers/collect-imports.test.ts +0 -547
- package/src/helpers/collect-imports.ts +0 -275
- package/src/helpers/find-discriminator.test.ts +0 -88
- package/src/helpers/find-discriminator.ts +0 -39
- package/src/helpers/get-default-value.test.ts +0 -139
- package/src/helpers/get-default-value.ts +0 -95
- package/src/helpers/get-discriminator-value.test.ts +0 -83
- package/src/helpers/get-discriminator-value.ts +0 -21
- package/src/index.ts +0 -1
- package/src/types/schema-extensions.ts +0 -35
|
@@ -1,330 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import { generateSchemaChecks } from './generate-schema-checks'
|
|
4
|
-
|
|
5
|
-
describe('generate-schema-checks', () => {
|
|
6
|
-
it('returns empty checks for a schema without type and no inferrable keywords', () => {
|
|
7
|
-
const result = generateSchemaChecks('value', { description: 'no type' })
|
|
8
|
-
expect(result).toEqual([])
|
|
9
|
-
})
|
|
10
|
-
|
|
11
|
-
it.todo('infers object type from properties keyword', () => {
|
|
12
|
-
const result = generateSchemaChecks('value', {
|
|
13
|
-
properties: { id: { type: 'string' } },
|
|
14
|
-
})
|
|
15
|
-
expect(result).toEqual(['typeof value === "object" && value !== null && !Array.isArray(value)'])
|
|
16
|
-
})
|
|
17
|
-
|
|
18
|
-
it.todo('infers object type from required keyword', () => {
|
|
19
|
-
const result = generateSchemaChecks('value', {
|
|
20
|
-
required: ['id', 'name'],
|
|
21
|
-
})
|
|
22
|
-
expect(result).toEqual([
|
|
23
|
-
'typeof value === "object" && value !== null && !Array.isArray(value)',
|
|
24
|
-
'"id" in value',
|
|
25
|
-
'"name" in value',
|
|
26
|
-
])
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
it.todo('infers array type from items keyword', () => {
|
|
30
|
-
const result = generateSchemaChecks('value', {
|
|
31
|
-
items: { type: 'string' },
|
|
32
|
-
})
|
|
33
|
-
expect(result).toEqual(['Array.isArray(value)'])
|
|
34
|
-
})
|
|
35
|
-
|
|
36
|
-
it.todo('infers array type from minItems/maxItems keywords', () => {
|
|
37
|
-
const result = generateSchemaChecks('value', {
|
|
38
|
-
minItems: 1,
|
|
39
|
-
maxItems: 10,
|
|
40
|
-
})
|
|
41
|
-
expect(result).toEqual(['Array.isArray(value)', 'value.length >= 1', 'value.length <= 10'])
|
|
42
|
-
})
|
|
43
|
-
|
|
44
|
-
it.todo('infers string type from minLength keyword', () => {
|
|
45
|
-
const result = generateSchemaChecks('value', {
|
|
46
|
-
minLength: 1,
|
|
47
|
-
})
|
|
48
|
-
expect(result).toEqual(['typeof value === "string"', 'value.length >= 1'])
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
it.todo('infers string type from pattern keyword', () => {
|
|
52
|
-
const result = generateSchemaChecks('value', {
|
|
53
|
-
pattern: '^[a-z]+$',
|
|
54
|
-
})
|
|
55
|
-
expect(result).toEqual(['typeof value === "string"', '/^[a-z]+$/.test(value)'])
|
|
56
|
-
})
|
|
57
|
-
|
|
58
|
-
it.todo('infers number type from minimum keyword', () => {
|
|
59
|
-
const result = generateSchemaChecks('value', {
|
|
60
|
-
minimum: 0,
|
|
61
|
-
})
|
|
62
|
-
expect(result).toEqual(['typeof value === "number"', 'value >= 0'])
|
|
63
|
-
})
|
|
64
|
-
|
|
65
|
-
it.todo('infers number type from multipleOf keyword', () => {
|
|
66
|
-
const result = generateSchemaChecks('value', {
|
|
67
|
-
multipleOf: 5,
|
|
68
|
-
})
|
|
69
|
-
expect(result).toEqual(['typeof value === "number"', 'value % 5 === 0'])
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
it.todo('infers boolean type from const boolean value', () => {
|
|
73
|
-
const result = generateSchemaChecks('value', {
|
|
74
|
-
const: true,
|
|
75
|
-
})
|
|
76
|
-
expect(result).toEqual(['typeof value === "boolean"'])
|
|
77
|
-
})
|
|
78
|
-
|
|
79
|
-
it.todo('infers null type from const null value', () => {
|
|
80
|
-
const result = generateSchemaChecks('value', {
|
|
81
|
-
const: null,
|
|
82
|
-
})
|
|
83
|
-
expect(result).toEqual(['value === null'])
|
|
84
|
-
})
|
|
85
|
-
|
|
86
|
-
it.todo('infers null type from all-null enum', () => {
|
|
87
|
-
const result = generateSchemaChecks('value', {
|
|
88
|
-
enum: [null, null],
|
|
89
|
-
})
|
|
90
|
-
expect(result).toEqual(['[null,null].includes(value as never)'])
|
|
91
|
-
})
|
|
92
|
-
|
|
93
|
-
// When object and array keywords tie, object wins (matches inferSchemaType priority)
|
|
94
|
-
it.todo('prefers object over array when keyword scores tie', () => {
|
|
95
|
-
const result = generateSchemaChecks('value', {
|
|
96
|
-
minProperties: 1,
|
|
97
|
-
minItems: 1,
|
|
98
|
-
})
|
|
99
|
-
expect(result).toContain('typeof value === "object" && value !== null && !Array.isArray(value)')
|
|
100
|
-
expect(result).not.toContain('Array.isArray(value)')
|
|
101
|
-
})
|
|
102
|
-
|
|
103
|
-
// Array keywords outnumber string keywords so array wins
|
|
104
|
-
it.todo('selects highest scoring type when multiple keyword categories present', () => {
|
|
105
|
-
const result = generateSchemaChecks('value', {
|
|
106
|
-
minItems: 1,
|
|
107
|
-
maxItems: 3,
|
|
108
|
-
minLength: 2,
|
|
109
|
-
})
|
|
110
|
-
expect(result).toContain('Array.isArray(value)')
|
|
111
|
-
expect(result).not.toContain('typeof value === "string"')
|
|
112
|
-
})
|
|
113
|
-
|
|
114
|
-
it.todo('applies inferred type constraints alongside explicit enum check', () => {
|
|
115
|
-
const result = generateSchemaChecks('value', {
|
|
116
|
-
minimum: 0,
|
|
117
|
-
maximum: 10,
|
|
118
|
-
enum: [0, 5, 10],
|
|
119
|
-
})
|
|
120
|
-
expect(result).toContain('typeof value === "number"')
|
|
121
|
-
expect(result).toContain('value >= 0')
|
|
122
|
-
expect(result).toContain('value <= 10')
|
|
123
|
-
expect(result).toContain('[0,5,10].includes(value as never)')
|
|
124
|
-
})
|
|
125
|
-
|
|
126
|
-
it('returns empty checks for a boolean schema', () => {
|
|
127
|
-
const result = generateSchemaChecks('value', true)
|
|
128
|
-
expect(result).toEqual([])
|
|
129
|
-
})
|
|
130
|
-
|
|
131
|
-
it('generates typeof check for string type', () => {
|
|
132
|
-
const result = generateSchemaChecks('value', { type: 'string' })
|
|
133
|
-
expect(result).toEqual(['typeof value === "string"'])
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
it('generates string check with pattern', () => {
|
|
137
|
-
const result = generateSchemaChecks('value', { type: 'string', pattern: '^[a-z]+$' })
|
|
138
|
-
expect(result).toEqual(['typeof value === "string"', '/^[a-z]+$/.test(value)'])
|
|
139
|
-
})
|
|
140
|
-
|
|
141
|
-
it('generates string check with minLength', () => {
|
|
142
|
-
const result = generateSchemaChecks('value', { type: 'string', minLength: 1 })
|
|
143
|
-
expect(result).toEqual(['typeof value === "string"', 'value.length >= 1'])
|
|
144
|
-
})
|
|
145
|
-
|
|
146
|
-
it('generates string check with maxLength', () => {
|
|
147
|
-
const result = generateSchemaChecks('value', { type: 'string', maxLength: 100 })
|
|
148
|
-
expect(result).toEqual(['typeof value === "string"', 'value.length <= 100'])
|
|
149
|
-
})
|
|
150
|
-
|
|
151
|
-
it('generates string check with both min and max length', () => {
|
|
152
|
-
const result = generateSchemaChecks('value', { type: 'string', minLength: 1, maxLength: 50 })
|
|
153
|
-
expect(result).toEqual(['typeof value === "string"', 'value.length >= 1', 'value.length <= 50'])
|
|
154
|
-
})
|
|
155
|
-
|
|
156
|
-
it('generates typeof check for number type', () => {
|
|
157
|
-
const result = generateSchemaChecks('value', { type: 'number' })
|
|
158
|
-
expect(result).toEqual(['typeof value === "number"'])
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
it('generates number check with minimum', () => {
|
|
162
|
-
const result = generateSchemaChecks('value', { type: 'number', minimum: 0 })
|
|
163
|
-
expect(result).toEqual(['typeof value === "number"', 'value >= 0'])
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
it('generates number check with maximum', () => {
|
|
167
|
-
const result = generateSchemaChecks('value', { type: 'number', maximum: 100 })
|
|
168
|
-
expect(result).toEqual(['typeof value === "number"', 'value <= 100'])
|
|
169
|
-
})
|
|
170
|
-
|
|
171
|
-
it('generates number check with exclusiveMinimum', () => {
|
|
172
|
-
const result = generateSchemaChecks('value', { type: 'number', exclusiveMinimum: 0 })
|
|
173
|
-
expect(result).toEqual(['typeof value === "number"', 'value > 0'])
|
|
174
|
-
})
|
|
175
|
-
|
|
176
|
-
it('generates number check with exclusiveMaximum', () => {
|
|
177
|
-
const result = generateSchemaChecks('value', { type: 'number', exclusiveMaximum: 100 })
|
|
178
|
-
expect(result).toEqual(['typeof value === "number"', 'value < 100'])
|
|
179
|
-
})
|
|
180
|
-
|
|
181
|
-
it('generates number check with multipleOf', () => {
|
|
182
|
-
const result = generateSchemaChecks('value', { type: 'number', multipleOf: 5 })
|
|
183
|
-
expect(result).toEqual(['typeof value === "number"', 'value % 5 === 0'])
|
|
184
|
-
})
|
|
185
|
-
|
|
186
|
-
it('generates typeof check for integer type', () => {
|
|
187
|
-
const result = generateSchemaChecks('value', { type: 'integer' })
|
|
188
|
-
expect(result).toEqual(['typeof value === "number"'])
|
|
189
|
-
})
|
|
190
|
-
|
|
191
|
-
it('generates integer check with all numeric constraints', () => {
|
|
192
|
-
const result = generateSchemaChecks('value', {
|
|
193
|
-
type: 'integer',
|
|
194
|
-
minimum: 1,
|
|
195
|
-
maximum: 10,
|
|
196
|
-
exclusiveMinimum: 0,
|
|
197
|
-
exclusiveMaximum: 11,
|
|
198
|
-
multipleOf: 2,
|
|
199
|
-
})
|
|
200
|
-
expect(result).toEqual([
|
|
201
|
-
'typeof value === "number"',
|
|
202
|
-
'value >= 1',
|
|
203
|
-
'value <= 10',
|
|
204
|
-
'value > 0',
|
|
205
|
-
'value < 11',
|
|
206
|
-
'value % 2 === 0',
|
|
207
|
-
])
|
|
208
|
-
})
|
|
209
|
-
|
|
210
|
-
it('generates typeof check for boolean type', () => {
|
|
211
|
-
const result = generateSchemaChecks('value', { type: 'boolean' })
|
|
212
|
-
expect(result).toEqual(['typeof value === "boolean"'])
|
|
213
|
-
})
|
|
214
|
-
|
|
215
|
-
it('generates Array.isArray check for array type', () => {
|
|
216
|
-
const result = generateSchemaChecks('value', { type: 'array' })
|
|
217
|
-
expect(result).toEqual(['Array.isArray(value)'])
|
|
218
|
-
})
|
|
219
|
-
|
|
220
|
-
it('generates array check with minItems', () => {
|
|
221
|
-
const result = generateSchemaChecks('value', { type: 'array', minItems: 1 })
|
|
222
|
-
expect(result).toEqual(['Array.isArray(value)', 'value.length >= 1'])
|
|
223
|
-
})
|
|
224
|
-
|
|
225
|
-
it('generates array check with maxItems', () => {
|
|
226
|
-
const result = generateSchemaChecks('value', { type: 'array', maxItems: 10 })
|
|
227
|
-
expect(result).toEqual(['Array.isArray(value)', 'value.length <= 10'])
|
|
228
|
-
})
|
|
229
|
-
|
|
230
|
-
it('generates array check with uniqueItems true', () => {
|
|
231
|
-
const result = generateSchemaChecks('value', { type: 'array', uniqueItems: true })
|
|
232
|
-
expect(result).toEqual(['Array.isArray(value)', 'new Set(value).size === value.length'])
|
|
233
|
-
})
|
|
234
|
-
|
|
235
|
-
it('does not add uniqueItems check when uniqueItems is false', () => {
|
|
236
|
-
const result = generateSchemaChecks('value', { type: 'array', uniqueItems: false })
|
|
237
|
-
expect(result).toEqual(['Array.isArray(value)'])
|
|
238
|
-
})
|
|
239
|
-
|
|
240
|
-
it('generates object type check', () => {
|
|
241
|
-
const result = generateSchemaChecks('value', { type: 'object' })
|
|
242
|
-
expect(result).toEqual(['typeof value === "object" && value !== null && !Array.isArray(value)'])
|
|
243
|
-
})
|
|
244
|
-
|
|
245
|
-
it('generates object check with required properties', () => {
|
|
246
|
-
const result = generateSchemaChecks('value', {
|
|
247
|
-
type: 'object',
|
|
248
|
-
required: ['name', 'age'],
|
|
249
|
-
})
|
|
250
|
-
expect(result).toEqual([
|
|
251
|
-
'typeof value === "object" && value !== null && !Array.isArray(value)',
|
|
252
|
-
'"name" in value',
|
|
253
|
-
'"age" in value',
|
|
254
|
-
])
|
|
255
|
-
})
|
|
256
|
-
|
|
257
|
-
it('generates object check with minProperties', () => {
|
|
258
|
-
const result = generateSchemaChecks('value', { type: 'object', minProperties: 1 })
|
|
259
|
-
expect(result).toEqual([
|
|
260
|
-
'typeof value === "object" && value !== null && !Array.isArray(value)',
|
|
261
|
-
'Object.keys(value).length >= 1',
|
|
262
|
-
])
|
|
263
|
-
})
|
|
264
|
-
|
|
265
|
-
it('generates object check with maxProperties', () => {
|
|
266
|
-
const result = generateSchemaChecks('value', { type: 'object', maxProperties: 5 })
|
|
267
|
-
expect(result).toEqual([
|
|
268
|
-
'typeof value === "object" && value !== null && !Array.isArray(value)',
|
|
269
|
-
'Object.keys(value).length <= 5',
|
|
270
|
-
])
|
|
271
|
-
})
|
|
272
|
-
|
|
273
|
-
it('generates object check with additionalProperties false', () => {
|
|
274
|
-
const result = generateSchemaChecks('value', {
|
|
275
|
-
type: 'object',
|
|
276
|
-
properties: { name: { type: 'string' } },
|
|
277
|
-
additionalProperties: false,
|
|
278
|
-
})
|
|
279
|
-
expect(result).toEqual([
|
|
280
|
-
'typeof value === "object" && value !== null && !Array.isArray(value)',
|
|
281
|
-
'Object.keys(value).every(k => ["name"].includes(k))',
|
|
282
|
-
])
|
|
283
|
-
})
|
|
284
|
-
|
|
285
|
-
it('does not add additionalProperties check when additionalProperties is true', () => {
|
|
286
|
-
const result = generateSchemaChecks('value', {
|
|
287
|
-
type: 'object',
|
|
288
|
-
properties: { name: { type: 'string' } },
|
|
289
|
-
additionalProperties: true,
|
|
290
|
-
})
|
|
291
|
-
expect(result).toEqual(['typeof value === "object" && value !== null && !Array.isArray(value)'])
|
|
292
|
-
})
|
|
293
|
-
|
|
294
|
-
it('does not add additionalProperties check when no properties defined', () => {
|
|
295
|
-
const result = generateSchemaChecks('value', {
|
|
296
|
-
type: 'object',
|
|
297
|
-
additionalProperties: false,
|
|
298
|
-
})
|
|
299
|
-
expect(result).toEqual(['typeof value === "object" && value !== null && !Array.isArray(value)'])
|
|
300
|
-
})
|
|
301
|
-
|
|
302
|
-
it('skips required check when required array is empty', () => {
|
|
303
|
-
const result = generateSchemaChecks('value', { type: 'object', required: [] })
|
|
304
|
-
expect(result).toEqual(['typeof value === "object" && value !== null && !Array.isArray(value)'])
|
|
305
|
-
})
|
|
306
|
-
|
|
307
|
-
it('appends enum check regardless of type', () => {
|
|
308
|
-
const result = generateSchemaChecks('value', { type: 'string', enum: ['a', 'b'] })
|
|
309
|
-
expect(result).toContain('typeof value === "string"')
|
|
310
|
-
expect(result).toContain('["a","b"].includes(value as never)')
|
|
311
|
-
})
|
|
312
|
-
|
|
313
|
-
it('does not append enum check when enum is empty', () => {
|
|
314
|
-
const result = generateSchemaChecks('value', { type: 'string', enum: [] })
|
|
315
|
-
expect(result).toEqual(['typeof value === "string"'])
|
|
316
|
-
})
|
|
317
|
-
|
|
318
|
-
it('uses the provided accessor in all checks', () => {
|
|
319
|
-
const result = generateSchemaChecks('input?.name', { type: 'string', minLength: 1 })
|
|
320
|
-
expect(result).toEqual(['typeof input?.name === "string"', 'input?.name.length >= 1'])
|
|
321
|
-
})
|
|
322
|
-
|
|
323
|
-
it('returns an instanceof check for an x-mjst instanceOf node', () => {
|
|
324
|
-
expect(generateSchemaChecks('value', { 'x-mjst': { instanceOf: 'Date' } })).toEqual(['value instanceof Date'])
|
|
325
|
-
})
|
|
326
|
-
|
|
327
|
-
it('returns a typeof check for an x-mjst bigint node', () => {
|
|
328
|
-
expect(generateSchemaChecks('value', { 'x-mjst': { primitive: 'bigint' } })).toEqual(['typeof value === "bigint"'])
|
|
329
|
-
})
|
|
330
|
-
})
|
|
@@ -1,128 +0,0 @@
|
|
|
1
|
-
import { escapeRegexPattern } from '@amritk/helpers/escape-regex-pattern'
|
|
2
|
-
import { getMjstInstanceOf, getMjstPrimitive } from '@amritk/helpers/mjst-extension'
|
|
3
|
-
import {
|
|
4
|
-
hasAdditionalProperties,
|
|
5
|
-
hasEnum,
|
|
6
|
-
hasExclusiveMaximum,
|
|
7
|
-
hasExclusiveMinimum,
|
|
8
|
-
hasMaxItems,
|
|
9
|
-
hasMaximum,
|
|
10
|
-
hasMaxLength,
|
|
11
|
-
hasMaxProperties,
|
|
12
|
-
hasMinItems,
|
|
13
|
-
hasMinimum,
|
|
14
|
-
hasMinLength,
|
|
15
|
-
hasMinProperties,
|
|
16
|
-
hasMultipleOf,
|
|
17
|
-
hasPattern,
|
|
18
|
-
hasProperties,
|
|
19
|
-
hasRequired,
|
|
20
|
-
hasType,
|
|
21
|
-
hasUniqueItems,
|
|
22
|
-
} from '@amritk/helpers/schema-guards'
|
|
23
|
-
import type { JSONSchema } from 'json-schema-typed/draft-2020-12'
|
|
24
|
-
|
|
25
|
-
import { generateEnumCheck } from './generate-enum-check'
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Generates validation checks for a single schema (used in union validation).
|
|
29
|
-
*/
|
|
30
|
-
export const generateSchemaChecks = (accessor: string, schema: JSONSchema): string[] => {
|
|
31
|
-
const checks: string[] = []
|
|
32
|
-
|
|
33
|
-
// x-mjst instanceOf nodes validate purely by instance check (no native type).
|
|
34
|
-
const instanceOf = getMjstInstanceOf(schema)
|
|
35
|
-
if (instanceOf) {
|
|
36
|
-
return [`${accessor} instanceof ${instanceOf}`]
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
// x-mjst primitive nodes (e.g. bigint) validate purely by typeof check.
|
|
40
|
-
const primitive = getMjstPrimitive(schema)
|
|
41
|
-
if (primitive) {
|
|
42
|
-
return [`typeof ${accessor} === "${primitive}"`]
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
if (!hasType(schema)) {
|
|
46
|
-
return checks
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
switch (schema.type) {
|
|
50
|
-
case 'string': {
|
|
51
|
-
checks.push(`typeof ${accessor} === "string"`)
|
|
52
|
-
if (hasPattern(schema)) {
|
|
53
|
-
checks.push(`/${escapeRegexPattern(schema.pattern)}/.test(${accessor})`)
|
|
54
|
-
}
|
|
55
|
-
if (hasMinLength(schema)) {
|
|
56
|
-
checks.push(`${accessor}.length >= ${schema.minLength}`)
|
|
57
|
-
}
|
|
58
|
-
if (hasMaxLength(schema)) {
|
|
59
|
-
checks.push(`${accessor}.length <= ${schema.maxLength}`)
|
|
60
|
-
}
|
|
61
|
-
break
|
|
62
|
-
}
|
|
63
|
-
case 'number':
|
|
64
|
-
case 'integer':
|
|
65
|
-
checks.push(`typeof ${accessor} === "number"`)
|
|
66
|
-
if (hasMinimum(schema)) {
|
|
67
|
-
checks.push(`${accessor} >= ${schema.minimum}`)
|
|
68
|
-
}
|
|
69
|
-
if (hasMaximum(schema)) {
|
|
70
|
-
checks.push(`${accessor} <= ${schema.maximum}`)
|
|
71
|
-
}
|
|
72
|
-
if (hasExclusiveMinimum(schema)) {
|
|
73
|
-
checks.push(`${accessor} > ${schema.exclusiveMinimum}`)
|
|
74
|
-
}
|
|
75
|
-
if (hasExclusiveMaximum(schema)) {
|
|
76
|
-
checks.push(`${accessor} < ${schema.exclusiveMaximum}`)
|
|
77
|
-
}
|
|
78
|
-
if (hasMultipleOf(schema)) {
|
|
79
|
-
checks.push(`${accessor} % ${schema.multipleOf} === 0`)
|
|
80
|
-
}
|
|
81
|
-
break
|
|
82
|
-
case 'boolean':
|
|
83
|
-
checks.push(`typeof ${accessor} === "boolean"`)
|
|
84
|
-
break
|
|
85
|
-
case 'array':
|
|
86
|
-
checks.push(`Array.isArray(${accessor})`)
|
|
87
|
-
if (hasMinItems(schema)) {
|
|
88
|
-
checks.push(`${accessor}.length >= ${schema.minItems}`)
|
|
89
|
-
}
|
|
90
|
-
if (hasMaxItems(schema)) {
|
|
91
|
-
checks.push(`${accessor}.length <= ${schema.maxItems}`)
|
|
92
|
-
}
|
|
93
|
-
if (hasUniqueItems(schema) && schema.uniqueItems === true) {
|
|
94
|
-
checks.push(`new Set(${accessor}).size === ${accessor}.length`)
|
|
95
|
-
}
|
|
96
|
-
break
|
|
97
|
-
case 'object': {
|
|
98
|
-
checks.push(`typeof ${accessor} === "object" && ${accessor} !== null && !Array.isArray(${accessor})`)
|
|
99
|
-
// Check required properties exist
|
|
100
|
-
if (hasRequired(schema) && schema.required.length > 0) {
|
|
101
|
-
for (const requiredKey of schema.required) {
|
|
102
|
-
checks.push(`"${requiredKey}" in ${accessor}`)
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
// Check minProperties
|
|
106
|
-
if (hasMinProperties(schema)) {
|
|
107
|
-
checks.push(`Object.keys(${accessor}).length >= ${schema.minProperties}`)
|
|
108
|
-
}
|
|
109
|
-
// Check maxProperties
|
|
110
|
-
if (hasMaxProperties(schema)) {
|
|
111
|
-
checks.push(`Object.keys(${accessor}).length <= ${schema.maxProperties}`)
|
|
112
|
-
}
|
|
113
|
-
// Check additionalProperties
|
|
114
|
-
if (hasAdditionalProperties(schema) && schema.additionalProperties === false && hasProperties(schema)) {
|
|
115
|
-
const allowedKeys = Object.keys(schema.properties)
|
|
116
|
-
const allowedKeysStr = JSON.stringify(allowedKeys)
|
|
117
|
-
checks.push(`Object.keys(${accessor}).every(k => ${allowedKeysStr}.includes(k))`)
|
|
118
|
-
}
|
|
119
|
-
break
|
|
120
|
-
}
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
if (hasEnum(schema) && schema.enum.length > 0) {
|
|
124
|
-
checks.push(generateEnumCheck(accessor, schema.enum))
|
|
125
|
-
}
|
|
126
|
-
|
|
127
|
-
return checks
|
|
128
|
-
}
|
|
@@ -1,67 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import { generateObjectStrictAssertion, generateScalarStrictAssertion } from './generate-strict-assertion'
|
|
4
|
-
|
|
5
|
-
describe('generate-strict-assertion x-mjst instanceOf', () => {
|
|
6
|
-
it('asserts instanceof for a required Date property', () => {
|
|
7
|
-
const schema = {
|
|
8
|
-
type: 'object' as const,
|
|
9
|
-
properties: { createdAt: { 'x-mjst': { instanceOf: 'Date' } } },
|
|
10
|
-
required: ['createdAt'],
|
|
11
|
-
}
|
|
12
|
-
const lines = generateObjectStrictAssertion(schema, 'Event').join('\n')
|
|
13
|
-
|
|
14
|
-
expect(lines).toContain('missing required property "createdAt"')
|
|
15
|
-
expect(lines).toContain('!(input.createdAt instanceof Date)')
|
|
16
|
-
expect(lines).toContain('field "createdAt" must be Date')
|
|
17
|
-
})
|
|
18
|
-
|
|
19
|
-
it('guards undefined before asserting instanceof for an optional Date property', () => {
|
|
20
|
-
const schema = {
|
|
21
|
-
type: 'object' as const,
|
|
22
|
-
properties: { createdAt: { 'x-mjst': { instanceOf: 'Date' } } },
|
|
23
|
-
}
|
|
24
|
-
const lines = generateObjectStrictAssertion(schema, 'Event').join('\n')
|
|
25
|
-
|
|
26
|
-
expect(lines).toContain('input.createdAt !== undefined && !(input.createdAt instanceof Date)')
|
|
27
|
-
})
|
|
28
|
-
|
|
29
|
-
it('asserts instanceof for a top-level Date schema', () => {
|
|
30
|
-
const line = generateScalarStrictAssertion({ 'x-mjst': { instanceOf: 'Date' } }, 'When')
|
|
31
|
-
|
|
32
|
-
expect(line).toContain('!(input instanceof Date)')
|
|
33
|
-
expect(line).toContain('expected Date')
|
|
34
|
-
})
|
|
35
|
-
})
|
|
36
|
-
|
|
37
|
-
describe('generate-strict-assertion x-mjst primitive', () => {
|
|
38
|
-
it('asserts typeof bigint for a required property', () => {
|
|
39
|
-
const schema = {
|
|
40
|
-
type: 'object' as const,
|
|
41
|
-
properties: { balance: { 'x-mjst': { primitive: 'bigint' } } },
|
|
42
|
-
required: ['balance'],
|
|
43
|
-
}
|
|
44
|
-
const lines = generateObjectStrictAssertion(schema, 'Account').join('\n')
|
|
45
|
-
|
|
46
|
-
expect(lines).toContain('missing required property "balance"')
|
|
47
|
-
expect(lines).toContain('typeof input.balance !== "bigint"')
|
|
48
|
-
expect(lines).toContain('field "balance" must be bigint')
|
|
49
|
-
})
|
|
50
|
-
|
|
51
|
-
it('guards undefined before asserting typeof for an optional bigint property', () => {
|
|
52
|
-
const schema = {
|
|
53
|
-
type: 'object' as const,
|
|
54
|
-
properties: { balance: { 'x-mjst': { primitive: 'bigint' } } },
|
|
55
|
-
}
|
|
56
|
-
const lines = generateObjectStrictAssertion(schema, 'Account').join('\n')
|
|
57
|
-
|
|
58
|
-
expect(lines).toContain('input.balance !== undefined && typeof input.balance !== "bigint"')
|
|
59
|
-
})
|
|
60
|
-
|
|
61
|
-
it('asserts typeof for a top-level bigint schema', () => {
|
|
62
|
-
const line = generateScalarStrictAssertion({ 'x-mjst': { primitive: 'bigint' } }, 'Big')
|
|
63
|
-
|
|
64
|
-
expect(line).toContain('typeof input !== "bigint"')
|
|
65
|
-
expect(line).toContain('expected bigint')
|
|
66
|
-
})
|
|
67
|
-
})
|