@amritk/generate-validators 0.5.1 → 0.6.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.
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"generate-validator-function.d.ts","sourceRoot":"","sources":["../../src/generators/generate-validator-function.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"generate-validator-function.d.ts","sourceRoot":"","sources":["../../src/generators/generate-validator-function.ts"],"names":[],"mappings":"AA0BA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,iCAAiC,CAAA;AA+ejE;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,eAAO,MAAM,yBAAyB,WAAY,UAAU,YAAY,MAAM,sBAAgB,MAM7F,CAAA"}
|
|
@@ -1,6 +1,7 @@
|
|
|
1
|
+
import { escapeRegexPattern } from '@amritk/helpers/escape-regex-pattern';
|
|
1
2
|
import { getMjstInstanceOf, getMjstPrimitive } from '@amritk/helpers/mjst-extension';
|
|
2
3
|
import { refToName } from '@amritk/helpers/ref-to-name';
|
|
3
|
-
import { hasAdditionalProperties, hasEnum, hasExclusiveMaximum, hasExclusiveMinimum, hasItems, hasMaximum, hasMaxLength, hasMinimum, hasMinLength, hasMultipleOf, hasOneOf, hasPattern, hasProperties, hasRef, hasRequired, hasType, isObjectSchema, isSchemaObject, } from '@amritk/helpers/schema-guards';
|
|
4
|
+
import { hasAdditionalProperties, hasConst, hasDependentRequired, hasEnum, hasExclusiveMaximum, hasExclusiveMinimum, hasItems, hasMaximum, hasMaxLength, hasMinimum, hasMinLength, hasMultipleOf, hasOneOf, hasPattern, hasProperties, hasPropertyNames, hasRef, hasRequired, hasType, isObjectSchema, isSchemaObject, } from '@amritk/helpers/schema-guards';
|
|
4
5
|
/**
|
|
5
6
|
* Derives the validator function name from a type name.
|
|
6
7
|
* e.g. "InfoObject" → "validateInfoObject"
|
|
@@ -14,6 +15,18 @@ const typeofString = (type) => {
|
|
|
14
15
|
return 'number';
|
|
15
16
|
return type;
|
|
16
17
|
};
|
|
18
|
+
/**
|
|
19
|
+
* Generates the inline condition that is TRUE when `accessor` does NOT equal the
|
|
20
|
+
* `const` value. Primitives compare with `!==`; objects/arrays compare by their
|
|
21
|
+
* canonical JSON serialization (sufficient for the literal, fixed shapes `const`
|
|
22
|
+
* is used for).
|
|
23
|
+
*/
|
|
24
|
+
const constMismatchCondition = (accessor, value) => {
|
|
25
|
+
if (value === null || typeof value !== 'object') {
|
|
26
|
+
return `${accessor} !== ${JSON.stringify(value)}`;
|
|
27
|
+
}
|
|
28
|
+
return `JSON.stringify(${accessor}) !== ${JSON.stringify(JSON.stringify(value))}`;
|
|
29
|
+
};
|
|
17
30
|
/**
|
|
18
31
|
* Generates the inline condition that is TRUE when a value is the wrong type.
|
|
19
32
|
*/
|
|
@@ -98,6 +111,24 @@ const generatePropertyChecks = (key, propSchema, isRequired, suffix) => {
|
|
|
98
111
|
}
|
|
99
112
|
return lines;
|
|
100
113
|
}
|
|
114
|
+
// const — value must equal the fixed value exactly
|
|
115
|
+
if (hasConst(propSchema)) {
|
|
116
|
+
const mismatch = constMismatchCondition(raw, propSchema.const);
|
|
117
|
+
const msg = JSON.stringify(`must be ${JSON.stringify(propSchema.const)}`);
|
|
118
|
+
if (isRequired) {
|
|
119
|
+
lines.push(` if (!(${JSON.stringify(key)} in obj)) {`);
|
|
120
|
+
lines.push(` errors.push({ message: "must have required property '${key}'", path: _path })`);
|
|
121
|
+
lines.push(` } else if (${mismatch}) {`);
|
|
122
|
+
lines.push(` errors.push({ message: ${msg}, path: ${path} })`);
|
|
123
|
+
lines.push(` }`);
|
|
124
|
+
}
|
|
125
|
+
else {
|
|
126
|
+
lines.push(` if (${raw} !== undefined && ${mismatch}) {`);
|
|
127
|
+
lines.push(` errors.push({ message: ${msg}, path: ${path} })`);
|
|
128
|
+
lines.push(` }`);
|
|
129
|
+
}
|
|
130
|
+
return lines;
|
|
131
|
+
}
|
|
101
132
|
// enum
|
|
102
133
|
if (hasEnum(propSchema)) {
|
|
103
134
|
const allowed = JSON.stringify(propSchema.enum);
|
|
@@ -138,8 +169,10 @@ const generatePropertyChecks = (key, propSchema, isRequired, suffix) => {
|
|
|
138
169
|
// String constraints
|
|
139
170
|
if (t === 'string') {
|
|
140
171
|
if (hasPattern(propSchema)) {
|
|
141
|
-
|
|
142
|
-
|
|
172
|
+
const re = escapeRegexPattern(propSchema.pattern);
|
|
173
|
+
const msg = JSON.stringify(`must match pattern ${propSchema.pattern}`);
|
|
174
|
+
lines.push(` if (typeof ${raw} === 'string' && !/${re}/.test(${raw})) {`);
|
|
175
|
+
lines.push(` errors.push({ message: ${msg}, path: ${path} })`);
|
|
143
176
|
lines.push(` }`);
|
|
144
177
|
}
|
|
145
178
|
if (hasMinLength(propSchema)) {
|
|
@@ -236,6 +269,30 @@ const generateObjectValidator = (schema, typeName, suffix) => {
|
|
|
236
269
|
propertyLines.push(` if (_r !== true) errors.push(..._r.errors)`);
|
|
237
270
|
propertyLines.push(` }`);
|
|
238
271
|
}
|
|
272
|
+
// dependentRequired — when a trigger property is present, its dependencies must be too.
|
|
273
|
+
if (hasDependentRequired(schema)) {
|
|
274
|
+
for (const [trigger, deps] of Object.entries(schema.dependentRequired)) {
|
|
275
|
+
if (!Array.isArray(deps))
|
|
276
|
+
continue;
|
|
277
|
+
for (const dep of deps) {
|
|
278
|
+
const msg = JSON.stringify(`must have property '${dep}' when '${trigger}' is present`);
|
|
279
|
+
propertyLines.push(` if (${JSON.stringify(trigger)} in obj && !(${JSON.stringify(dep)} in obj)) {`);
|
|
280
|
+
propertyLines.push(` errors.push({ message: ${msg}, path: _path })`);
|
|
281
|
+
propertyLines.push(` }`);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
// propertyNames with a pattern — every key must match. (Only the pattern form
|
|
286
|
+
// is emitted; a $ref/complex propertyNames schema is left to runtime validation.)
|
|
287
|
+
if (hasPropertyNames(schema) && isSchemaObject(schema.propertyNames) && hasPattern(schema.propertyNames)) {
|
|
288
|
+
const re = escapeRegexPattern(schema.propertyNames.pattern);
|
|
289
|
+
const msg = JSON.stringify(`property name must match pattern ${schema.propertyNames.pattern}`);
|
|
290
|
+
propertyLines.push(` for (const _name of Object.keys(obj)) {`);
|
|
291
|
+
propertyLines.push(` if (!/${re}/.test(_name)) {`);
|
|
292
|
+
propertyLines.push(` errors.push({ message: ${msg}, path: \`\${_path}/\${_name}\` })`);
|
|
293
|
+
propertyLines.push(` }`);
|
|
294
|
+
propertyLines.push(` }`);
|
|
295
|
+
}
|
|
239
296
|
const body = propertyLines.length > 0 ? '\n' + propertyLines.join('\n') + '\n' : '';
|
|
240
297
|
return [
|
|
241
298
|
`export const ${vName} = (input: unknown, _path = ''): ValidationResult => {`,
|
|
@@ -291,6 +348,19 @@ const generateScalarValidator = (schema, typeName, suffix) => {
|
|
|
291
348
|
`}`,
|
|
292
349
|
].join('\n');
|
|
293
350
|
}
|
|
351
|
+
// Top-level const
|
|
352
|
+
if (hasConst(schema)) {
|
|
353
|
+
const mismatch = constMismatchCondition('input', schema.const);
|
|
354
|
+
const msg = JSON.stringify(`must be ${JSON.stringify(schema.const)}`);
|
|
355
|
+
return [
|
|
356
|
+
`export const ${vName} = (input: unknown, _path = ''): ValidationResult => {`,
|
|
357
|
+
` if (${mismatch}) {`,
|
|
358
|
+
` return { valid: false, errors: [{ message: ${msg}, path: _path }] }`,
|
|
359
|
+
` }`,
|
|
360
|
+
` return true`,
|
|
361
|
+
`}`,
|
|
362
|
+
].join('\n');
|
|
363
|
+
}
|
|
294
364
|
// Top-level enum
|
|
295
365
|
if (hasEnum(schema)) {
|
|
296
366
|
const allowed = JSON.stringify(schema.enum);
|
|
@@ -330,8 +400,10 @@ const generateScalarValidator = (schema, typeName, suffix) => {
|
|
|
330
400
|
const constraintLines = [];
|
|
331
401
|
if (t === 'string') {
|
|
332
402
|
if (hasPattern(schema)) {
|
|
333
|
-
|
|
334
|
-
|
|
403
|
+
const re = escapeRegexPattern(schema.pattern);
|
|
404
|
+
const msg = JSON.stringify(`must match pattern ${schema.pattern}`);
|
|
405
|
+
constraintLines.push(` if (typeof input === 'string' && !/${re}/.test(input)) {`);
|
|
406
|
+
constraintLines.push(` errors.push({ message: ${msg}, path: _path })`);
|
|
335
407
|
constraintLines.push(` }`);
|
|
336
408
|
}
|
|
337
409
|
if (hasMinLength(schema)) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@amritk/generate-validators",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Generate TypeScript validation functions from JSON Schemas.",
|
|
5
5
|
"module": "./dist/index.js",
|
|
6
6
|
"type": "module",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
},
|
|
48
48
|
"dependencies": {
|
|
49
49
|
"json-schema-typed": "^8.0.1",
|
|
50
|
-
"@amritk/helpers": "0.
|
|
50
|
+
"@amritk/helpers": "0.8.0"
|
|
51
51
|
},
|
|
52
52
|
"devDependencies": {
|
|
53
53
|
"@scalar/openapi-parser": "^0.26.1"
|
|
@@ -104,6 +104,77 @@ describe('generate-validator-function', () => {
|
|
|
104
104
|
expect(code).toContain('.test(')
|
|
105
105
|
})
|
|
106
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
|
+
|
|
107
178
|
it('generates min/maxLength checks', () => {
|
|
108
179
|
const schema = {
|
|
109
180
|
type: 'object' as const,
|
|
@@ -153,7 +224,9 @@ describe('generate-validator-function', () => {
|
|
|
153
224
|
|
|
154
225
|
// All three constraints push onto a shared errors array instead of returning early
|
|
155
226
|
expect(code).toContain('const errors: ValidationError[] = []')
|
|
156
|
-
expect(code).toContain(
|
|
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)')
|
|
157
230
|
expect(code).toContain("errors.push({ message: 'must have at least 2 characters'")
|
|
158
231
|
expect(code).toContain("errors.push({ message: 'must have at most 4 characters'")
|
|
159
232
|
expect(code).toContain('return errors.length > 0 ? { valid: false, errors } : true')
|
|
@@ -1,7 +1,10 @@
|
|
|
1
|
+
import { escapeRegexPattern } from '@amritk/helpers/escape-regex-pattern'
|
|
1
2
|
import { getMjstInstanceOf, getMjstPrimitive } from '@amritk/helpers/mjst-extension'
|
|
2
3
|
import { refToName } from '@amritk/helpers/ref-to-name'
|
|
3
4
|
import {
|
|
4
5
|
hasAdditionalProperties,
|
|
6
|
+
hasConst,
|
|
7
|
+
hasDependentRequired,
|
|
5
8
|
hasEnum,
|
|
6
9
|
hasExclusiveMaximum,
|
|
7
10
|
hasExclusiveMinimum,
|
|
@@ -14,6 +17,7 @@ import {
|
|
|
14
17
|
hasOneOf,
|
|
15
18
|
hasPattern,
|
|
16
19
|
hasProperties,
|
|
20
|
+
hasPropertyNames,
|
|
17
21
|
hasRef,
|
|
18
22
|
hasRequired,
|
|
19
23
|
hasType,
|
|
@@ -36,6 +40,19 @@ const typeofString = (type: string): string => {
|
|
|
36
40
|
return type
|
|
37
41
|
}
|
|
38
42
|
|
|
43
|
+
/**
|
|
44
|
+
* Generates the inline condition that is TRUE when `accessor` does NOT equal the
|
|
45
|
+
* `const` value. Primitives compare with `!==`; objects/arrays compare by their
|
|
46
|
+
* canonical JSON serialization (sufficient for the literal, fixed shapes `const`
|
|
47
|
+
* is used for).
|
|
48
|
+
*/
|
|
49
|
+
const constMismatchCondition = (accessor: string, value: unknown): string => {
|
|
50
|
+
if (value === null || typeof value !== 'object') {
|
|
51
|
+
return `${accessor} !== ${JSON.stringify(value)}`
|
|
52
|
+
}
|
|
53
|
+
return `JSON.stringify(${accessor}) !== ${JSON.stringify(JSON.stringify(value))}`
|
|
54
|
+
}
|
|
55
|
+
|
|
39
56
|
/**
|
|
40
57
|
* Generates the inline condition that is TRUE when a value is the wrong type.
|
|
41
58
|
*/
|
|
@@ -123,6 +140,24 @@ const generatePropertyChecks = (key: string, propSchema: JSONSchema, isRequired:
|
|
|
123
140
|
return lines
|
|
124
141
|
}
|
|
125
142
|
|
|
143
|
+
// const — value must equal the fixed value exactly
|
|
144
|
+
if (hasConst(propSchema)) {
|
|
145
|
+
const mismatch = constMismatchCondition(raw, propSchema.const)
|
|
146
|
+
const msg = JSON.stringify(`must be ${JSON.stringify(propSchema.const)}`)
|
|
147
|
+
if (isRequired) {
|
|
148
|
+
lines.push(` if (!(${JSON.stringify(key)} in obj)) {`)
|
|
149
|
+
lines.push(` errors.push({ message: "must have required property '${key}'", path: _path })`)
|
|
150
|
+
lines.push(` } else if (${mismatch}) {`)
|
|
151
|
+
lines.push(` errors.push({ message: ${msg}, path: ${path} })`)
|
|
152
|
+
lines.push(` }`)
|
|
153
|
+
} else {
|
|
154
|
+
lines.push(` if (${raw} !== undefined && ${mismatch}) {`)
|
|
155
|
+
lines.push(` errors.push({ message: ${msg}, path: ${path} })`)
|
|
156
|
+
lines.push(` }`)
|
|
157
|
+
}
|
|
158
|
+
return lines
|
|
159
|
+
}
|
|
160
|
+
|
|
126
161
|
// enum
|
|
127
162
|
if (hasEnum(propSchema)) {
|
|
128
163
|
const allowed = JSON.stringify(propSchema.enum)
|
|
@@ -165,8 +200,10 @@ const generatePropertyChecks = (key: string, propSchema: JSONSchema, isRequired:
|
|
|
165
200
|
// String constraints
|
|
166
201
|
if (t === 'string') {
|
|
167
202
|
if (hasPattern(propSchema)) {
|
|
168
|
-
|
|
169
|
-
|
|
203
|
+
const re = escapeRegexPattern(propSchema.pattern)
|
|
204
|
+
const msg = JSON.stringify(`must match pattern ${propSchema.pattern}`)
|
|
205
|
+
lines.push(` if (typeof ${raw} === 'string' && !/${re}/.test(${raw})) {`)
|
|
206
|
+
lines.push(` errors.push({ message: ${msg}, path: ${path} })`)
|
|
170
207
|
lines.push(` }`)
|
|
171
208
|
}
|
|
172
209
|
if (hasMinLength(propSchema)) {
|
|
@@ -278,6 +315,31 @@ const generateObjectValidator = (schema: JSONSchema, typeName: string, suffix: s
|
|
|
278
315
|
propertyLines.push(` }`)
|
|
279
316
|
}
|
|
280
317
|
|
|
318
|
+
// dependentRequired — when a trigger property is present, its dependencies must be too.
|
|
319
|
+
if (hasDependentRequired(schema)) {
|
|
320
|
+
for (const [trigger, deps] of Object.entries(schema.dependentRequired)) {
|
|
321
|
+
if (!Array.isArray(deps)) continue
|
|
322
|
+
for (const dep of deps) {
|
|
323
|
+
const msg = JSON.stringify(`must have property '${dep}' when '${trigger}' is present`)
|
|
324
|
+
propertyLines.push(` if (${JSON.stringify(trigger)} in obj && !(${JSON.stringify(dep)} in obj)) {`)
|
|
325
|
+
propertyLines.push(` errors.push({ message: ${msg}, path: _path })`)
|
|
326
|
+
propertyLines.push(` }`)
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// propertyNames with a pattern — every key must match. (Only the pattern form
|
|
332
|
+
// is emitted; a $ref/complex propertyNames schema is left to runtime validation.)
|
|
333
|
+
if (hasPropertyNames(schema) && isSchemaObject(schema.propertyNames) && hasPattern(schema.propertyNames)) {
|
|
334
|
+
const re = escapeRegexPattern(schema.propertyNames.pattern)
|
|
335
|
+
const msg = JSON.stringify(`property name must match pattern ${schema.propertyNames.pattern}`)
|
|
336
|
+
propertyLines.push(` for (const _name of Object.keys(obj)) {`)
|
|
337
|
+
propertyLines.push(` if (!/${re}/.test(_name)) {`)
|
|
338
|
+
propertyLines.push(` errors.push({ message: ${msg}, path: \`\${_path}/\${_name}\` })`)
|
|
339
|
+
propertyLines.push(` }`)
|
|
340
|
+
propertyLines.push(` }`)
|
|
341
|
+
}
|
|
342
|
+
|
|
281
343
|
const body = propertyLines.length > 0 ? '\n' + propertyLines.join('\n') + '\n' : ''
|
|
282
344
|
|
|
283
345
|
return [
|
|
@@ -342,6 +404,20 @@ const generateScalarValidator = (schema: JSONSchema, typeName: string, suffix: s
|
|
|
342
404
|
].join('\n')
|
|
343
405
|
}
|
|
344
406
|
|
|
407
|
+
// Top-level const
|
|
408
|
+
if (hasConst(schema)) {
|
|
409
|
+
const mismatch = constMismatchCondition('input', schema.const)
|
|
410
|
+
const msg = JSON.stringify(`must be ${JSON.stringify(schema.const)}`)
|
|
411
|
+
return [
|
|
412
|
+
`export const ${vName} = (input: unknown, _path = ''): ValidationResult => {`,
|
|
413
|
+
` if (${mismatch}) {`,
|
|
414
|
+
` return { valid: false, errors: [{ message: ${msg}, path: _path }] }`,
|
|
415
|
+
` }`,
|
|
416
|
+
` return true`,
|
|
417
|
+
`}`,
|
|
418
|
+
].join('\n')
|
|
419
|
+
}
|
|
420
|
+
|
|
345
421
|
// Top-level enum
|
|
346
422
|
if (hasEnum(schema)) {
|
|
347
423
|
const allowed = JSON.stringify(schema.enum)
|
|
@@ -385,8 +461,10 @@ const generateScalarValidator = (schema: JSONSchema, typeName: string, suffix: s
|
|
|
385
461
|
|
|
386
462
|
if (t === 'string') {
|
|
387
463
|
if (hasPattern(schema)) {
|
|
388
|
-
|
|
389
|
-
|
|
464
|
+
const re = escapeRegexPattern(schema.pattern)
|
|
465
|
+
const msg = JSON.stringify(`must match pattern ${schema.pattern}`)
|
|
466
|
+
constraintLines.push(` if (typeof input === 'string' && !/${re}/.test(input)) {`)
|
|
467
|
+
constraintLines.push(` errors.push({ message: ${msg}, path: _path })`)
|
|
390
468
|
constraintLines.push(` }`)
|
|
391
469
|
}
|
|
392
470
|
if (hasMinLength(schema)) {
|