@amritk/generate-validators 0.6.0 → 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 +37 -0
- package/dist/generators/build-schema.d.ts +0 -1
- package/dist/generators/build-schema.js +29 -0
- package/dist/generators/collect-validator-imports.d.ts +0 -1
- package/dist/generators/collect-validator-imports.js +5 -1
- package/dist/generators/generate-files.d.ts +0 -1
- package/dist/generators/generate-files.js +6 -0
- package/dist/generators/generate-validator-function.d.ts +0 -1
- package/dist/generators/generate-validator-function.js +309 -40
- package/dist/index.d.ts +0 -1
- package/package.json +10 -7
- package/dist/generators/build-schema.d.ts.map +0 -1
- package/dist/generators/collect-validator-imports.d.ts.map +0 -1
- package/dist/generators/generate-files.d.ts.map +0 -1
- package/dist/generators/generate-validator-function.d.ts.map +0 -1
- package/dist/index.d.ts.map +0 -1
- package/src/generators/build-schema.test.ts +0 -163
- package/src/generators/build-schema.ts +0 -81
- package/src/generators/collect-validator-imports.ts +0 -134
- package/src/generators/generate-files.ts +0 -79
- package/src/generators/generate-validator-function.test.ts +0 -311
- package/src/generators/generate-validator-function.ts +0 -550
- package/src/index.ts +0 -2
|
@@ -1,311 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from 'vitest'
|
|
2
|
-
|
|
3
|
-
import { generateValidatorFunction } from './generate-validator-function'
|
|
4
|
-
|
|
5
|
-
// Eval helper: compiles a generated function string in context of a minimal
|
|
6
|
-
// ValidationResult runtime so we can actually run the generated code.
|
|
7
|
-
const _evalValidator = (code: string): ((input: unknown, path?: string) => unknown) => {
|
|
8
|
-
const _wrapped = `
|
|
9
|
-
const ValidationResult = null // type-only
|
|
10
|
-
${code}
|
|
11
|
-
`
|
|
12
|
-
// eslint-disable-next-line no-new-func
|
|
13
|
-
return new Function(`
|
|
14
|
-
${code}
|
|
15
|
-
return validate${code.match(/export const validate(\w+)/)?.[1] ?? ''}
|
|
16
|
-
`)() as (input: unknown, path?: string) => unknown
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
describe('generate-validator-function', () => {
|
|
20
|
-
it('generates a validator for a required string property', () => {
|
|
21
|
-
const schema = {
|
|
22
|
-
type: 'object' as const,
|
|
23
|
-
properties: { name: { type: 'string' as const } },
|
|
24
|
-
required: ['name'],
|
|
25
|
-
}
|
|
26
|
-
const code = generateValidatorFunction(schema, 'Info')
|
|
27
|
-
|
|
28
|
-
expect(code).toContain('export const validateInfo')
|
|
29
|
-
expect(code).toContain('"name" in obj')
|
|
30
|
-
expect(code).toContain("must have required property 'name'")
|
|
31
|
-
expect(code).toContain('typeof obj["name"] !== \'string\'')
|
|
32
|
-
expect(code).toContain('must be string')
|
|
33
|
-
})
|
|
34
|
-
|
|
35
|
-
it('generates a validator for an optional number property', () => {
|
|
36
|
-
const schema = {
|
|
37
|
-
type: 'object' as const,
|
|
38
|
-
properties: { count: { type: 'number' as const } },
|
|
39
|
-
}
|
|
40
|
-
const code = generateValidatorFunction(schema, 'Stats')
|
|
41
|
-
|
|
42
|
-
expect(code).toContain('obj["count"] !== undefined')
|
|
43
|
-
expect(code).toContain('typeof obj["count"] !== \'number\'')
|
|
44
|
-
expect(code).toContain('must be number')
|
|
45
|
-
})
|
|
46
|
-
|
|
47
|
-
it('generates a validator for a boolean property', () => {
|
|
48
|
-
const schema = {
|
|
49
|
-
type: 'object' as const,
|
|
50
|
-
properties: { enabled: { type: 'boolean' as const } },
|
|
51
|
-
required: ['enabled'],
|
|
52
|
-
}
|
|
53
|
-
const code = generateValidatorFunction(schema, 'Config')
|
|
54
|
-
|
|
55
|
-
expect(code).toContain('typeof obj["enabled"] !== \'boolean\'')
|
|
56
|
-
expect(code).toContain('must be boolean')
|
|
57
|
-
})
|
|
58
|
-
|
|
59
|
-
it('generates required-property check at parent path', () => {
|
|
60
|
-
const schema = {
|
|
61
|
-
type: 'object' as const,
|
|
62
|
-
properties: { title: { type: 'string' as const } },
|
|
63
|
-
required: ['title'],
|
|
64
|
-
}
|
|
65
|
-
const code = generateValidatorFunction(schema, 'Doc')
|
|
66
|
-
|
|
67
|
-
// Required errors use _path (parent), not a child path
|
|
68
|
-
expect(code).toContain('path: _path')
|
|
69
|
-
expect(code).toContain("must have required property 'title'")
|
|
70
|
-
})
|
|
71
|
-
|
|
72
|
-
it('generates type error at child path', () => {
|
|
73
|
-
const schema = {
|
|
74
|
-
type: 'object' as const,
|
|
75
|
-
properties: { title: { type: 'string' as const } },
|
|
76
|
-
required: ['title'],
|
|
77
|
-
}
|
|
78
|
-
const code = generateValidatorFunction(schema, 'Doc')
|
|
79
|
-
|
|
80
|
-
// Type errors use the child path
|
|
81
|
-
expect(code).toContain('`${_path}/title`')
|
|
82
|
-
})
|
|
83
|
-
|
|
84
|
-
it('generates an enum validator', () => {
|
|
85
|
-
const schema = {
|
|
86
|
-
enum: ['get', 'post', 'put', 'delete'],
|
|
87
|
-
}
|
|
88
|
-
const code = generateValidatorFunction(schema as Parameters<typeof generateValidatorFunction>[0], 'Method')
|
|
89
|
-
|
|
90
|
-
expect(code).toContain('must be one of')
|
|
91
|
-
expect(code).toContain('"get"')
|
|
92
|
-
expect(code).toContain('"post"')
|
|
93
|
-
})
|
|
94
|
-
|
|
95
|
-
it('generates a string pattern check', () => {
|
|
96
|
-
const schema = {
|
|
97
|
-
type: 'object' as const,
|
|
98
|
-
properties: { version: { type: 'string' as const, pattern: '^\\d+\\.\\d+' } },
|
|
99
|
-
required: ['version'],
|
|
100
|
-
}
|
|
101
|
-
const code = generateValidatorFunction(schema, 'Info')
|
|
102
|
-
|
|
103
|
-
expect(code).toContain('must match pattern')
|
|
104
|
-
expect(code).toContain('.test(')
|
|
105
|
-
})
|
|
106
|
-
|
|
107
|
-
it('escapes forward slashes in a pattern so the emitted regex literal compiles', () => {
|
|
108
|
-
const schema = {
|
|
109
|
-
type: 'object' as const,
|
|
110
|
-
properties: { date: { type: 'string' as const, pattern: '^\\d{4}/\\d{2}/\\d{2}$' } },
|
|
111
|
-
required: ['date'],
|
|
112
|
-
}
|
|
113
|
-
const code = generateValidatorFunction(schema, 'Event')
|
|
114
|
-
|
|
115
|
-
// The bare slashes are escaped (\/) and the digit classes keep their single
|
|
116
|
-
// backslash, so the literal is valid and means what the pattern says.
|
|
117
|
-
expect(code).toContain('!/^\\d{4}\\/\\d{2}\\/\\d{2}$/.test(')
|
|
118
|
-
// Sanity check: the emitted regex source actually parses and matches.
|
|
119
|
-
const emitted = /!\/(.+)\/\.test\(/.exec(code)?.[1]
|
|
120
|
-
expect(emitted).toBeDefined()
|
|
121
|
-
expect(new RegExp(emitted as string).test('2024/01/02')).toBe(true)
|
|
122
|
-
})
|
|
123
|
-
|
|
124
|
-
it('checks a const property for an exact value', () => {
|
|
125
|
-
const schema = {
|
|
126
|
-
type: 'object' as const,
|
|
127
|
-
properties: { kind: { const: 'user' }, version: { const: 2 } },
|
|
128
|
-
required: ['kind'],
|
|
129
|
-
}
|
|
130
|
-
const code = generateValidatorFunction(schema, 'Record')
|
|
131
|
-
|
|
132
|
-
expect(code).toContain('obj["kind"] !== "user"')
|
|
133
|
-
expect(code).toContain('obj["version"] !== 2')
|
|
134
|
-
expect(code).toContain('must be \\"user\\"')
|
|
135
|
-
})
|
|
136
|
-
|
|
137
|
-
it('checks a const object property by canonical JSON', () => {
|
|
138
|
-
const schema = {
|
|
139
|
-
type: 'object' as const,
|
|
140
|
-
properties: { meta: { const: { a: 1 } } },
|
|
141
|
-
}
|
|
142
|
-
const code = generateValidatorFunction(schema, 'Record')
|
|
143
|
-
|
|
144
|
-
expect(code).toContain('JSON.stringify(obj["meta"]) !== ')
|
|
145
|
-
})
|
|
146
|
-
|
|
147
|
-
it('generates a top-level const validator', () => {
|
|
148
|
-
const code = generateValidatorFunction({ const: 'fixed' }, 'Tag')
|
|
149
|
-
|
|
150
|
-
expect(code).toContain('input !== "fixed"')
|
|
151
|
-
expect(code).toContain('must be \\"fixed\\"')
|
|
152
|
-
})
|
|
153
|
-
|
|
154
|
-
it('generates dependentRequired presence checks', () => {
|
|
155
|
-
const schema = {
|
|
156
|
-
type: 'object' as const,
|
|
157
|
-
properties: { creditCard: { type: 'number' as const }, billingAddress: { type: 'string' as const } },
|
|
158
|
-
dependentRequired: { creditCard: ['billingAddress'] },
|
|
159
|
-
}
|
|
160
|
-
const code = generateValidatorFunction(schema, 'Payment')
|
|
161
|
-
|
|
162
|
-
expect(code).toContain('"creditCard" in obj && !("billingAddress" in obj)')
|
|
163
|
-
expect(code).toContain("must have property 'billingAddress' when 'creditCard' is present")
|
|
164
|
-
})
|
|
165
|
-
|
|
166
|
-
it('generates a propertyNames pattern check over every key', () => {
|
|
167
|
-
const schema = {
|
|
168
|
-
type: 'object' as const,
|
|
169
|
-
propertyNames: { pattern: '^[a-z]+$' },
|
|
170
|
-
}
|
|
171
|
-
const code = generateValidatorFunction(schema, 'Dict')
|
|
172
|
-
|
|
173
|
-
expect(code).toContain('for (const _name of Object.keys(obj))')
|
|
174
|
-
expect(code).toContain('!/^[a-z]+$/.test(_name)')
|
|
175
|
-
expect(code).toContain('property name must match pattern')
|
|
176
|
-
})
|
|
177
|
-
|
|
178
|
-
it('generates min/maxLength checks', () => {
|
|
179
|
-
const schema = {
|
|
180
|
-
type: 'object' as const,
|
|
181
|
-
properties: { name: { type: 'string' as const, minLength: 1, maxLength: 100 } },
|
|
182
|
-
required: ['name'],
|
|
183
|
-
}
|
|
184
|
-
const code = generateValidatorFunction(schema, 'Info')
|
|
185
|
-
|
|
186
|
-
expect(code).toContain('must have at least 1 characters')
|
|
187
|
-
expect(code).toContain('must have at most 100 characters')
|
|
188
|
-
})
|
|
189
|
-
|
|
190
|
-
it('generates minimum/maximum checks', () => {
|
|
191
|
-
const schema = {
|
|
192
|
-
type: 'object' as const,
|
|
193
|
-
properties: { port: { type: 'number' as const, minimum: 0, maximum: 65535 } },
|
|
194
|
-
}
|
|
195
|
-
const code = generateValidatorFunction(schema, 'Server')
|
|
196
|
-
|
|
197
|
-
expect(code).toContain('>= 0')
|
|
198
|
-
expect(code).toContain('<= 65535')
|
|
199
|
-
})
|
|
200
|
-
|
|
201
|
-
it('generates a $ref property delegation', () => {
|
|
202
|
-
const schema = {
|
|
203
|
-
type: 'object' as const,
|
|
204
|
-
properties: { info: { $ref: '#/$defs/info' } },
|
|
205
|
-
required: ['info'],
|
|
206
|
-
}
|
|
207
|
-
const code = generateValidatorFunction(schema, 'Document')
|
|
208
|
-
|
|
209
|
-
expect(code).toContain('validateInfo(')
|
|
210
|
-
expect(code).toContain('"info" in obj')
|
|
211
|
-
})
|
|
212
|
-
|
|
213
|
-
it('generates a scalar string validator', () => {
|
|
214
|
-
const schema = { type: 'string' as const }
|
|
215
|
-
const code = generateValidatorFunction(schema, 'StringValue')
|
|
216
|
-
|
|
217
|
-
expect(code).toContain("typeof input !== 'string'")
|
|
218
|
-
expect(code).toContain('must be string')
|
|
219
|
-
})
|
|
220
|
-
|
|
221
|
-
it('accumulates all constraint errors for a scalar string schema', () => {
|
|
222
|
-
const schema = { type: 'string' as const, pattern: '^\\d+$', minLength: 2, maxLength: 4 }
|
|
223
|
-
const code = generateValidatorFunction(schema, 'Code')
|
|
224
|
-
|
|
225
|
-
// All three constraints push onto a shared errors array instead of returning early
|
|
226
|
-
expect(code).toContain('const errors: ValidationError[] = []')
|
|
227
|
-
expect(code).toContain('errors.push({ message: "must match pattern')
|
|
228
|
-
// The pattern body keeps its backslash (\d), so the emitted literal is a digit class.
|
|
229
|
-
expect(code).toContain('!/^\\d+$/.test(input)')
|
|
230
|
-
expect(code).toContain("errors.push({ message: 'must have at least 2 characters'")
|
|
231
|
-
expect(code).toContain("errors.push({ message: 'must have at most 4 characters'")
|
|
232
|
-
expect(code).toContain('return errors.length > 0 ? { valid: false, errors } : true')
|
|
233
|
-
})
|
|
234
|
-
|
|
235
|
-
it('returns true for empty object schemas', () => {
|
|
236
|
-
const schema = { type: 'object' as const }
|
|
237
|
-
const code = generateValidatorFunction(schema, 'Empty')
|
|
238
|
-
|
|
239
|
-
expect(code).toContain('validateEmpty')
|
|
240
|
-
expect(code).toContain('must be object')
|
|
241
|
-
expect(code).toContain('return errors.length > 0')
|
|
242
|
-
})
|
|
243
|
-
|
|
244
|
-
it('generates object guard at top of object validator', () => {
|
|
245
|
-
const schema = { type: 'object' as const, properties: { x: { type: 'string' as const } } }
|
|
246
|
-
const code = generateValidatorFunction(schema, 'Foo')
|
|
247
|
-
|
|
248
|
-
expect(code).toContain("typeof input !== 'object'")
|
|
249
|
-
expect(code).toContain('Array.isArray(input)')
|
|
250
|
-
expect(code).toContain('must be object')
|
|
251
|
-
})
|
|
252
|
-
|
|
253
|
-
it('generates an instanceof check for a required x-mjst Date property', () => {
|
|
254
|
-
const schema = {
|
|
255
|
-
type: 'object' as const,
|
|
256
|
-
properties: { createdAt: { 'x-mjst': { instanceOf: 'Date' } } },
|
|
257
|
-
required: ['createdAt'],
|
|
258
|
-
}
|
|
259
|
-
const code = generateValidatorFunction(schema, 'Event')
|
|
260
|
-
|
|
261
|
-
expect(code).toContain('"createdAt" in obj')
|
|
262
|
-
expect(code).toContain('!(obj["createdAt"] instanceof Date)')
|
|
263
|
-
expect(code).toContain('must be Date')
|
|
264
|
-
})
|
|
265
|
-
|
|
266
|
-
it('generates an instanceof check for an optional x-mjst Date property', () => {
|
|
267
|
-
const schema = {
|
|
268
|
-
type: 'object' as const,
|
|
269
|
-
properties: { createdAt: { 'x-mjst': { instanceOf: 'Date' } } },
|
|
270
|
-
}
|
|
271
|
-
const code = generateValidatorFunction(schema, 'Event')
|
|
272
|
-
|
|
273
|
-
expect(code).toContain('obj["createdAt"] !== undefined && !(obj["createdAt"] instanceof Date)')
|
|
274
|
-
})
|
|
275
|
-
|
|
276
|
-
it('generates an instanceof check for a top-level x-mjst Date schema', () => {
|
|
277
|
-
const code = generateValidatorFunction({ 'x-mjst': { instanceOf: 'Date' } }, 'When')
|
|
278
|
-
|
|
279
|
-
expect(code).toContain('!(input instanceof Date)')
|
|
280
|
-
expect(code).toContain('must be Date')
|
|
281
|
-
})
|
|
282
|
-
|
|
283
|
-
it('generates a typeof check for a required x-mjst bigint property', () => {
|
|
284
|
-
const schema = {
|
|
285
|
-
type: 'object' as const,
|
|
286
|
-
properties: { balance: { 'x-mjst': { primitive: 'bigint' } } },
|
|
287
|
-
required: ['balance'],
|
|
288
|
-
}
|
|
289
|
-
const code = generateValidatorFunction(schema, 'Account')
|
|
290
|
-
|
|
291
|
-
expect(code).toContain('typeof obj["balance"] !== "bigint"')
|
|
292
|
-
expect(code).toContain('must be bigint')
|
|
293
|
-
})
|
|
294
|
-
|
|
295
|
-
it('guards undefined for an optional x-mjst bigint property', () => {
|
|
296
|
-
const schema = {
|
|
297
|
-
type: 'object' as const,
|
|
298
|
-
properties: { balance: { 'x-mjst': { primitive: 'bigint' } } },
|
|
299
|
-
}
|
|
300
|
-
const code = generateValidatorFunction(schema, 'Account')
|
|
301
|
-
|
|
302
|
-
expect(code).toContain('obj["balance"] !== undefined && typeof obj["balance"] !== "bigint"')
|
|
303
|
-
})
|
|
304
|
-
|
|
305
|
-
it('generates a typeof check for a top-level x-mjst bigint schema', () => {
|
|
306
|
-
const code = generateValidatorFunction({ 'x-mjst': { primitive: 'bigint' } }, 'Big')
|
|
307
|
-
|
|
308
|
-
expect(code).toContain('typeof input !== "bigint"')
|
|
309
|
-
expect(code).toContain('must be bigint')
|
|
310
|
-
})
|
|
311
|
-
})
|