z-schema 9.0.1 → 10.0.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.
Files changed (59) hide show
  1. package/README.md +123 -191
  2. package/cjs/index.d.ts +33 -9
  3. package/cjs/index.js +4799 -3984
  4. package/dist/errors.js +5 -0
  5. package/dist/format-validators.js +65 -0
  6. package/dist/json-schema-versions.js +5 -0
  7. package/dist/json-schema.js +11 -4
  8. package/dist/json-validation.js +151 -10
  9. package/dist/report.js +2 -3
  10. package/dist/schema-cache.js +23 -2
  11. package/dist/schema-compiler.js +25 -10
  12. package/dist/schema-validator.js +66 -45
  13. package/dist/schemas/draft-06-hyper-schema.json +132 -0
  14. package/dist/schemas/draft-06-links.json +43 -0
  15. package/dist/schemas/draft-06-schema.json +155 -0
  16. package/dist/types/errors.d.ts +4 -0
  17. package/dist/types/index.d.ts +2 -1
  18. package/dist/types/json-schema-versions.d.ts +23 -0
  19. package/dist/types/json-schema.d.ts +5 -9
  20. package/dist/types/json-validation.d.ts +3 -3
  21. package/dist/types/report.d.ts +3 -3
  22. package/dist/types/schema-cache.d.ts +1 -1
  23. package/dist/types/schema-compiler.d.ts +1 -1
  24. package/dist/types/schema-validator.d.ts +1 -1
  25. package/dist/types/utils/what-is.d.ts +1 -0
  26. package/dist/types/z-schema-base.d.ts +1 -1
  27. package/dist/types/z-schema-options.d.ts +1 -1
  28. package/dist/types/z-schema-reader.d.ts +1 -1
  29. package/dist/types/z-schema-versions.d.ts +1 -0
  30. package/dist/types/z-schema.d.ts +10 -1
  31. package/dist/utils/schema-regex.js +4 -3
  32. package/dist/utils/what-is.js +4 -1
  33. package/dist/z-schema-base.js +4 -5
  34. package/dist/z-schema-options.js +3 -1
  35. package/dist/z-schema-versions.js +27 -0
  36. package/dist/z-schema.js +21 -7
  37. package/package.json +2 -2
  38. package/src/errors.ts +6 -0
  39. package/src/format-validators.ts +65 -0
  40. package/src/index.ts +2 -1
  41. package/src/json-schema-versions.ts +34 -0
  42. package/src/json-schema.ts +22 -16
  43. package/src/json-validation.ts +183 -13
  44. package/src/report.ts +5 -6
  45. package/src/schema-cache.ts +25 -3
  46. package/src/schema-compiler.ts +25 -11
  47. package/src/schema-validator.ts +128 -62
  48. package/src/schemas/draft-06-hyper-schema.json +133 -0
  49. package/src/schemas/draft-06-links.json +43 -0
  50. package/src/schemas/draft-06-schema.json +155 -0
  51. package/src/utils/schema-regex.ts +5 -3
  52. package/src/utils/what-is.ts +5 -1
  53. package/src/z-schema-base.ts +5 -6
  54. package/src/z-schema-options.ts +3 -2
  55. package/src/z-schema-reader.ts +1 -1
  56. package/src/z-schema-versions.ts +38 -0
  57. package/src/z-schema.ts +27 -11
  58. package/umd/ZSchema.js +5100 -4285
  59. package/umd/ZSchema.min.js +1 -1
@@ -1,4 +1,4 @@
1
- import type { JsonSchema, JsonSchemaInternal } from './json-schema.js';
1
+ import type { JsonSchema, JsonSchemaAll, JsonSchemaInternal } from './json-schema-versions.js';
2
2
  import type { ValidateOptions, ZSchemaBase } from './z-schema-base.js';
3
3
 
4
4
  import { getFormatValidators } from './format-validators.js';
