@amritk/generate-validators 0.16.0 → 0.17.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.
@@ -11,7 +11,8 @@ import { unknownKeyCheck } from "@amritk/helpers/unknown-key-check";
11
11
  import { DEFAULT_UNKNOWN_KEYS } from "@amritk/helpers/unknown-keys-strategy";
12
12
  import { assertGeneratableRefs } from "./assert-generatable-refs.js";
13
13
  import { assertUnevaluatedGeneratable, UNPROVABLE_COVERAGE_MESSAGE } from "./assert-unevaluated-generatable.js";
14
- import { declaresKeywordOutside } from "./enforced-keywords.js";
14
+ import { formatCheckName, formatFamily } from "./emit-format-checks.js";
15
+ import { declaresKeywordOutside, enforcesFormat, NO_FORMATS } from "./enforced-keywords.js";
15
16
  import { tupleShapeOf } from "./tuple-shape.js";
16
17
  import { unevaluatedItemsExpr, unevaluatedPropertiesExpr } from "./unevaluated-match.js";
17
18
  const validatorName = (typeName) => `validate${typeName}`;
@@ -97,14 +98,17 @@ const getTypeArray = (schema) => {
97
98
  return schema.type;
98
99
  };
99
100
  const ROOT_ERROR_SINK = "(errors ??= [])";
101
+ const pushError = (sink, message, path, keyword, params = "{}") => `${sink}.push({ message: ${message}, path: ${path}, keyword: ${JSON.stringify(keyword)}, params: ${params} })`;
102
+ const returnError = (message, path, keyword, params = "{}") => `return { valid: false, errors: [{ message: ${message}, path: ${path}, keyword: ${JSON.stringify(keyword)}, params: ${params} }] }`;
100
103
  const MATCH_ERROR_SINK = "_m";