@@ -24,7 +24,7 @@ const shouldSkipValidate = function (options: ValidateOptions, errors: any) {
24
24
 
25
25
  type JsonValidatorFn = (this: ZSchemaBase, report: Report, schema: JsonSchema, json: unknown) => void;
26
26
 
27
- export const JsonValidators: Record<keyof JsonSchema, JsonValidatorFn> = {
27
+ export const JsonValidators: Record<keyof JsonSchemaAll, JsonValidatorFn> = {
28
28
  id: () => {},
29
29
  $ref: () => {},
30
30
  $schema: () => {},
@@ -63,8 +63,20 @@ export const JsonValidators: Record<keyof JsonSchema, JsonValidatorFn> = {
63
63
  }
64
64
  }
65
65
  },
66
- exclusiveMaximum: function () {
67
- // covered in maximum
66
+ exclusiveMaximum: function (this: ZSchemaBase, report: Report, schema: JsonSchemaInternal, json: unknown) {
67
+ // In draft-06+, exclusiveMaximum is a standalone number
68
+ if (typeof schema.exclusiveMaximum === 'number') {
69
+ if (shouldSkipValidate(this.validateOptions, ['MAXIMUM_EXCLUSIVE'])) {
70
+ return;
71
+ }
72
+ if (typeof json !== 'number') {
73
+ return;
74
+ }
75
+ if (json >= schema.exclusiveMaximum) {
76
+ report.addError('MAXIMUM_EXCLUSIVE', [json, schema.exclusiveMaximum], undefined, schema, 'exclusiveMaximum');
77
+ }
78
+ }
79
+ // In draft-04, exclusiveMaximum is a boolean handled inside the `maximum` validator
68
80
  },
69
81
  minimum: function (this: ZSchemaBase, report: Report, schema: JsonSchemaInternal, json: unknown) {
70
82
  // http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.1.3.2
@@ -84,8 +96,20 @@ export const JsonValidators: Record<keyof JsonSchema, JsonValidatorFn> = {
84
96
  }
85
97
  }
86
98
  },
87
- exclusiveMinimum: function () {
88
- // covered in minimum
99
+ exclusiveMinimum: function (this: ZSchemaBase, report: Report, schema: JsonSchemaInternal, json: unknown) {
100
+ // In draft-06+, exclusiveMinimum is a standalone number
101
+ if (typeof schema.exclusiveMinimum === 'number') {
102
+ if (shouldSkipValidate(this.validateOptions, ['MINIMUM_EXCLUSIVE'])) {
103
+ return;
104
+ }
105
+ if (typeof json !== 'number') {
106
+ return;
107
+ }
108
+ if (json <= schema.exclusiveMinimum) {
109
+ report.addError('MINIMUM_EXCLUSIVE', [json, schema.exclusiveMinimum], undefined, schema, 'exclusiveMinimum');
110
+ }
111
+ }
112
+ // In draft-04, exclusiveMinimum is a boolean handled inside the `minimum` validator
89
113
  },
90
114
  maxLength: function (this: ZSchemaBase, report: Report, schema: JsonSchemaInternal, json: unknown) {
91
115
  // http://json-schema.org/latest/json-schema-validation.html#rfc.section.5.2.1.2
@@ -276,7 +300,11 @@ export const JsonValidators: Record<keyof JsonSchema, JsonValidatorFn> = {
276
300
  // for each regex in "pp", remove all elements of "s" which this regex matches.
277
301
  let idx = pp.length;
278
302
  while (idx--) {
279
- const regExp = RegExp(pp[idx]);
303
+ const result = compileSchemaRegex(pp[idx]);
304
+ if (!result.ok) {
305
+ continue;
306
+ }
307
+ const regExp = result.value;
280
308
  let idx2 = s.length;
281
309
  while (idx2--) {
282
310
  if (regExp.test(s[idx2]) === true) {
@@ -596,6 +624,134 @@ export const JsonValidators: Record<keyof JsonSchema, JsonValidatorFn> = {
596
624
  report.addError('UNKNOWN_FORMAT', [schema.format!], undefined, schema, 'format');
597
625
  }
598
626
  },
627
+ // draft-06 additions
628
+ $id: () => {
629
+ // TODO: implement
630
+ },
631
+ const: function (this: ZSchemaBase, report: Report, schema: JsonSchemaInternal, json: unknown) {
632
+ const constValue = (schema as JsonSchemaAll).const;
633
+ if (areEqual(json, constValue) === false) {
634
+ report.addError('CONST', [JSON.stringify(constValue)], undefined, schema, undefined);
635
+ }
636
+ },
637
+ contains: function (this: ZSchemaBase, report: Report, schema: JsonSchemaInternal, json: unknown) {
638
+ if (shouldSkipValidate(this.validateOptions, ['CONTAINS'])) {
639
+ return;
640
+ }
641
+
642
+ if (!Array.isArray(json)) {
643
+ return;
644
+ }
645
+
646
+ const containsSchema = (schema as JsonSchemaAll).contains;
647
+ if (containsSchema === undefined) {
648
+ return;
649
+ }
650
+
651
+ const subReports: Report[] = [];
652
+ let idx = json.length;
653
+ while (idx--) {
654
+ const subReport = new Report(report);
655
+ subReports.push(subReport);
656
+ validate.call(this, subReport, containsSchema as any, json[idx]);
657
+ }
658
+
659
+ const asyncTasksBefore = report.asyncTasks.length;
660
+ for (const subReport of subReports) {
661
+ report.asyncTasks.push(...subReport.asyncTasks);
662
+ }
663
+ const hasAsyncTasks = report.asyncTasks.length > asyncTasksBefore;
664
+
665
+ const addContainsErrorIfNeeded = () => {
666
+ let hasValidItem = false;
667
+ for (const subReport of subReports) {
668
+ if (subReport.errors.length === 0) {
669
+ hasValidItem = true;
670
+ break;
671
+ }
672
+ }
673
+ if (!hasValidItem) {
674
+ report.addError('CONTAINS', undefined, subReports, schema, undefined);
675
+ }
676
+ };
677
+
678
+ if (hasAsyncTasks) {
679
+ const pathBeforeAsync = shallowClone(report.path);
680
+ report.addAsyncTask(
681
+ (callback) => {
682
+ setTimeout(() => callback(null), 0);
683
+ },
684
+ [] as any,
685
+ () => {
686
+ const backup = report.path;
687
+ report.path = pathBeforeAsync;
688
+ addContainsErrorIfNeeded();
689
+ report.path = backup;
690
+ }
691
+ );
692
+ return;
693
+ }
694
+
695
+ addContainsErrorIfNeeded();
696
+ },
697
+ examples: () => {
698
+ // TODO: implement
699
+ },
700
+ propertyNames: function (this: ZSchemaBase, report: Report, schema: JsonSchemaInternal, json: unknown) {
701
+ if (shouldSkipValidate(this.validateOptions, ['PROPERTY_NAMES'])) {
702
+ return;
703
+ }
704
+
705
+ if (!isObject(json)) {
706
+ return;
707
+ }
708
+
709
+ const propertyNamesSchema = (schema as JsonSchemaAll).propertyNames;
710
+ if (propertyNamesSchema === undefined) {
711
+ return;
712
+ }
713
+
714
+ const keys = Object.keys(json);
715
+ const subReports: Report[] = [];
716
+ for (const key of keys) {
717
+ const subReport = new Report(report);
718
+ subReports.push(subReport);
719
+ validate.call(this, subReport, propertyNamesSchema as any, key);
720
+ }
721
+
722
+ const asyncTasksBefore = report.asyncTasks.length;
723
+ for (const subReport of subReports) {
724
+ report.asyncTasks.push(...subReport.asyncTasks);
725
+ }
726
+ const hasAsyncTasks = report.asyncTasks.length > asyncTasksBefore;
727
+
728
+ const addPropertyNameErrors = () => {
729
+ for (let idx = 0; idx < keys.length; idx++) {
730
+ if (subReports[idx].errors.length > 0) {
731
+ report.addError('PROPERTY_NAMES', [keys[idx]], subReports[idx], schema, undefined);
732
+ }
733
+ }
734
+ };
735
+
736
+ if (hasAsyncTasks) {
737
+ const pathBeforeAsync = shallowClone(report.path);
738
+ report.addAsyncTask(
739
+ (callback) => {
740
+ setTimeout(() => callback(null), 0);
741
+ },
742
+ [] as any,
743
+ () => {
744
+ const backup = report.path;
745
+ report.path = pathBeforeAsync;
746
+ addPropertyNameErrors();
747
+ report.path = backup;
748
+ }
749
+ );
750
+ return;
751
+ }
752
+
753
+ addPropertyNameErrors();
754
+ },
599
755
  };
600
756
 
601
757
  const recurseArray = function (this: ZSchemaBase, report: Report, schema: JsonSchemaInternal, json: Array<unknown>) {
@@ -623,7 +779,7 @@ const recurseArray = function (this: ZSchemaBase, report: Report, schema: JsonSc
623
779
  }
624
780
  }
625
781
  }
626
- } else if (typeof schema.items === 'object') {
782
+ } else if (typeof schema.items === 'object' || typeof schema.items === 'boolean') {
627
783
  // If items is a schema, then the child instance must be valid against this schema,
628
784
  // regardless of its index, and regardless of the value of "additionalItems".
629
785
  while (idx--) {
@@ -673,7 +829,8 @@ const recurseObject = function (this: ZSchemaBase, report: Report, schema: JsonS
673
829
  let idx2 = pp.length;
674
830
  while (idx2--) {
675
831
  const regexString = pp[idx2];
676
- if (RegExp(regexString).test(m) === true) {
832
+ const result = compileSchemaRegex(regexString);
833
+ if (result.ok && result.value.test(m) === true) {
677
834
  s.push(schema.patternProperties![regexString]);
678
835
  }
679
836
  }
@@ -710,13 +867,26 @@ const recurseObject = function (this: ZSchemaBase, report: Report, schema: JsonS
710
867
  }
711
868
  };
712
869
 
713
- export function validate(this: ZSchemaBase, report: Report, schema: JsonSchemaInternal, json: unknown): boolean {
870
+ export function validate(
871
+ this: ZSchemaBase,
872
+ report: Report,
873
+ schema: boolean | JsonSchemaInternal,
874
+ json: unknown
875
+ ): boolean {
714
876
  report.commonErrorMessage = 'JSON_OBJECT_VALIDATION_FAILED';
715
877
 
878
+ if (schema === true) {
879
+ return true;
880
+ }
881
+
882
+ if (schema === false) {
883
+ report.addError('SCHEMA_IS_FALSE', [], undefined, schema);
884
+ return false;
885
+ }
886
+
716
887
  // check if schema is an object
717
- const to = whatIs(schema);
718
- if (to !== 'object') {
719
- report.addError('SCHEMA_NOT_AN_OBJECT', [to], undefined, schema);
888
+ if (!isObject(schema)) {
889
+ report.addError('SCHEMA_NOT_AN_OBJECT', [whatIs(schema)], undefined, schema);
720
890
  return false;
721
891
  }
722
892
 
package/src/report.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import type { ErrorCode, ErrorParam } from './errors.js';
2
- import type { JsonSchema, JsonSchemaInternal } from './json-schema.js';
2
+ import type { JsonSchema, JsonSchemaInternal } from './json-schema-versions.js';
3
3
  import type { ValidateCallback, ValidateOptions } from './z-schema-base.js';
4
4
  import type { ZSchemaOptions } from './z-schema-options.js';
5
5
 
@@ -7,7 +7,7 @@ import { Errors, getValidateError } from './errors.js';
7
7
  import { get } from './utils/json.js';
8
8
  import { jsonSymbol, schemaSymbol } from './utils/symbols.js';
9
9
  import { isAbsoluteUri } from './utils/uri.js';
10
- import { whatIs } from './utils/what-is.js';
10
+ import { isObject } from './utils/what-is.js';
11
11
 
12
12
  export interface SchemaErrorDetail {
13
13
  /**
@@ -261,7 +261,7 @@ export class Report {
261
261
  errCode: ErrorCode,
262
262
  errParams?: ErrorParam[],
263
263
  subReports?: Report | Report[],
264
- schema?: JsonSchema,
264
+ schema?: JsonSchema | boolean,
265
265
  keyword?: keyof JsonSchema
266
266
  ) {
267
267
  if (!errCode) {
@@ -286,7 +286,7 @@ export class Report {
286
286
  errorMessage: string,
287
287
  params?: ErrorParam[],
288
288
  subReports?: Report | Report[],
289
- schema?: JsonSchema,
289
+ schema?: JsonSchema | boolean,
290
290
  keyword?: keyof JsonSchema
291
291
  ) {
292
292
  if (typeof this.reportOptions.maxErrors === 'number' && this.errors.length >= this.reportOptions.maxErrors) {
@@ -301,8 +301,7 @@ export class Report {
301
301
 
302
302
  let idx = params.length;
303
303
  while (idx--) {
304
- const paramType = whatIs(params[idx]);
305
- const param = paramType === 'object' || paramType === 'null' ? JSON.stringify(params[idx]) : params[idx];
304
+ const param = params[idx] === null || isObject(params[idx]) ? JSON.stringify(params[idx]) : params[idx];
306
305
  errorMessage = errorMessage.replace('{' + idx + '}', param.toString());
307
306
  }
308
307
 
@@ -1,7 +1,7 @@
1
- import type { JsonSchema, JsonSchemaInternal } from './json-schema.js';
1
+ import type { JsonSchema, JsonSchemaInternal } from './json-schema-versions.js';
2
2
  import type { ZSchemaBase } from './z-schema-base.js';
3
3
 
4
- import { findId } from './json-schema.js';
4
+ import { findId, getId } from './json-schema.js';
5
5
  import { Report } from './report.js';
6
6
  import { deepClone } from './utils/clone.js';
7
7
  import { decodeJSONPointer } from './utils/json.js';
@@ -63,7 +63,9 @@ export class SchemaCache {
63
63
  return this.cache[path];
64
64
  }
65
65
  const asClone = (s: JsonSchema) => {
66
- s.id ??= path;
66
+ if (!s.id || (!isAbsoluteUri(s.id) && isAbsoluteUri(path))) {
67
+ s.id = path;
68
+ }
67
69
  return deepClone(s);
68
70
  };
69
71
  found = SchemaCache.global_cache[path];
@@ -74,10 +76,30 @@ export class SchemaCache {
74
76
  }
75
77
 
76
78
  getSchemaByUri(report: Report, uri: string, root?: JsonSchemaInternal) {
79
+ if (root && !isAbsoluteUri(uri)) {
80
+ let rootId = getId(root);
81
+ if ((!rootId || !isAbsoluteUri(rootId)) && typeof root.id === 'string' && isAbsoluteUri(root.id)) {
82
+ rootId = root.id;
83
+ }
84
+ if (rootId && isAbsoluteUri(rootId)) {
85
+ const hashIndex = rootId.indexOf('#');
86
+ const rootBase = hashIndex === -1 ? rootId : rootId.slice(0, hashIndex);
87
+ try {
88
+ uri = new URL(uri, rootBase).toString();
89
+ } catch {
90
+ // keep original uri when URL construction fails
91
+ }
92
+ }
93
+ }
94
+
77
95
  const remotePath = getRemotePath(uri);
78
96
  const queryPath = getQueryPath(uri);
79
97
  let result = remotePath ? this.fromCache(remotePath) : root;
80
98
 
99
+ if (result && remotePath && isAbsoluteUri(remotePath) && (!result.id || !isAbsoluteUri(result.id))) {
100
+ result.id = remotePath;
101
+ }
102
+
81
103
  if (result && remotePath) {
82
104
  // we need to avoid compiling schemas in a recursive loop
83
105
  const compileRemote = result !== root;
@@ -1,6 +1,7 @@
1
- import type { JsonSchemaInternal } from './json-schema.js';
1
+ import type { JsonSchemaInternal } from './json-schema-versions.js';
2
2
  import type { ZSchemaBase } from './z-schema-base.js';
3
3
 
4
+ import { getId } from './json-schema.js';
4
5
  import { Report } from './report.js';
5
6
  import { getRemotePath, isAbsoluteUri } from './utils/uri.js';
6
7
  import { getSchemaReader } from './z-schema-reader.js';
@@ -15,29 +16,34 @@ interface Id {
15
16
 
16
17
  export const collectIds = (obj: JsonSchemaInternal) => {
17
18
  const ids: Id[] = [];
19
+ const doNotCollectIdsFrom = ['enum', 'const', 'default', 'examples'];
18
20
  function walk(node: any, scope: Id[]) {
19
21
  if (typeof node !== 'object' || node == null) return;
20
22
 
21
23
  let addedScope = false;
22
24
 
23
- if (node.id && typeof node.id === 'string') {
24
- let type: Id['type'] = isAbsoluteUri(node.id) ? 'absolute' : 'relative';
25
+ const nodeId = getId(node as JsonSchemaInternal);
26
+ if (typeof nodeId === 'string') {
27
+ let type: Id['type'] = isAbsoluteUri(nodeId) ? 'absolute' : 'relative';
25
28
  if (scope.length === 0) {
26
29
  type = 'root';
27
30
  }
28
31
  const id: Id = {
29
- id: node.id,
32
+ id: nodeId,
30
33
  type,
31
34
  obj: node,
32
35
  };
33
- if (type === 'absolute' || (type === 'root' && isAbsoluteUri(node.id))) {
34
- id.absoluteUri = node.id;
36
+ if (type === 'absolute' || (type === 'root' && isAbsoluteUri(nodeId))) {
37
+ id.absoluteUri = nodeId;
38
+ } else if (type === 'root' && typeof node.id === 'string' && isAbsoluteUri(node.id) && node.id !== nodeId) {
39
+ id.absoluteUri = resolveIdScope(node.id, nodeId);
35
40
  } else if (type === 'relative') {
36
41
  id.absoluteParent = scope
37
42
  .filter((x) => x.type === 'absolute' || (x.type === 'root' && x.absoluteUri))
38
43
  .slice(-1)[0];
39
44
  if (id.absoluteParent) {
40
- id.absoluteUri = id.absoluteParent.id.split('/').slice(0, -1).concat(id.id).join('/');
45
+ const parentUri = id.absoluteParent.absoluteUri || id.absoluteParent.id;
46
+ id.absoluteUri = parentUri.split('/').slice(0, -1).concat(id.id).join('/');
41
47
  }
42
48
  }
43
49
  ids.push(id);
@@ -51,7 +57,7 @@ export const collectIds = (obj: JsonSchemaInternal) => {
51
57
  }
52
58
  } else {
53
59
  for (const key of Object.keys(node)) {
54
- if (key.indexOf('__$') === 0) continue;
60
+ if (key.indexOf('__$') === 0 || doNotCollectIdsFrom.includes(key)) continue;
55
61
  walk(node[key], scope);
56
62
  }
57
63
  }
@@ -71,7 +77,7 @@ export interface Reference {
71
77
  path: Array<string | number>;
72
78
  }
73
79
 
74
- const doNotCollectReferencesFrom = ['enum'];
80
+ const doNotCollectReferencesFrom = ['enum', 'const', 'default', 'examples'];
75
81
 
76
82
  export const collectReferences = (
77
83
  obj: JsonSchemaInternal,
@@ -90,9 +96,15 @@ export const collectReferences = (
90
96
  const hasRef = typeof obj.$ref === 'string' && typeof obj.__$refResolved === 'undefined';
91
97
  let addedScope = false;
92
98
  const isRootScope = scope.length === 0;
93
- if (typeof obj.id === 'string' && (isRootScope || !hasRef)) {
99
+ const objId = getId(obj);
100
+ let scopeId = objId;
101
+ if (typeof obj.id === 'string' && isAbsoluteUri(obj.id) && (!scopeId || !isAbsoluteUri(scopeId))) {
102
+ scopeId = obj.id;
103
+ }
104
+
105
+ if (typeof scopeId === 'string' && (isRootScope || !hasRef)) {
94
106
  const base = scope.length > 0 ? scope[scope.length - 1] : undefined;
95
- scope.push(resolveIdScope(base, obj.id));
107
+ scope.push(resolveIdScope(base, scopeId));
96
108
  addedScope = true;
97
109
  }
98
110
 
@@ -227,6 +239,8 @@ export class SchemaCompiler {
227
239
  schema.forEach((s) => this.collectAndCacheIds(s));
228
240
  }
229
241
  return this.compileArrayOfSchemas(report, schema);
242
+ } else if (typeof schema === 'boolean') {
243
+ return true;
230
244
  } else {
231
245
  if (!options?.noCache) {
232
246
  this.collectAndCacheIds(schema);