101
- const createRootContext = (rootSchema) => ({
104
+ const createRootContext = (rootSchema, formats = NO_FORMATS) => ({
102
105
  objVar: "obj",
103
106
  pathPrefix: "${_path}",
104
107
  depth: 0,
105
108
  hoisted: [],
106
109
  rootSchema,
107
- sink: ROOT_ERROR_SINK
110
+ sink: ROOT_ERROR_SINK,
111
+ formats
108
112
  });
109
113
  const pointerSegment = (key) => {
110
114
  const pointer = key.replace(/~/g, "~0").replace(/\//g, "~1");
@@ -157,7 +161,7 @@ const generateStrictKeyChecks = (schema, ctx) => {
157
161
  return [
158
162
  ` for (const _key${d} in ${ctx.objVar}) {`,
159
163
  ` if (${unknownTest}${patternGuard}) {`,
160
- ` ${ctx.sink}.push({ message: 'must NOT have additional properties', path: \`${ctx.pathPrefix}/\${escapePointer(_key${d})}\` })`,
164
+ ` ${pushError(ctx.sink, "'must NOT have additional properties'", `\`${ctx.pathPrefix}/\${escapePointer(_key${d})}\``, "additionalProperties", `{ additionalProperty: _key${d} }`)}`,
161
165
  ` }`,
162
166
  ` }`
163
167
  ];
@@ -172,7 +176,7 @@ const generateMissingRequiredChecks = (schema, ctx) => {
172
176
  if (Object.hasOwn(props, key))
173
177
  continue;
174
178
  lines.push(` if (${missingCheck(ctx.objVar, key)}) {`);
175
- lines.push(` ${ctx.sink}.push({ message: ${JSON.stringify(`must have required property '${key}'`)}, path: ${parentPath} })`);
179
+ lines.push(` ${pushError(ctx.sink, JSON.stringify(`must have required property '${key}'`), parentPath, "required", JSON.stringify({ missingProperty: key }))}`);
176
180
  lines.push(` }`);
177
181
  }
178
182
  return lines;
@@ -184,15 +188,15 @@ const generatePropertyChecks = (key, propSchema, isRequired, suffix, ctx) => {
184
188
  if (isRequired) {
185
189
  return [
186
190
  ` if (${missingCheck(ctx.objVar, key)}) {`,
187
- ` ${ctx.sink}.push({ message: ${JSON.stringify(`must have required property '${key}'`)}, path: ${parentPath} })`,
191
+ ` ${pushError(ctx.sink, JSON.stringify(`must have required property '${key}'`), parentPath, "required", JSON.stringify({ missingProperty: key }))}`,
188
192
  ` } else {`,
189
- ` ${ctx.sink}.push({ message: ${JSON.stringify(FALSE_SCHEMA_MESSAGE)}, path: ${path} })`,
193
+ ` ${pushError(ctx.sink, JSON.stringify(FALSE_SCHEMA_MESSAGE), path, "false schema")}`,
190
194
  ` }`
191
195
  ];
192
196
  }
193
197
  return [
194
198
  ` if (${hasOwnCheck(ctx.objVar, key)}) {`,
195
- ` ${ctx.sink}.push({ message: ${JSON.stringify(FALSE_SCHEMA_MESSAGE)}, path: ${path} })`,
199
+ ` ${pushError(ctx.sink, JSON.stringify(FALSE_SCHEMA_MESSAGE), path, "false schema")}`,
196
200
  ` }`
197
201
  ];
198
202
  }
@@ -236,26 +240,26 @@ const generateKeywordChecks = (key, raw, path, schema, suffix, ctx, presence) =>
236
240
  const instanceOf = getMjstInstanceOf(schema);
237
241
  if (instanceOf) {
238
242
  lines.push(` if (${presence}!(${raw} instanceof ${instanceOf})) {`);
239
- lines.push(` ${ctx.sink}.push({ message: 'must be ${instanceOf}', path: ${path} })`);
243
+ lines.push(` ${pushError(ctx.sink, `'must be ${instanceOf}'`, path, "type", JSON.stringify({ type: instanceOf }))}`);
240
244
  lines.push(` }`);
241
245
  }
242
246
  const primitive = getMjstPrimitive(schema);
243
247
  if (primitive) {
244
248
  lines.push(` if (${presence}typeof ${raw} !== "${primitive}") {`);
245
- lines.push(` ${ctx.sink}.push({ message: 'must be ${primitive}', path: ${path} })`);
249
+ lines.push(` ${pushError(ctx.sink, `'must be ${primitive}'`, path, "type", JSON.stringify({ type: primitive }))}`);
246
250
  lines.push(` }`);
247
251
  }
248
252
  if (hasConst(schema)) {
249
253
  const mismatch = constMismatchCondition(raw, schema.const);
250
254
  const msg = JSON.stringify(`must be ${JSON.stringify(schema.const)}`);
251
255
  lines.push(` if (${presence}${mismatch}) {`);
252
- lines.push(` ${ctx.sink}.push({ message: ${msg}, path: ${path} })`);
256
+ lines.push(` ${pushError(ctx.sink, msg, path, "const", JSON.stringify({ allowedValue: schema.const }))}`);
253
257
  lines.push(` }`);
254
258
  }
255
259
  if (hasEnum(schema)) {
256
260
  const label = schema.enum.map((v) => JSON.stringify(v)).join(", ");
257
261
  lines.push(` if (${presence}!${enumMembershipExpr(schema.enum, raw)}) {`);
258
- lines.push(` ${ctx.sink}.push({ message: ${JSON.stringify(`must be one of: ${label}`)}, path: ${path} })`);
262
+ lines.push(` ${pushError(ctx.sink, JSON.stringify(`must be one of: ${label}`), path, "enum", JSON.stringify({ allowedValues: schema.enum }))}`);
259
263
  lines.push(` }`);
260
264
  }
261
265
  if (instanceOf === void 0 && primitive === void 0) {
@@ -264,7 +268,7 @@ const generateKeywordChecks = (key, raw, path, schema, suffix, ctx, presence) =>
264
268
  const wrongType = wrongTypeCondition(raw, t);
265
269
  if (wrongType) {
266
270
  lines.push(` if (${presence === "" ? wrongType : `${presence}(${wrongType})`}) {`);
267
- lines.push(` ${ctx.sink}.push({ message: 'must be ${typeofString(t)}', path: ${path} })`);
271
+ lines.push(` ${pushError(ctx.sink, `'must be ${typeofString(t)}'`, path, "type", JSON.stringify({ type: t }))}`);
268
272
  lines.push(` }`);
269
273
  }
270
274
  }
@@ -274,7 +278,7 @@ const generateKeywordChecks = (key, raw, path, schema, suffix, ctx, presence) =>
274
278
  if (allWrong) {
275
279
  const label = typeArray.map((t) => typeofString(t)).join(" or ");
276
280
  lines.push(` if (${presence === "" ? allWrong : `${presence}(${allWrong})`}) {`);
277
- lines.push(` ${ctx.sink}.push({ message: ${JSON.stringify(`must be ${label}`)}, path: ${path} })`);
281
+ lines.push(` ${pushError(ctx.sink, JSON.stringify(`must be ${label}`), path, "type", JSON.stringify({ type: schema.type }))}`);
278
282
  lines.push(` }`);
279
283
  }
280
284
  }
@@ -293,7 +297,7 @@ const generatePropertyCheckLines = (key, propSchema, isRequired, suffix, ctx) =>
293
297
  const parentPath = ctx.depth === 0 ? "_path" : `\`${ctx.pathPrefix}\``;
294
298
  const missing = [
295
299
  ` if (${missingCheck(ctx.objVar, key)}) {`,
296
- ` ${ctx.sink}.push({ message: ${JSON.stringify(`must have required property '${key}'`)}, path: ${parentPath} })`
300
+ ` ${pushError(ctx.sink, JSON.stringify(`must have required property '${key}'`), parentPath, "required", JSON.stringify({ missingProperty: key }))}`
297
301
  ];
298
302
  if (!isSchemaObject(propSchema)) {
299
303
  if (isRequired && propSchema === true)
@@ -318,22 +322,31 @@ const generateConstraintChecks = (key, raw, path, propSchema, suffix, ctx) => {
318
322
  return [];
319
323
  const sp = propSchema;
320
324
  const lines = [];
325
+ if (isSchemaObject(propSchema) && enforcesFormat(propSchema, ctx.formats)) {
326
+ const format = propSchema.format;
327
+ const family = formatFamily(format);
328
+ if (family !== void 0) {
329
+ lines.push(` if (typeof ${raw} === '${family}' && !${formatCheckName(format)}(${raw})) {`);
330
+ lines.push(` ${pushError(ctx.sink, JSON.stringify(`must match format "${format}"`), path, "format", JSON.stringify({ format }))}`);
331
+ lines.push(` }`);
332
+ }
333
+ }
321
334
  if (hasPattern(propSchema) || hasMinLength(propSchema) || hasMaxLength(propSchema)) {
322
335
  if (hasPattern(propSchema)) {
323
336
  const re = regexLiteral(propSchema.pattern);
324
337
  const msg = JSON.stringify(`must match pattern ${propSchema.pattern}`);
325
338
  lines.push(` if (typeof ${raw} === 'string' && !${re}.test(${raw})) {`);
326
- lines.push(` ${ctx.sink}.push({ message: ${msg}, path: ${path} })`);
339
+ lines.push(` ${pushError(ctx.sink, msg, path, "pattern", JSON.stringify({ pattern: propSchema.pattern }))}`);
327
340
  lines.push(` }`);
328
341
  }
329
342
  if (hasMinLength(propSchema) && propSchema.minLength > 0) {
330
343
  lines.push(` if (typeof ${raw} === 'string' && ${minLengthFailExpr(raw, propSchema.minLength)}) {`);
331
- lines.push(` ${ctx.sink}.push({ message: 'must have at least ${propSchema.minLength} characters', path: ${path} })`);
344
+ lines.push(` ${pushError(ctx.sink, `'must have at least ${propSchema.minLength} characters'`, path, "minLength", JSON.stringify({ limit: propSchema.minLength }))}`);
332
345
  lines.push(` }`);
333
346
  }
334
347
  if (hasMaxLength(propSchema)) {
335
348
  lines.push(` if (typeof ${raw} === 'string' && ${maxLengthFailExpr(raw, propSchema.maxLength)}) {`);
336
- lines.push(` ${ctx.sink}.push({ message: 'must have at most ${propSchema.maxLength} characters', path: ${path} })`);
349
+ lines.push(` ${pushError(ctx.sink, `'must have at most ${propSchema.maxLength} characters'`, path, "maxLength", JSON.stringify({ limit: propSchema.maxLength }))}`);
337
350
  lines.push(` }`);
338
351
  }
339
352
  }
@@ -341,28 +354,28 @@ const generateConstraintChecks = (key, raw, path, propSchema, suffix, ctx) => {
341
354
  if (hasMinimum(propSchema)) {
342
355
  const strict = hasStrictExclusiveMinimum(propSchema);
343
356
  lines.push(` if (typeof ${raw} === 'number' && ${boundFailExpr(raw, "minimum", propSchema.minimum, strict)}) {`);
344
- lines.push(` ${ctx.sink}.push({ message: 'must be ${boundOperator("minimum", strict)} ${propSchema.minimum}', path: ${path} })`);
357
+ lines.push(` ${pushError(ctx.sink, `'must be ${boundOperator("minimum", strict)} ${propSchema.minimum}'`, path, strict ? "exclusiveMinimum" : "minimum", JSON.stringify({ comparison: boundOperator("minimum", strict), limit: propSchema.minimum }))}`);
345
358
  lines.push(` }`);
346
359
  }
347
360
  if (hasMaximum(propSchema)) {
348
361
  const strict = hasStrictExclusiveMaximum(propSchema);
349
362
  lines.push(` if (typeof ${raw} === 'number' && ${boundFailExpr(raw, "maximum", propSchema.maximum, strict)}) {`);
350
- lines.push(` ${ctx.sink}.push({ message: 'must be ${boundOperator("maximum", strict)} ${propSchema.maximum}', path: ${path} })`);
363
+ lines.push(` ${pushError(ctx.sink, `'must be ${boundOperator("maximum", strict)} ${propSchema.maximum}'`, path, strict ? "exclusiveMaximum" : "maximum", JSON.stringify({ comparison: boundOperator("maximum", strict), limit: propSchema.maximum }))}`);
351
364
  lines.push(` }`);
352
365
  }
353
366
  if (hasExclusiveMinimum(propSchema)) {
354
367
  lines.push(` if (typeof ${raw} === 'number' && ${boundFailExpr(raw, "minimum", propSchema.exclusiveMinimum, true)}) {`);
355
- lines.push(` ${ctx.sink}.push({ message: 'must be > ${propSchema.exclusiveMinimum}', path: ${path} })`);
368
+ lines.push(` ${pushError(ctx.sink, `'must be > ${propSchema.exclusiveMinimum}'`, path, "exclusiveMinimum", JSON.stringify({ comparison: ">", limit: propSchema.exclusiveMinimum }))}`);
356
369
  lines.push(` }`);
357
370
  }
358
371
  if (hasExclusiveMaximum(propSchema)) {
359
372
  lines.push(` if (typeof ${raw} === 'number' && ${boundFailExpr(raw, "maximum", propSchema.exclusiveMaximum, true)}) {`);
360
- lines.push(` ${ctx.sink}.push({ message: 'must be < ${propSchema.exclusiveMaximum}', path: ${path} })`);
373
+ lines.push(` ${pushError(ctx.sink, `'must be < ${propSchema.exclusiveMaximum}'`, path, "exclusiveMaximum", JSON.stringify({ comparison: "<", limit: propSchema.exclusiveMaximum }))}`);
361
374
  lines.push(` }`);
362
375
  }
363
376
  if (hasMultipleOf(propSchema)) {
364
377
  lines.push(` if (typeof ${raw} === 'number' && ${multipleOfFailExpr(raw, propSchema.multipleOf)}) {`);
365
- lines.push(` ${ctx.sink}.push({ message: 'must be a multiple of ${propSchema.multipleOf}', path: ${path} })`);
378
+ lines.push(` ${pushError(ctx.sink, `'must be a multiple of ${propSchema.multipleOf}'`, path, "multipleOf", JSON.stringify({ multipleOf: propSchema.multipleOf }))}`);
366
379
  lines.push(` }`);
367
380
  }
368
381
  }
@@ -397,23 +410,23 @@ const generateConstraintChecks = (key, raw, path, propSchema, suffix, ctx) => {
397
410
  if (hasMinItems(propSchema) || hasMaxItems(propSchema) || hasUniqueItems(propSchema) && propSchema.uniqueItems === true || declaresKey(sp, "contains") || tuple !== void 0 || tail === false && tuple === void 0) {
398
411
  if (tail === false && tuple === void 0) {
399
412
  lines.push(` if (Array.isArray(${raw}) && ${raw}.length > 0) {`);
400
- lines.push(` ${ctx.sink}.push({ message: 'must NOT have more than 0 items', path: ${path} })`);
413
+ lines.push(` ${pushError(ctx.sink, `'must NOT have more than 0 items'`, path, "items", "{ limit: 0 }")}`);
401
414
  lines.push(` }`);
402
415
  }
403
416
  if (hasMinItems(propSchema)) {
404
417
  lines.push(` if (Array.isArray(${raw}) && ${raw}.length < ${propSchema.minItems}) {`);
405
- lines.push(` ${ctx.sink}.push({ message: 'must have at least ${propSchema.minItems} items', path: ${path} })`);
418
+ lines.push(` ${pushError(ctx.sink, `'must have at least ${propSchema.minItems} items'`, path, "minItems", JSON.stringify({ limit: propSchema.minItems }))}`);
406
419
  lines.push(` }`);
407
420
  }
408
421
  if (hasMaxItems(propSchema)) {
409
422
  lines.push(` if (Array.isArray(${raw}) && ${raw}.length > ${propSchema.maxItems}) {`);
410
- lines.push(` ${ctx.sink}.push({ message: 'must have at most ${propSchema.maxItems} items', path: ${path} })`);
423
+ lines.push(` ${pushError(ctx.sink, `'must have at most ${propSchema.maxItems} items'`, path, "maxItems", JSON.stringify({ limit: propSchema.maxItems }))}`);
411
424
  lines.push(` }`);
412
425
  }
413
426
  if (hasUniqueItems(propSchema) && propSchema.uniqueItems === true) {
414
427
  const dupCond = arrayItemsAreScalarOnly(sp) ? `new Set(${raw} as unknown[]).size !== ${raw}.length` : `!allUnique(${raw} as unknown[])`;
415
428
  lines.push(` if (Array.isArray(${raw}) && ${dupCond}) {`);
416
- lines.push(` ${ctx.sink}.push({ message: 'must NOT have duplicate items', path: ${path} })`);
429
+ lines.push(` ${pushError(ctx.sink, `'must NOT have duplicate items'`, path, "uniqueItems")}`);
417
430
  lines.push(` }`);
418
431
  }
419
432
  if (declaresKey(sp, "contains")) {
@@ -423,7 +436,7 @@ const generateConstraintChecks = (key, raw, path, propSchema, suffix, ctx) => {
423
436
  const max = typeof declaredMax === "number" ? declaredMax : void 0;
424
437
  if (min > 0 || max !== void 0) {
425
438
  const matchExpr = generateMatchesExpr("_c", readKey(sp, "contains"), suffix, ctx, true);
426
- const report = `${ctx.sink}.push({ message: 'array does not contain the required matching items', path: ${path} })`;
439
+ const report = pushError(ctx.sink, `'array does not contain the required matching items'`, path, "contains");
427
440
  if (matchExpr === "true") {
428
441
  const bound = max !== void 0 ? `${raw}.length < ${min} || ${raw}.length > ${max}` : `${raw}.length < ${min}`;
429
442
  lines.push(` if (Array.isArray(${raw}) && (${bound})) {`);
@@ -465,7 +478,7 @@ const generateConstraintChecks = (key, raw, path, propSchema, suffix, ctx) => {
465
478
  }
466
479
  if (tailIsClosed) {
467
480
  lines.push(` if (${raw}.length > ${tuple.length}) {`);
468
- lines.push(` ${ctx.sink}.push({ message: 'must NOT have more than ${tuple.length} items', path: ${path} })`);
481
+ lines.push(` ${pushError(ctx.sink, `'must NOT have more than ${tuple.length} items'`, path, "items", JSON.stringify({ limit: tuple.length }))}`);
469
482
  lines.push(` }`);
470
483
  }
471
484
  lines.push(` }`);
@@ -485,7 +498,7 @@ const generateValueChecks = (key, raw, path, propSchema, suffix, ctx, required =
485
498
  const generateValueCheckLines = (_key, raw, path, propSchema, suffix, ctx, required = false) => {
486
499
  const lines = [];
487
500
  if (propSchema === false) {
488
- const report = `${ctx.sink}.push({ message: ${JSON.stringify(FALSE_SCHEMA_MESSAGE)}, path: ${path} })`;
501
+ const report = pushError(ctx.sink, JSON.stringify(FALSE_SCHEMA_MESSAGE), path, "false schema");
489
502
  if (required)
490
503
  return [` {`, ` ${report}`, ` }`];
491
504
  return [` if (${raw} !== undefined) {`, ` ${report}`, ` }`];
@@ -499,7 +512,8 @@ const generateValueCheckLines = (_key, raw, path, propSchema, suffix, ctx, requi
499
512
  depth: ctx.depth + 1,
500
513
  hoisted: ctx.hoisted,
501
514
  rootSchema: ctx.rootSchema,
502
- sink: ctx.sink
515
+ sink: ctx.sink,
516
+ formats: ctx.formats
503
517
  };
504
518
  lines.push(...generateKeywordChecks("", raw, path, propSchema, suffix, valueCtx, presence));
505
519
  return lines;
@@ -548,7 +562,7 @@ const generateUnevaluatedChecks = (raw, path, schema, suffix, ctx) => {
548
562
  for (const statement of properties.setup)
549
563
  lines.push(` ${statement}`);
550
564
  lines.push(` if (!(${properties.expr})) {`);
551
- lines.push(` ${ctx.sink}.push({ message: 'must NOT have unevaluated properties', path: ${path} })`);
565
+ lines.push(` ${pushError(ctx.sink, `'must NOT have unevaluated properties'`, path, "unevaluatedProperties")}`);
552
566
  lines.push(` }`);
553
567
  lines.push(` }`);
554
568
  }
@@ -560,7 +574,7 @@ const generateUnevaluatedChecks = (raw, path, schema, suffix, ctx) => {
560
574
  for (const statement of items.setup)
561
575
  lines.push(` ${statement}`);
562
576
  lines.push(` if (!(${items.expr})) {`);
563
- lines.push(` ${ctx.sink}.push({ message: 'must NOT have unevaluated items', path: ${path} })`);
577
+ lines.push(` ${pushError(ctx.sink, `'must NOT have unevaluated items'`, path, "unevaluatedItems")}`);
564
578
  lines.push(` }`);
565
579
  lines.push(` }`);
566
580
  }
@@ -578,14 +592,14 @@ const generateCombinatorChecks = (key, raw, path, schema, suffix, ctx) => {
578
592
  const conds = schema.anyOf.map((b) => generateMatchesExpr(raw, b, suffix, ctx, true));
579
593
  if (!conds.includes("true")) {
580
594
  lines.push(` if (!(${conds.join(" || ")})) {`);
581
- lines.push(` ${ctx.sink}.push({ message: 'must match a schema in anyOf', path: ${path} })`);
595
+ lines.push(` ${pushError(ctx.sink, `'must match a schema in anyOf'`, path, "anyOf")}`);
582
596
  lines.push(` }`);
583
597
  }
584
598
  }
585
599
  if (hasOneOf(schema) && schema.oneOf.length > 0) {
586
600
  const conds = schema.oneOf.map((b) => `(${generateMatchesExpr(raw, b, suffix, ctx, true)} ? 1 : 0)`);
587
601
  lines.push(` if ((${conds.join(" + ")}) !== 1) {`);
588
- lines.push(` ${ctx.sink}.push({ message: 'must match exactly one schema in oneOf', path: ${path} })`);
602
+ lines.push(` ${pushError(ctx.sink, `'must match exactly one schema in oneOf'`, path, "oneOf")}`);
589
603
  lines.push(` }`);
590
604
  }
591
605
  const not = readKey(schema, "not");
@@ -593,7 +607,7 @@ const generateCombinatorChecks = (key, raw, path, schema, suffix, ctx) => {
593
607
  const cond = generateMatchesExpr(raw, not, suffix, ctx, true);
594
608
  if (cond !== "false") {
595
609
  lines.push(` if (${cond}) {`);
596
- lines.push(` ${ctx.sink}.push({ message: 'must NOT match the schema in not', path: ${path} })`);
610
+ lines.push(` ${pushError(ctx.sink, `'must NOT match the schema in not'`, path, "not")}`);
597
611
  lines.push(` }`);
598
612
  }
599
613
  }
@@ -680,7 +694,8 @@ const generateInlineObjectChecks = (key, propSchema, raw, suffix, ctx) => {
680
694
  depth: ctx.depth + 1,
681
695
  hoisted: ctx.hoisted,
682
696
  rootSchema: ctx.rootSchema,
683
- sink: ctx.sink
697
+ sink: ctx.sink,
698
+ formats: ctx.formats
684
699
  };
685
700
  const required = new Set(hasRequired(propSchema) ? propSchema.required : []);
686
701
  const properties = hasProperties(propSchema) ? propSchema.properties : {};
@@ -715,7 +730,8 @@ const generatePropertyNameChecks = (nameSchema, suffix, ctx) => {
715
730
  depth: ctx.depth + 1,
716
731
  hoisted: ctx.hoisted,
717
732
  rootSchema: ctx.rootSchema,
718
- sink: ctx.sink
733
+ sink: ctx.sink,
734
+ formats: ctx.formats
719
735
  };
720
736
  const checks = generateValueChecks("", "_name", at, nameSchema, suffix, nameCtx, true);
721
737
  if (checks.length === 0)
@@ -734,7 +750,7 @@ const generateDependentRequiredChecks = (schema, ctx) => {
734
750
  for (const dep of deps) {
735
751
  const msg = JSON.stringify(`must have property '${dep}' when '${trigger}' is present`);
736
752
  lines.push(` if (${hasOwnCheck(obj, trigger)} && ${missingCheck(obj, dep)}) {`);
737
- lines.push(` ${ctx.sink}.push({ message: ${msg}, path: ${at} })`);
753
+ lines.push(` ${pushError(ctx.sink, msg, at, "dependentRequired", JSON.stringify({ missingProperty: dep, property: trigger, depsCount: 1 }))}`);
738
754
  lines.push(` }`);
739
755
  }
740
756
  }
@@ -760,7 +776,7 @@ const generateDependentSchemasChecks = (schema, suffix, ctx) => {
760
776
  if (sub === false) {
761
777
  const msg = JSON.stringify(`must NOT have property '${trigger}'`);
762
778
  lines.push(` if (${hasOwnCheck(obj, trigger)}) {`);
763
- lines.push(` ${ctx.sink}.push({ message: ${msg}, path: ${at} })`);
779
+ lines.push(` ${pushError(ctx.sink, msg, at, "dependentSchemas", JSON.stringify({ property: trigger }))}`);
764
780
  lines.push(` }`);
765
781
  continue;
766
782
  }
@@ -794,7 +810,7 @@ const generateDependenciesChecks = (schema, suffix, ctx) => {
794
810
  continue;
795
811
  const msg = JSON.stringify(`must have property '${key}' when '${trigger}' is present`);
796
812
  lines.push(` if (${hasOwnCheck(obj, trigger)} && ${missingCheck(obj, key)}) {`);
797
- lines.push(` ${ctx.sink}.push({ message: ${msg}, path: ${at} })`);
813
+ lines.push(` ${pushError(ctx.sink, msg, at, "dependencies", JSON.stringify({ missingProperty: key, property: trigger, depsCount: 1 }))}`);
798
814
  lines.push(` }`);
799
815
  }
800
816
  continue;
@@ -804,7 +820,7 @@ const generateDependenciesChecks = (schema, suffix, ctx) => {
804
820
  if (value === false) {
805
821
  const msg = JSON.stringify(`must NOT have property '${trigger}'`);
806
822
  lines.push(` if (${hasOwnCheck(obj, trigger)}) {`);
807
- lines.push(` ${ctx.sink}.push({ message: ${msg}, path: ${at} })`);
823
+ lines.push(` ${pushError(ctx.sink, msg, at, "dependencies", JSON.stringify({ property: trigger }))}`);
808
824
  lines.push(` }`);
809
825
  continue;
810
826
  }
@@ -835,13 +851,13 @@ const generateMinMaxPropertiesChecks = (schema, ctx) => {
835
851
  if (hasMin) {
836
852
  const msg = JSON.stringify(`must have at least ${schema.minProperties} properties`);
837
853
  lines.push(` if (${count} < ${schema.minProperties}) {`);
838
- lines.push(` ${ctx.sink}.push({ message: ${msg}, path: ${at} })`);
854
+ lines.push(` ${pushError(ctx.sink, msg, at, "minProperties", JSON.stringify({ limit: schema.minProperties }))}`);
839
855
  lines.push(` }`);
840
856
  }
841
857
  if (hasMax) {
842
858
  const msg = JSON.stringify(`must have at most ${schema.maxProperties} properties`);
843
859
  lines.push(` if (${count} > ${schema.maxProperties}) {`);
844
- lines.push(` ${ctx.sink}.push({ message: ${msg}, path: ${at} })`);
860
+ lines.push(` ${pushError(ctx.sink, msg, at, "maxProperties", JSON.stringify({ limit: schema.maxProperties }))}`);
845
861
  lines.push(` }`);
846
862
  }
847
863
  return lines;
@@ -857,7 +873,13 @@ const carriesUnevaluated = (schema) => {
857
873
  const s = schema;
858
874
  return declaresKey(s, "unevaluatedProperties") && s["unevaluatedProperties"] !== true || declaresKey(s, "unevaluatedItems") && s["unevaluatedItems"] !== true;
859
875
  };
860
- const createGuardContext = (unknownKeys) => ({ unknownKeys, locals: 0, loops: 0 });
876
+ const createGuardContext = (unknownKeys, formats = NO_FORMATS) => ({ unknownKeys, formats, locals: 0, loops: 0 });
877
+ const guardFormatPass = (schema, acc, ctx) => {
878
+ if (!enforcesFormat(schema, ctx.formats))
879
+ return null;
880
+ const format = schema.format;
881
+ return formatFamily(format) === void 0 ? null : `${formatCheckName(format)}(${acc})`;
882
+ };
861
883
  const emptyGuardBlock = () => ({ conditions: [], nested: [], keyChecks: [] });
862
884
  const isFlatGuardBlock = (block) => block.nested.length === 0 && block.keyChecks.length === 0;
863
885
  const keySetCheckLines = (check, bail, indent, unknownKeys) => {
@@ -996,11 +1018,11 @@ const guardObjectConditions = (schema, raw, objAcc, ctx) => {
996
1018
  }
997
1019
  return block;
998
1020
  };
999
- const generateObjectValidator = (schema, typeName, suffix, rootSchema, unknownKeys) => {
1021
+ const generateObjectValidator = (schema, typeName, suffix, rootSchema, unknownKeys, formats) => {
1000
1022
  const vName = validatorName(typeName);
1001
1023
  const required = new Set(hasRequired(schema) ? schema.required : []);
1002
1024
  const properties = hasProperties(schema) ? schema.properties : {};
1003
- const ctx = createRootContext(rootSchema);
1025
+ const ctx = createRootContext(rootSchema, formats);
1004
1026
  const propertyLines = [];
1005
1027
  for (const [key, propSchema] of Object.entries(properties)) {
1006
1028
  const checks = generatePropertyChecks(key, propSchema, required.has(key), suffix, ctx);
@@ -1025,13 +1047,13 @@ const generateObjectValidator = (schema, typeName, suffix, rootSchema, unknownKe
1025
1047
  propertyLines.push(...objectCombinators);
1026
1048
  }
1027
1049
  const body = propertyLines.length > 0 ? "\n" + propertyLines.join("\n") + "\n" : "";
1028
- const guard = guardObjectConditions(schema, "input", "obj", createGuardContext(unknownKeys));
1050
+ const guard = guardObjectConditions(schema, "input", "obj", createGuardContext(unknownKeys, formats));
1029
1051
  const objBinding = readsObjBinding(body) ? [` const obj = input as Record<string, unknown>`] : [];
1030
1052
  const collectBody = (name, exported) => [
1031
1053
  `${exported ? "export " : ""}const ${name} = (input: unknown, _path = ''): ValidationResult => {`,
1032
1054
  ...objBinding,
1033
1055
  ` if (typeof input !== 'object' || input === null || Array.isArray(input)) {`,
1034
- ` return { valid: false, errors: [{ message: 'must be object', path: _path }] }`,
1056
+ ` ${returnError(`'must be object'`, "_path", "type", JSON.stringify({ type: "object" }))}`,
1035
1057
  ` }`,
1036
1058
  ``,
1037
1059
  ` let errors: ValidationError[] | undefined`,
@@ -1095,7 +1117,7 @@ const booleanLeafExpr = (schema, acc, ctx) => {
1095
1117
  if (!hasType(schema)) {
1096
1118
  if (!hasEnum(schema))
1097
1119
  return null;
1098
- if (declaresKeywordOutside(schema, ["enum"]))
1120
+ if (declaresKeywordOutside(schema, ["enum"], ctx.formats))
1099
1121
  return null;
1100
1122
  return enumMembershipExpr(schema.enum, acc);
1101
1123
  }
@@ -1116,6 +1138,9 @@ const booleanLeafExpr = (schema, acc, ctx) => {
1116
1138
  // spellings of `(obj.a as Record<string, unknown>).b`.
1117
1139
  case "string": {
1118
1140
  const parts = [`typeof ${acc} === 'string'`];
1141
+ const formatPass = guardFormatPass(schema, acc, ctx);
1142
+ if (formatPass !== null)
1143
+ parts.push(formatPass);
1119
1144
  if (hasPattern(schema))
1120
1145
  parts.push(`${regexLiteral(schema.pattern)}.test(${acc})`);
1121
1146
  if (hasMinLength(schema))
@@ -1129,6 +1154,9 @@ const booleanLeafExpr = (schema, acc, ctx) => {
1129
1154
  const parts = [`typeof ${acc} === 'number'`];
1130
1155
  if (t === "integer")
1131
1156
  parts.push(`Number.isInteger(${acc})`);
1157
+ const formatPass = guardFormatPass(schema, acc, ctx);
1158
+ if (formatPass !== null)
1159
+ parts.push(formatPass);
1132
1160
  if (hasMinimum(schema))
1133
1161
  parts.push(boundPassExpr(acc, "minimum", schema.minimum, hasStrictExclusiveMinimum(schema)));
1134
1162
  if (hasMaximum(schema))
@@ -1266,12 +1294,12 @@ const typeDescribesEveryAcceptedValue = (schema) => {
1266
1294
  }
1267
1295
  return !IMPLICIT_OBJECT_KEYWORDS.some((keyword) => declaresKey(s, keyword));
1268
1296
  };
1269
- const generateBooleanGuard = (schema, typeName, _suffix = "", unknownKeys = DEFAULT_UNKNOWN_KEYS) => {
1297
+ const generateBooleanGuard = (schema, typeName, _suffix = "", unknownKeys = DEFAULT_UNKNOWN_KEYS, formats = NO_FORMATS) => {
1270
1298
  const name = guardName(typeName);
1271
1299
  const returns = typeDescribesEveryAcceptedValue(rewriteNullable(schema)) ? `input is ${typeName}` : "boolean";
1272
1300
  const fallback = `export const ${name} = (input: unknown): ${returns} => ${validatorName(typeName)}(input) === true`;
1273
1301
  const rewritten = rewriteNullable(schema);
1274
- const ctx = createGuardContext(unknownKeys);
1302
+ const ctx = createGuardContext(unknownKeys, formats);
1275
1303
  if (declaresObjectType(rewritten)) {
1276
1304
  if (!objectRootIsSelfContained(rewritten))
1277
1305
  return fallback;
@@ -1293,19 +1321,19 @@ const generateBooleanGuard = (schema, typeName, _suffix = "", unknownKeys = DEFA
1293
1321
  return fallback;
1294
1322
  return `export const ${name} = (input: unknown): ${returns} => ${expr}`;
1295
1323
  };
1296
- const generateScalarValidator = (schema, typeName, suffix, rootSchema) => {
1324
+ const generateScalarValidator = (schema, typeName, suffix, rootSchema, formats) => {
1297
1325
  const vName = validatorName(typeName);
1298
1326
  if (!isSchemaObject(schema)) {
1299
1327
  if (schema === false) {
1300
1328
  return [
1301
1329
  `export const ${vName} = (_input: unknown, _path = ''): ValidationResult => {`,
1302
- ` return { valid: false, errors: [{ message: ${JSON.stringify(FALSE_SCHEMA_MESSAGE)}, path: _path }] }`,
1330
+ ` ${returnError(JSON.stringify(FALSE_SCHEMA_MESSAGE), "_path", "false schema")}`,
1303
1331
  `}`
1304
1332
  ].join("\n");
1305
1333
  }
1306
1334
  return [`export const ${vName} = (_input: unknown, _path = ''): ValidationResult => {`, ` return true`, `}`].join("\n");
1307
1335
  }
1308
- const generalRoot = () => generateGeneralRootValidator(schema, typeName, suffix, rootSchema);
1336
+ const generalRoot = () => generateGeneralRootValidator(schema, typeName, suffix, rootSchema, formats);
1309
1337
  if (hasRef(schema)) {
1310
1338
  if (declaresKeywordOutside(schema, ["$ref"]))
1311
1339
  return generalRoot();
@@ -1323,7 +1351,7 @@ const generateScalarValidator = (schema, typeName, suffix, rootSchema) => {
1323
1351
  return [
1324
1352
  `export const ${vName} = (input: unknown, _path = ''): ValidationResult => {`,
1325
1353
  ` if (!(input instanceof ${instanceOf})) {`,
1326
- ` return { valid: false, errors: [{ message: 'must be ${instanceOf}', path: _path }] }`,
1354
+ ` ${returnError(`'must be ${instanceOf}'`, "_path", "type", JSON.stringify({ type: instanceOf }))}`,
1327
1355
  ` }`,
1328
1356
  ` return true`,
1329
1357
  `}`
@@ -1336,7 +1364,7 @@ const generateScalarValidator = (schema, typeName, suffix, rootSchema) => {
1336
1364
  return [
1337
1365
  `export const ${vName} = (input: unknown, _path = ''): ValidationResult => {`,
1338
1366
  ` if (typeof input !== "${primitive}") {`,
1339
- ` return { valid: false, errors: [{ message: 'must be ${primitive}', path: _path }] }`,
1367
+ ` ${returnError(`'must be ${primitive}'`, "_path", "type", JSON.stringify({ type: primitive }))}`,
1340
1368
  ` }`,
1341
1369
  ` return true`,
1342
1370
  `}`
@@ -1350,7 +1378,7 @@ const generateScalarValidator = (schema, typeName, suffix, rootSchema) => {
1350
1378
  return [
1351
1379
  `export const ${vName} = (input: unknown, _path = ''): ValidationResult => {`,
1352
1380
  ` if (${mismatch}) {`,
1353
- ` return { valid: false, errors: [{ message: ${msg}, path: _path }] }`,
1381
+ ` ${returnError(msg, "_path", "const", JSON.stringify({ allowedValue: schema.const }))}`,
1354
1382
  ` }`,
1355
1383
  ` return true`,
1356
1384
  `}`
@@ -1363,14 +1391,14 @@ const generateScalarValidator = (schema, typeName, suffix, rootSchema) => {
1363
1391
  return [
1364
1392
  `export const ${vName} = (input: unknown, _path = ''): ValidationResult => {`,
1365
1393
  ` if (!${enumMembershipExpr(schema.enum, "input")}) {`,
1366
- ` return { valid: false, errors: [{ message: ${JSON.stringify(`must be one of: ${label}`)}, path: _path }] }`,
1394
+ ` ${returnError(JSON.stringify(`must be one of: ${label}`), "_path", "enum", JSON.stringify({ allowedValues: schema.enum }))}`,
1367
1395
  ` }`,
1368
1396
  ` return true`,
1369
1397
  `}`
1370
1398
  ].join("\n");
1371
1399
  }
1372
1400
  if (hasAllOf(schema) || hasAnyOf(schema) || hasOneOf(schema) || declaresKey(schema, "not") || declaresKey(schema, "if")) {
1373
- const ctx = createRootContext(rootSchema);
1401
+ const ctx = createRootContext(rootSchema, formats);
1374
1402
  const checks = [];
1375
1403
  const rootPath = "`${_path}`";
1376
1404
  const rootTypeArray2 = getTypeArray(schema);
@@ -1379,7 +1407,7 @@ const generateScalarValidator = (schema, typeName, suffix, rootSchema) => {
1379
1407
  if (allWrong) {
1380
1408
  const label = rootTypeArray2.map((t) => typeofString(t)).join(" or ");
1381
1409
  checks.push(` if (${allWrong}) {`);
1382
- checks.push(` ${ctx.sink}.push({ message: ${JSON.stringify(`must be ${label}`)}, path: ${rootPath} })`);
1410
+ checks.push(` ${pushError(ctx.sink, JSON.stringify(`must be ${label}`), rootPath, "type", JSON.stringify({ type: schema.type }))}`);
1383
1411
  checks.push(` }`);
1384
1412
  }
1385
1413
  } else if (hasType(schema)) {
@@ -1387,7 +1415,7 @@ const generateScalarValidator = (schema, typeName, suffix, rootSchema) => {
1387
1415
  const wrongType = wrongTypeCondition("input", t);
1388
1416
  if (wrongType) {
1389
1417
  checks.push(` if (${wrongType}) {`);
1390
- checks.push(` ${ctx.sink}.push({ message: 'must be ${typeofString(t)}', path: ${rootPath} })`);
1418
+ checks.push(` ${pushError(ctx.sink, `'must be ${typeofString(t)}'`, rootPath, "type", JSON.stringify({ type: t }))}`);
1391
1419
  checks.push(` }`);
1392
1420
  }
1393
1421
  }
@@ -1404,14 +1432,14 @@ const generateScalarValidator = (schema, typeName, suffix, rootSchema) => {
1404
1432
  }
1405
1433
  const rootTypeArray = getTypeArray(schema);
1406
1434
  if (rootTypeArray) {
1407
- const ctx = createRootContext(rootSchema);
1435
+ const ctx = createRootContext(rootSchema, formats);
1408
1436
  const rootPath = "`${_path}`";
1409
1437
  const checks = [];
1410
1438
  const allWrong = rootTypeArray.map((t) => wrongTypeCondition("input", t)).filter((c) => c !== "").map((c) => `(${c})`).join(" && ");
1411
1439
  if (allWrong) {
1412
1440
  const label = rootTypeArray.map((t) => typeofString(t)).join(" or ");
1413
1441
  checks.push(` if (${allWrong}) {`);
1414
- checks.push(` ${ctx.sink}.push({ message: ${JSON.stringify(`must be ${label}`)}, path: ${rootPath} })`);
1442
+ checks.push(` ${pushError(ctx.sink, JSON.stringify(`must be ${label}`), rootPath, "type", JSON.stringify({ type: schema.type }))}`);
1415
1443
  checks.push(` }`);
1416
1444
  }
1417
1445
  checks.push(...generateConstraintChecks("", "input", rootPath, schema, suffix, ctx));
@@ -1435,7 +1463,7 @@ const generateScalarValidator = (schema, typeName, suffix, rootSchema) => {
1435
1463
  const t = schema.type;
1436
1464
  const wrongType = wrongTypeCondition("input", t);
1437
1465
  const typLabel = typeofString(t);
1438
- const rootCtx = createRootContext(rootSchema);
1466
+ const rootCtx = createRootContext(rootSchema, formats);
1439
1467
  const constraintLines = generateConstraintChecks("", "_root", "`${_path}`", schema, suffix, rootCtx);
1440
1468
  if (!wrongType) {
1441
1469
  return [
@@ -1448,7 +1476,7 @@ const generateScalarValidator = (schema, typeName, suffix, rootSchema) => {
1448
1476
  return [
1449
1477
  `export const ${vName} = (input: unknown, _path = ''): ValidationResult => {`,
1450
1478
  ` if (${wrongType}) {`,
1451
- ` return { valid: false, errors: [{ message: 'must be ${typLabel}', path: _path }] }`,
1479
+ ` ${returnError(`'must be ${typLabel}'`, "_path", "type", JSON.stringify({ type: schema.type }))}`,
1452
1480
  ` }`,
1453
1481
  ` return true`,
1454
1482
  `}`
@@ -1457,7 +1485,7 @@ const generateScalarValidator = (schema, typeName, suffix, rootSchema) => {
1457
1485
  return withHoisted(rootCtx.hoisted, [
1458
1486
  `export const ${vName} = (input: unknown, _path = ''): ValidationResult => {`,
1459
1487
  ` if (${wrongType}) {`,
1460
- ` return { valid: false, errors: [{ message: 'must be ${typLabel}', path: _path }] }`,
1488
+ ` ${returnError(`'must be ${typLabel}'`, "_path", "type", JSON.stringify({ type: schema.type }))}`,
1461
1489
  ` }`,
1462
1490
  ...readsBinding("_root", constraintLines.join("\n")) ? [` const _root: unknown = input`] : [],
1463
1491
  ` let errors: ValidationError[] | undefined`,
@@ -1466,7 +1494,7 @@ const generateScalarValidator = (schema, typeName, suffix, rootSchema) => {
1466
1494
  `}`
1467
1495
  ].join("\n"));
1468
1496
  }
1469
- const typelessCtx = createRootContext(rootSchema);
1497
+ const typelessCtx = createRootContext(rootSchema, formats);
1470
1498
  const typelessChecks = generateConstraintChecks("", "input", "`${_path}`", schema, suffix, typelessCtx);
1471
1499
  if (typelessChecks.length === 0) {
1472
1500
  return [`export const ${vName} = (_input: unknown, _path = ''): ValidationResult => {`, ` return true`, `}`].join("\n");
@@ -1479,8 +1507,8 @@ const generateScalarValidator = (schema, typeName, suffix, rootSchema) => {
1479
1507
  `}`
1480
1508
  ].join("\n"));
1481
1509
  };
1482
- const generateGeneralRootValidator = (schema, typeName, suffix, rootSchema) => {
1483
- const ctx = createRootContext(rootSchema);
1510
+ const generateGeneralRootValidator = (schema, typeName, suffix, rootSchema, formats) => {
1511
+ const ctx = createRootContext(rootSchema, formats);
1484
1512
  const checks = generateValueChecks("", "input", "`${_path}`", schema, suffix, ctx, true);
1485
1513
  const body = checks.join("\n");
1486
1514
  return withHoisted(ctx.hoisted, [
@@ -1546,18 +1574,18 @@ const rewriteNullable = (node) => {
1546
1574
  return { anyOf: [{ type: "null" }, out] };
1547
1575
  return out;
1548
1576
  };
1549
- const generateValidatorFunction = (schema, typeName, suffix = "", rootSchema, unknownKeys = DEFAULT_UNKNOWN_KEYS) => {
1577
+ const generateValidatorFunction = (schema, typeName, suffix = "", rootSchema, unknownKeys = DEFAULT_UNKNOWN_KEYS, formats = NO_FORMATS) => {
1550
1578
  assertGeneratableRefs(schema, typeName);
1551
1579
  const rewritten = rewriteNullable(schema);
1552
1580
  const document = rootSchema ?? schema;
1553
- assertUnevaluatedGeneratable(rewritten, typeName, document, unevaluatedMatcher(suffix, createRootContext(document)));
1581
+ assertUnevaluatedGeneratable(rewritten, typeName, document, unevaluatedMatcher(suffix, createRootContext(document, formats)));
1554
1582
  if (carriesUnevaluated(rewritten)) {
1555
- return generateGeneralRootValidator(rewritten, typeName, suffix, document);
1583
+ return generateGeneralRootValidator(rewritten, typeName, suffix, document, formats);
1556
1584
  }
1557
1585
  if (declaresObjectType(rewritten) && objectRootIsSelfContained(rewritten)) {
1558
- return generateObjectValidator(rewritten, typeName, suffix, document, unknownKeys);
1586
+ return generateObjectValidator(rewritten, typeName, suffix, document, unknownKeys, formats);
1559
1587
  }
1560
- return generateScalarValidator(rewritten, typeName, suffix, document);
1588
+ return generateScalarValidator(rewritten, typeName, suffix, document, formats);
1561
1589
  };
1562
1590
  export {
1563
1591
  generateBooleanGuard,