json-schema-library 5.3.0 → 7.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 (161) hide show
  1. package/.editorconfig +1 -0
  2. package/.prettierignore +1 -0
  3. package/.prettierrc +7 -0
  4. package/CHANGELOG.md +106 -0
  5. package/README.md +811 -199
  6. package/TASKS.md +3 -81
  7. package/dist/index.d.ts +29 -71
  8. package/dist/jsonSchemaLibrary.js +1 -1
  9. package/dist/lib/SchemaService.d.ts +6 -8
  10. package/dist/lib/{addSchema.d.ts → addRemoteSchema.d.ts} +2 -1
  11. package/dist/lib/addValidator.d.ts +3 -2
  12. package/dist/lib/compile/getRef.d.ts +2 -1
  13. package/dist/lib/compile/index.d.ts +15 -1
  14. package/dist/lib/compile/types.d.ts +5 -0
  15. package/dist/lib/config/strings.d.ts +1 -39
  16. package/dist/lib/draft/index.d.ts +132 -0
  17. package/dist/lib/draft04/index.d.ts +7 -0
  18. package/dist/lib/draft06/compile/index.d.ts +16 -0
  19. package/dist/lib/draft06/index.d.ts +7 -0
  20. package/dist/lib/draft06/validation/keyword.d.ts +3 -0
  21. package/dist/lib/draft06/validation/type.d.ts +10 -0
  22. package/dist/lib/draft06/validation/typeKeywordMapping.d.ts +13 -0
  23. package/dist/lib/draft07/index.d.ts +7 -0
  24. package/dist/lib/each.d.ts +3 -2
  25. package/dist/lib/eachSchema.d.ts +2 -4
  26. package/dist/lib/getChildSchemaSelection.d.ts +7 -5
  27. package/dist/lib/getSchema.d.ts +2 -2
  28. package/dist/lib/getTemplate.d.ts +1 -1
  29. package/dist/lib/getTypeOf.d.ts +2 -1
  30. package/dist/lib/isValid.d.ts +1 -1
  31. package/dist/lib/jsoneditor/index.d.ts +7 -0
  32. package/dist/lib/resolveAllOf.d.ts +1 -1
  33. package/dist/lib/resolveAnyOf.d.ts +1 -1
  34. package/dist/lib/resolveOneOf.fuzzy.d.ts +1 -1
  35. package/dist/lib/resolveOneOf.strict.d.ts +2 -2
  36. package/dist/lib/{resolveRef.withOverwrite.d.ts → resolveRef.merge.d.ts} +0 -0
  37. package/dist/lib/schema/getTypeDefs.d.ts +6 -4
  38. package/dist/lib/schema/getTypeId.d.ts +1 -1
  39. package/dist/lib/schema/types.d.ts +7 -57
  40. package/dist/lib/step.d.ts +5 -5
  41. package/dist/lib/types.d.ts +11 -3
  42. package/dist/lib/utils/createCustomError.d.ts +8 -11
  43. package/dist/lib/utils/filter.d.ts +4 -4
  44. package/dist/lib/utils/flattenArray.d.ts +1 -1
  45. package/dist/lib/utils/merge.d.ts +3 -0
  46. package/dist/lib/utils/punycode.ucs2decode.d.ts +1 -1
  47. package/dist/lib/validate.d.ts +2 -2
  48. package/dist/lib/validateAsync.d.ts +1 -1
  49. package/dist/lib/validation/errors.d.ts +1 -3
  50. package/dist/lib/validation/format.d.ts +4 -11
  51. package/dist/lib/validation/keyword.d.ts +2 -27
  52. package/dist/lib/validation/type.d.ts +3 -10
  53. package/dist/lib/validation/typeKeywordMapping.d.ts +4 -4
  54. package/dist/module/index.js +23 -32
  55. package/dist/module/lib/SchemaService.js +7 -4
  56. package/dist/module/lib/{addSchema.js → addRemoteSchema.js} +2 -4
  57. package/dist/module/lib/addValidator.js +3 -4
  58. package/dist/module/lib/compile/getRef.js +1 -1
  59. package/dist/module/lib/compile/index.js +43 -18
  60. package/dist/module/lib/compile/types.js +1 -0
  61. package/dist/module/lib/config/strings.js +15 -2
  62. package/dist/module/lib/createSchemaOf.js +1 -1
  63. package/dist/module/lib/draft/index.js +133 -0
  64. package/dist/module/lib/draft04/index.js +90 -0
  65. package/dist/module/lib/draft06/compile/index.js +77 -0
  66. package/dist/module/lib/draft06/index.js +96 -0
  67. package/dist/module/lib/draft06/validation/keyword.js +168 -0
  68. package/dist/module/lib/draft06/validation/type.js +31 -0
  69. package/dist/module/lib/draft06/validation/typeKeywordMapping.js +15 -0
  70. package/dist/module/lib/draft07/index.js +96 -0
  71. package/dist/module/lib/each.js +2 -2
  72. package/dist/module/lib/eachSchema.js +28 -19
  73. package/dist/module/lib/getChildSchemaSelection.js +7 -6
  74. package/dist/module/lib/getSchema.js +4 -2
  75. package/dist/module/lib/getTemplate.js +42 -15
  76. package/dist/module/lib/jsoneditor/index.js +16 -0
  77. package/dist/module/lib/resolveAllOf.js +3 -4
  78. package/dist/module/lib/resolveOneOf.fuzzy.js +13 -3
  79. package/dist/module/lib/resolveOneOf.strict.js +49 -2
  80. package/dist/module/lib/{resolveRef.withOverwrite.js → resolveRef.merge.js} +0 -0
  81. package/dist/module/lib/resolveRef.strict.js +8 -0
  82. package/dist/module/lib/schema/getTypeDefs.js +14 -3
  83. package/dist/module/lib/schema/getTypeId.js +10 -6
  84. package/dist/module/lib/schema/types.js +33 -9
  85. package/dist/module/lib/step.js +67 -13
  86. package/dist/module/lib/types.js +7 -1
  87. package/dist/module/lib/utils/createCustomError.js +4 -4
  88. package/dist/module/lib/utils/filter.js +3 -5
  89. package/dist/module/lib/utils/flattenArray.js +4 -3
  90. package/dist/module/lib/utils/merge.js +4 -0
  91. package/dist/module/lib/utils/punycode.ucs2decode.js +4 -3
  92. package/dist/module/lib/validate.js +34 -8
  93. package/dist/module/lib/validateAsync.js +7 -7
  94. package/dist/module/lib/validation/errors.js +16 -3
  95. package/dist/module/lib/validation/format.js +115 -8
  96. package/dist/module/lib/validation/keyword.js +79 -32
  97. package/dist/module/lib/validation/type.js +2 -1
  98. package/index.ts +46 -32
  99. package/lib/SchemaService.ts +18 -11
  100. package/lib/{addSchema.ts → addRemoteSchema.ts} +3 -5
  101. package/lib/addValidator.ts +15 -12
  102. package/lib/compile/getRef.ts +3 -4
  103. package/lib/compile/index.ts +65 -19
  104. package/lib/compile/types.ts +6 -0
  105. package/lib/config/strings.ts +17 -3
  106. package/lib/createSchemaOf.ts +1 -3
  107. package/lib/draft/index.ts +201 -0
  108. package/lib/draft04/index.ts +95 -0
  109. package/lib/draft06/compile/index.ts +104 -0
  110. package/lib/draft06/index.ts +101 -0
  111. package/lib/draft06/validation/keyword.ts +199 -0
  112. package/lib/draft06/validation/type.ts +47 -0
  113. package/lib/draft06/validation/typeKeywordMapping.ts +15 -0
  114. package/lib/draft07/index.ts +101 -0
  115. package/lib/each.ts +11 -4
  116. package/lib/eachSchema.ts +45 -32
  117. package/lib/getChildSchemaSelection.ts +14 -7
  118. package/lib/getSchema.ts +18 -9
  119. package/lib/getTemplate.ts +155 -42
  120. package/lib/getTypeOf.ts +2 -1
  121. package/lib/isValid.ts +7 -3
  122. package/lib/jsoneditor/index.ts +20 -0
  123. package/lib/resolveAllOf.ts +10 -6
  124. package/lib/resolveAnyOf.ts +7 -3
  125. package/lib/resolveOneOf.fuzzy.ts +26 -9
  126. package/lib/resolveOneOf.strict.ts +63 -5
  127. package/lib/{resolveRef.withOverwrite.ts → resolveRef.merge.ts} +0 -0
  128. package/lib/resolveRef.strict.ts +9 -0
  129. package/lib/schema/getTypeDefs.ts +19 -7
  130. package/lib/schema/getTypeId.ts +11 -8
  131. package/lib/schema/types.ts +41 -9
  132. package/lib/step.ts +109 -25
  133. package/lib/types.ts +23 -5
  134. package/lib/utils/createCustomError.ts +8 -13
  135. package/lib/utils/filter.ts +7 -9
  136. package/lib/utils/flattenArray.ts +5 -4
  137. package/lib/utils/merge.ts +5 -0
  138. package/lib/utils/punycode.ucs2decode.ts +6 -5
  139. package/lib/validate.ts +47 -16
  140. package/lib/validateAsync.ts +15 -14
  141. package/lib/validation/errors.ts +17 -6
  142. package/lib/validation/format.ts +147 -13
  143. package/lib/validation/keyword.ts +172 -103
  144. package/lib/validation/type.ts +5 -1
  145. package/package.json +75 -63
  146. package/{dist/module/remotes/draft04.json → remotes/draft06.json} +47 -42
  147. package/remotes/draft07.json +172 -0
  148. package/remotes/draft2019-09.json +86 -0
  149. package/tsconfig.json +3 -10
  150. package/dist/lib/cores/CoreInterface.d.ts +0 -23
  151. package/dist/lib/cores/Draft04.d.ts +0 -13
  152. package/dist/lib/cores/JsonEditor.d.ts +0 -13
  153. package/dist/module/lib/cores/CoreInterface.js +0 -51
  154. package/dist/module/lib/cores/Draft04.js +0 -52
  155. package/dist/module/lib/cores/JsonEditor.js +0 -51
  156. package/dist/module/remotes/index.js +0 -10
  157. package/dist/remotes/index.d.ts +0 -5
  158. package/lib/cores/CoreInterface.ts +0 -76
  159. package/lib/cores/Draft04.ts +0 -66
  160. package/lib/cores/JsonEditor.ts +0 -64
  161. package/remotes/index.ts +0 -11
@@ -1,10 +1,11 @@
1
1
  export default function flattenArray(list, result = []) {
2
2
  for (let i = 0; i < list.length; i += 1) {
3
- if (Array.isArray(list[i])) {
4
- flattenArray(list[i], result);
3
+ const item = list[i];
4
+ if (Array.isArray(item)) {
5
+ flattenArray(item, result);
5
6
  }
6
7
  else {
7
- result.push(list[i]);
8
+ result.push(item);
8
9
  }
9
10
  }
10
11
  return result;
@@ -1,3 +1,7 @@
1
1
  import deepmerge from "deepmerge";
2
+ // @ts-ignore
2
3
  const overwriteMerge = (destinationArray, sourceArray) => sourceArray;
4
+ /**
5
+ * returns a new json-schema, where properties are combined and arrays are replaced
6
+ */
3
7
  export default (a, b) => deepmerge(a, b, { arrayMerge: overwriteMerge });
@@ -20,12 +20,13 @@ export default function ucs2decode(string) {
20
20
  const length = string.length;
21
21
  while (counter < length) {
22
22
  const value = string.charCodeAt(counter++);
23
- if (value >= 0xD800 && value <= 0xDBFF && counter < length) {
23
+ if (value >= 0xd800 && value <= 0xdbff && counter < length) {
24
24
  // It's a high surrogate, and there is a next character.
25
25
  const extra = string.charCodeAt(counter++);
26
26
  // eslint-disable-next-line eqeqeq
27
- if ((extra & 0xFC00) == 0xDC00) { // Low surrogate.
28
- output.push(((value & 0x3FF) << 10) + (extra & 0x3FF) + 0x10000);
27
+ if ((extra & 0xfc00) == 0xdc00) {
28
+ // Low surrogate.
29
+ output.push(((value & 0x3ff) << 10) + (extra & 0x3ff) + 0x10000);
29
30
  }
30
31
  else {
31
32
  // It's an unmatched surrogate; only append this code unit, in case the
@@ -1,11 +1,14 @@
1
1
  import getTypeOf from "./getTypeOf";
2
2
  import { errorOrPromise } from "./utils/filter";
3
3
  import flattenArray from "./utils/flattenArray";
4
+ import { isJSONError } from "./types";
5
+ import equal from "fast-deep-equal";
4
6
  function getJsonSchemaType(value, expectedType) {
5
- let jsType = getTypeOf(value);
6
- if (jsType === "number" && (expectedType === "integer" ||
7
- (Array.isArray(expectedType) && expectedType.includes("integer")))) {
8
- jsType = Number.isInteger(value) ? "integer" : "number";
7
+ const jsType = getTypeOf(value);
8
+ if (jsType === "number" &&
9
+ (expectedType === "integer" ||
10
+ (Array.isArray(expectedType) && expectedType.includes("integer")))) {
11
+ return Number.isInteger(value) ? "integer" : "number";
9
12
  }
10
13
  return jsType;
11
14
  }
@@ -19,19 +22,42 @@ function getJsonSchemaType(value, expectedType) {
19
22
  * @return list of errors or empty
20
23
  */
21
24
  export default function validate(core, value, schema = core.rootSchema, pointer = "#") {
22
- if (schema.type === "error") {
25
+ schema = core.resolveRef(schema);
26
+ // this is a high level v7 schema validation
27
+ if (getTypeOf(schema) === "boolean") {
28
+ if (schema) {
29
+ return [];
30
+ }
31
+ return [core.errors.invalidDataError({ value, pointer })];
32
+ }
33
+ if (isJSONError(schema)) {
23
34
  return [schema];
24
35
  }
25
- schema = core.resolveRef(schema);
36
+ // @draft >= 6 const
37
+ if (schema.const !== undefined) {
38
+ if (equal(schema.const, value)) {
39
+ return [];
40
+ }
41
+ return [core.errors.constError({ value, expected: schema.const, pointer })];
42
+ }
26
43
  const receivedType = getJsonSchemaType(value, schema.type);
27
44
  const expectedType = schema.type || receivedType;
28
- if (receivedType !== expectedType && (!Array.isArray(expectedType) || !expectedType.includes(receivedType))) {
29
- return [core.errors.typeError({ received: receivedType, expected: expectedType, value, pointer })];
45
+ if (receivedType !== expectedType &&
46
+ (!Array.isArray(expectedType) || !expectedType.includes(receivedType))) {
47
+ return [
48
+ core.errors.typeError({
49
+ received: receivedType,
50
+ expected: expectedType,
51
+ value,
52
+ pointer
53
+ })
54
+ ];
30
55
  }
31
56
  if (core.validateType[receivedType] == null) {
32
57
  return [core.errors.invalidTypeError({ receivedType, pointer })];
33
58
  }
34
59
  const errors = flattenArray(core.validateType[receivedType](core, schema, value, pointer));
35
60
  // also promises may be passed along (validateAsync)
61
+ // @ts-ignore
36
62
  return errors.filter(errorOrPromise);
37
63
  }
@@ -1,5 +1,6 @@
1
- import { isError, errorsOnly } from "./utils/filter";
1
+ import { errorsOnly } from "./utils/filter";
2
2
  import flattenArray from "./utils/flattenArray";
3
+ import { isJSONError } from "./types";
3
4
  function createErrorNotification(onError) {
4
5
  return function notifyError(error) {
5
6
  if (Array.isArray(error)) {
@@ -7,7 +8,7 @@ function createErrorNotification(onError) {
7
8
  error.forEach(notifyError);
8
9
  return error;
9
10
  }
10
- if (isError(error)) {
11
+ if (isJSONError(error)) {
11
12
  onError(error);
12
13
  }
13
14
  return error;
@@ -35,16 +36,15 @@ export default function validateAsync(core, value, options) {
35
36
  if (errors[i] instanceof Promise) {
36
37
  errors[i].then(notifyError);
37
38
  }
38
- else if (isError(errors[i])) {
39
+ else if (isJSONError(errors[i])) {
39
40
  onError(errors[i]);
40
41
  }
41
42
  }
42
43
  }
43
- return Promise
44
- .all(errors)
44
+ return Promise.all(errors)
45
45
  .then(flattenArray)
46
- .then(resolvedErrors => resolvedErrors.filter(errorsOnly))
47
- .catch(e => {
46
+ .then((resolvedErrors) => resolvedErrors.filter(errorsOnly))
47
+ .catch((e) => {
48
48
  console.log("Failed resolving promises", e.message);
49
49
  console.log(e.stack);
50
50
  throw e;
@@ -1,21 +1,34 @@
1
1
  /* eslint no-invalid-this: 0 */
2
- import createCustomError from "../utils/createCustomError";
2
+ import { createCustomError } from "../utils/createCustomError";
3
3
  const errors = {
4
4
  additionalItemsError: createCustomError("AdditionalItemsError"),
5
5
  additionalPropertiesError: createCustomError("AdditionalPropertiesError"),
6
6
  anyOfError: createCustomError("AnyOfError"),
7
7
  allOfError: createCustomError("AllOfError"),
8
+ constError: createCustomError("ConstError"),
9
+ containsError: createCustomError("ContainsError"),
10
+ containsArrayError: createCustomError("ContainsArrayError"),
11
+ containsAnyError: createCustomError("ContainsAnyError"),
8
12
  enumError: createCustomError("EnumError"),
9
- formatUrlError: createCustomError("FormatUrlError"),
10
- formatUriError: createCustomError("FormatUriError"),
13
+ formatURLError: createCustomError("FormatURLError"),
14
+ formatURIError: createCustomError("FormatURIError"),
15
+ formatURIReferenceError: createCustomError("FormatURIReferenceError"),
16
+ formatURITemplateError: createCustomError("FormatURITemplateError"),
17
+ formatDateError: createCustomError("FormatDateaError"),
11
18
  formatDateTimeError: createCustomError("FormatDateTimeError"),
12
19
  formatEmailError: createCustomError("FormatEmailError"),
13
20
  formatHostnameError: createCustomError("FormatHostnameError"),
14
21
  formatIPV4Error: createCustomError("FormatIPV4Error"),
22
+ formatIPV4LeadingZeroError: createCustomError("FormatIPV4LeadingZeroError"),
15
23
  formatIPV6Error: createCustomError("FormatIPV6Error"),
24
+ formatIPV6LeadingZeroError: createCustomError("FormatIPV6LeadingZeroError"),
25
+ formatJSONPointerError: createCustomError("FormatJSONPointerError"),
16
26
  formatRegExError: createCustomError("FormatRegExError"),
27
+ formatTimeError: createCustomError("FormatTimeError"),
17
28
  invalidSchemaError: createCustomError("InvalidSchemaError"),
29
+ invalidDataError: createCustomError("InvalidDataError"),
18
30
  invalidTypeError: createCustomError("InvalidTypeError"),
31
+ invalidPropertyNameError: createCustomError("InvalidPropertyNameError"),
19
32
  maximumError: createCustomError("MaximumError"),
20
33
  maxItemsError: createCustomError("MaxItemsError"),
21
34
  maxLengthError: createCustomError("MaxLengthError"),
@@ -1,13 +1,46 @@
1
- /* eslint-disable max-len */
1
+ /* eslint-disable max-len, no-control-regex */
2
2
  import errors from "./errors";
3
3
  import validUrl from "valid-url";
4
+ // referenced
5
+ // https://github.com/cfworker/cfworker/blob/main/packages/json-schema/src/format.ts
4
6
  // https://gist.github.com/marcelotmelo/b67f58a08bee6c2468f8
5
7
  const isValidDateTime = new RegExp("^([0-9]+)-(0[1-9]|1[012])-(0[1-9]|[12][0-9]|3[01])[Tt]([01][0-9]|2[0-3]):([0-5][0-9]):([0-5][0-9]|60)(\\.[0-9]+)?(([Zz])|([\\+|\\-]([01][0-9]|2[0-3]):[0-5][0-9]))$");
6
8
  const isValidIPV4 = /^(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)$/;
7
9
  const isValidIPV6 = /^((([0-9a-f]{1,4}:){7}([0-9a-f]{1,4}|:))|(([0-9a-f]{1,4}:){6}(:[0-9a-f]{1,4}|((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){5}(((:[0-9a-f]{1,4}){1,2})|:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3})|:))|(([0-9a-f]{1,4}:){4}(((:[0-9a-f]{1,4}){1,3})|((:[0-9a-f]{1,4})?:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){3}(((:[0-9a-f]{1,4}){1,4})|((:[0-9a-f]{1,4}){0,2}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){2}(((:[0-9a-f]{1,4}){1,5})|((:[0-9a-f]{1,4}){0,3}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(([0-9a-f]{1,4}:){1}(((:[0-9a-f]{1,4}){1,6})|((:[0-9a-f]{1,4}){0,4}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:))|(:(((:[0-9a-f]{1,4}){1,7})|((:[0-9a-f]{1,4}){0,5}:((25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)(\.(25[0-5]|2[0-4]\d|1\d\d|[1-9]?\d)){3}))|:)))$/i;
8
10
  const isValidHostname = /^(?=.{1,255}$)[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?(?:\.[0-9A-Za-z](?:(?:[0-9A-Za-z]|-){0,61}[0-9A-Za-z])?)*\.?$/;
11
+ const matchDate = /^(\d\d\d\d)-(\d\d)-(\d\d)$/;
12
+ const matchTime = /^(\d\d):(\d\d):(\d\d)(\.\d+)?(z|[+-]\d\d(?::?\d\d)?)?$/i;
13
+ const DAYS = [0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
14
+ const isValidJSONPointer = /^(?:\/(?:[^~/]|~0|~1)*)*$/;
15
+ const isValidRelativeJSONPointer = /^(?:0|[1-9][0-9]*)(?:#|(?:\/(?:[^~/]|~0|~1)*)*)$/;
16
+ const isValidURIRef = /^(?:[a-z][a-z0-9+\-.]*:)?(?:\/?\/(?:(?:[a-z0-9\-._~!$&'()*+,;=:]|%[0-9a-f]{2})*@)?(?:\[(?:(?:(?:(?:[0-9a-f]{1,4}:){6}|::(?:[0-9a-f]{1,4}:){5}|(?:[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){4}|(?:(?:[0-9a-f]{1,4}:){0,1}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){3}|(?:(?:[0-9a-f]{1,4}:){0,2}[0-9a-f]{1,4})?::(?:[0-9a-f]{1,4}:){2}|(?:(?:[0-9a-f]{1,4}:){0,3}[0-9a-f]{1,4})?::[0-9a-f]{1,4}:|(?:(?:[0-9a-f]{1,4}:){0,4}[0-9a-f]{1,4})?::)(?:[0-9a-f]{1,4}:[0-9a-f]{1,4}|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?))|(?:(?:[0-9a-f]{1,4}:){0,5}[0-9a-f]{1,4})?::[0-9a-f]{1,4}|(?:(?:[0-9a-f]{1,4}:){0,6}[0-9a-f]{1,4})?::)|[Vv][0-9a-f]+\.[a-z0-9\-._~!$&'()*+,;=:]+)\]|(?:(?:25[0-5]|2[0-4]\d|[01]?\d\d?)\.){3}(?:25[0-5]|2[0-4]\d|[01]?\d\d?)|(?:[a-z0-9\-._~!$&'"()*+,;=]|%[0-9a-f]{2})*)(?::\d*)?(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*|\/(?:(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*)?|(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})+(?:\/(?:[a-z0-9\-._~!$&'"()*+,;=:@]|%[0-9a-f]{2})*)*)?(?:\?(?:[a-z0-9\-._~!$&'"()*+,;=:@/?]|%[0-9a-f]{2})*)?(?:#(?:[a-z0-9\-._~!$&'"()*+,;=:@/?]|%[0-9a-f]{2})*)?$/i;
17
+ // uri-template: https://tools.ietf.org/html/rfc6570
18
+ const isValidURITemplate = /^(?:(?:[^\x00-\x20"'<>%\\^`{|}]|%[0-9a-f]{2})|\{[+#./;?&=,!@|]?(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\*)?(?:,(?:[a-z0-9_]|%[0-9a-f]{2})+(?::[1-9][0-9]{0,3}|\*)?)*\})*$/i;
9
19
  // Default JSON-Schema formats: date-time, email, hostname, ipv4, ipv6, uri, uriref
10
- export default {
20
+ const formatValidators = {
21
+ date: (core, schema, value, pointer) => {
22
+ if (typeof value !== "string") {
23
+ return undefined;
24
+ }
25
+ // https://github.com/cfworker/cfworker/blob/main/packages/json-schema/src/format.ts
26
+ // full-date from http://tools.ietf.org/html/rfc3339#section-5.6
27
+ const matches = value.match(matchDate);
28
+ if (!matches) {
29
+ return errors.formatDateTimeError({ value, pointer });
30
+ }
31
+ const year = +matches[1];
32
+ const month = +matches[2];
33
+ const day = +matches[3];
34
+ // https://tools.ietf.org/html/rfc3339#appendix-C
35
+ const isLeapYear = year % 4 === 0 && (year % 100 !== 0 || year % 400 === 0);
36
+ if (month >= 1 &&
37
+ month <= 12 &&
38
+ day >= 1 &&
39
+ day <= (month == 2 && isLeapYear ? 29 : DAYS[month])) {
40
+ return undefined;
41
+ }
42
+ return errors.formatDateError({ value, pointer });
43
+ },
11
44
  "date-time": (core, schema, value, pointer) => {
12
45
  if (typeof value !== "string") {
13
46
  return undefined;
@@ -28,17 +61,17 @@ export default {
28
61
  if (value[0] === '"') {
29
62
  return errors.formatEmailError({ value, pointer });
30
63
  }
31
- const [name, host, ...rest] = value.split('@');
64
+ const [name, host, ...rest] = value.split("@");
32
65
  if (!name || !host || rest.length !== 0 || name.length > 64 || host.length > 253) {
33
66
  return errors.formatEmailError({ value, pointer });
34
67
  }
35
- if (name[0] === '.' || name.endsWith('.') || name.includes('..')) {
68
+ if (name[0] === "." || name.endsWith(".") || name.includes("..")) {
36
69
  return errors.formatEmailError({ value, pointer });
37
70
  }
38
71
  if (!/^[a-z0-9.-]+$/i.test(host) || !/^[a-z0-9.!#$%&'*+/=?^_`{|}~-]+$/i.test(name)) {
39
72
  return errors.formatEmailError({ value, pointer });
40
73
  }
41
- if (!host.split('.').every(part => /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/i.test(part))) {
74
+ if (!host.split(".").every((part) => /^[a-z0-9]([a-z0-9-]{0,61}[a-z0-9])?$/i.test(part))) {
42
75
  return errors.formatEmailError({ value, pointer });
43
76
  }
44
77
  return undefined;
@@ -56,6 +89,10 @@ export default {
56
89
  if (typeof value !== "string" || value === "") {
57
90
  return undefined;
58
91
  }
92
+ if (value && value[0] === "0") {
93
+ // leading zeroes should be rejected, as they are treated as octals
94
+ return errors.formatIPV4LeadingZeroError({ value, pointer });
95
+ }
59
96
  if (value.length <= 15 && isValidIPV4.test(value)) {
60
97
  return undefined;
61
98
  }
@@ -65,25 +102,94 @@ export default {
65
102
  if (typeof value !== "string" || value === "") {
66
103
  return undefined;
67
104
  }
105
+ if (value && value[0] === "0") {
106
+ // leading zeroes should be rejected, as they are treated as octals
107
+ return errors.formatIPV6LeadingZeroError({ value, pointer });
108
+ }
68
109
  if (value.length <= 45 && isValidIPV6.test(value)) {
69
110
  return undefined;
70
111
  }
71
112
  return errors.formatIPV6Error({ value, pointer });
72
113
  },
114
+ "json-pointer": (core, schema, value, pointer) => {
115
+ if (typeof value !== "string" || value === "") {
116
+ return undefined;
117
+ }
118
+ if (isValidJSONPointer.test(value)) {
119
+ return undefined;
120
+ }
121
+ return errors.formatJSONPointerError({ value, pointer });
122
+ },
123
+ "relative-json-pointer": (core, schema, value, pointer) => {
124
+ if (typeof value !== "string" || value === "") {
125
+ return undefined;
126
+ }
127
+ if (isValidRelativeJSONPointer.test(value)) {
128
+ return undefined;
129
+ }
130
+ return errors.formatJSONPointerError({ value, pointer });
131
+ },
73
132
  regex: (core, schema, value, pointer) => {
74
133
  if (typeof value === "string" && /\\Z$/.test(value) === false) {
134
+ try {
135
+ new RegExp(value);
136
+ return undefined;
137
+ }
138
+ catch (e) { } // eslint-disable-line no-empty
139
+ return errors.formatRegExError({ value, pointer });
140
+ }
141
+ // v7 tests, ignore non-regex values
142
+ if (typeof value === "object" || typeof value === "number" || Array.isArray(value)) {
75
143
  return undefined;
76
144
  }
77
145
  return errors.formatRegExError({ value, pointer });
78
146
  },
79
- uri: (core, schema, value, pointer) => {
147
+ time: (core, schema, value, pointer) => {
80
148
  if (typeof value !== "string") {
81
149
  return undefined;
82
150
  }
83
- if (value === "" || validUrl.isUri(value)) {
151
+ // https://github.com/cfworker/cfworker/blob/main/packages/json-schema/src/format.ts
152
+ const matches = value.match(matchTime);
153
+ if (!matches) {
154
+ return errors.formatDateTimeError({ value, pointer });
155
+ }
156
+ const hour = +matches[1];
157
+ const minute = +matches[2];
158
+ const second = +matches[3];
159
+ const timeZone = !!matches[5];
160
+ if (((hour <= 23 && minute <= 59 && second <= 59) ||
161
+ (hour == 23 && minute == 59 && second == 60)) &&
162
+ timeZone) {
163
+ return undefined;
164
+ }
165
+ return errors.formatTimeError({ value, pointer });
166
+ },
167
+ uri: (core, schema, value, pointer) => {
168
+ if (typeof value !== "string" || value === "") {
169
+ return undefined;
170
+ }
171
+ if (validUrl.isUri(value)) {
172
+ return undefined;
173
+ }
174
+ return errors.formatURIError({ value, pointer });
175
+ },
176
+ "uri-reference": (core, schema, value, pointer) => {
177
+ if (typeof value !== "string" || value === "") {
178
+ return undefined;
179
+ }
180
+ if (isValidURIRef.test(value)) {
181
+ return undefined;
182
+ }
183
+ return errors.formatURIReferenceError({ value, pointer });
184
+ },
185
+ "uri-template": (core, schema, value, pointer) => {
186
+ if (typeof value !== "string" || value === "") {
187
+ return undefined;
188
+ }
189
+ if (isValidURITemplate.test(value)) {
84
190
  return undefined;
85
191
  }
86
- return errors.formatUriError({ value, pointer });
192
+ return errors.formatURITemplateError({ value, pointer });
87
193
  },
88
194
  url: (core, schema, value, pointer) => {
89
195
  if (value === "" || validUrl.isWebUri(value)) {
@@ -92,3 +198,4 @@ export default {
92
198
  return errors.formatUrlError({ value, pointer });
93
199
  }
94
200
  };
201
+ export default formatValidators;
@@ -2,25 +2,29 @@ import getTypeOf from "../getTypeOf";
2
2
  import isSame from "../utils/deepCompare";
3
3
  import settings from "../config/settings";
4
4
  import ucs2decode from "../utils/punycode.ucs2decode";
5
+ import { isJSONError } from "../types";
5
6
  const FPP = settings.floatingPointPrecision;
7
+ const hasOwnProperty = Object.prototype.hasOwnProperty;
8
+ const hasProperty = (value, property) => !(value[property] === undefined || !hasOwnProperty.call(value, property));
6
9
  // list of validation keywords: http://json-schema.org/latest/json-schema-validation.html#rfc.section.5
7
10
  const KeywordValidation = {
8
11
  additionalProperties: (core, schema, value, pointer) => {
9
12
  if (schema.additionalProperties === true || schema.additionalProperties == null) {
10
13
  return undefined;
11
14
  }
12
- if (getTypeOf(schema.patternProperties) === "object" && schema.additionalProperties === false) {
15
+ if (getTypeOf(schema.patternProperties) === "object" &&
16
+ schema.additionalProperties === false) {
13
17
  // this is an arrangement with patternProperties. patternProperties validate before additionalProperties:
14
18
  // https://spacetelescope.github.io/understanding-json-schema/reference/object.html#index-5
15
19
  return undefined;
16
20
  }
17
21
  const errors = [];
18
- let receivedProperties = Object.keys(value).filter(prop => settings.propertyBlacklist.includes(prop) === false);
22
+ let receivedProperties = Object.keys(value).filter((prop) => settings.propertyBlacklist.includes(prop) === false);
19
23
  const expectedProperties = Object.keys(schema.properties || {});
20
24
  if (getTypeOf(schema.patternProperties) === "object") {
21
25
  // filter received properties by matching patternProperties
22
- const patterns = Object.keys(schema.patternProperties).map(pattern => new RegExp(pattern));
23
- receivedProperties = receivedProperties.filter(prop => {
26
+ const patterns = Object.keys(schema.patternProperties).map((pattern) => new RegExp(pattern));
27
+ receivedProperties = receivedProperties.filter((prop) => {
24
28
  for (let i = 0; i < patterns.length; i += 1) {
25
29
  if (patterns[i].test(prop)) {
26
30
  return false; // remove
@@ -37,7 +41,7 @@ const KeywordValidation = {
37
41
  // additionalProperties { oneOf: [] }
38
42
  if (isObject && Array.isArray(schema.additionalProperties.oneOf)) {
39
43
  const result = core.resolveOneOf(value[property], schema.additionalProperties, `${pointer}/${property}`);
40
- if (result.type === "error") {
44
+ if (isJSONError(result)) {
41
45
  errors.push(core.errors.additionalPropertiesError({
42
46
  schema: schema.additionalProperties,
43
47
  property: receivedProperties[i],
@@ -48,12 +52,13 @@ const KeywordValidation = {
48
52
  }));
49
53
  }
50
54
  else {
51
- errors.push(core.validate(value[property], result, pointer));
55
+ errors.push(...core.validate(value[property], result, pointer));
52
56
  }
53
57
  // additionalProperties {}
54
58
  }
55
59
  else if (isObject) {
56
- if (core.validate(value[property], schema.additionalProperties, pointer).length !== 0) {
60
+ if (core.validate(value[property], schema.additionalProperties, pointer)
61
+ .length !== 0) {
57
62
  errors.push(core.errors.additionalPropertiesError({
58
63
  schema: schema.additionalProperties,
59
64
  property: receivedProperties[i],
@@ -63,7 +68,11 @@ const KeywordValidation = {
63
68
  }
64
69
  }
65
70
  else {
66
- errors.push(core.errors.noAdditionalPropertiesError({ property: receivedProperties[i], properties: expectedProperties, pointer }));
71
+ errors.push(core.errors.noAdditionalPropertiesError({
72
+ property: receivedProperties[i],
73
+ properties: expectedProperties,
74
+ pointer
75
+ }));
67
76
  }
68
77
  }
69
78
  }
@@ -74,8 +83,8 @@ const KeywordValidation = {
74
83
  return undefined;
75
84
  }
76
85
  const errors = [];
77
- schema.allOf.forEach(subSchema => {
78
- errors.push(core.validate(value, subSchema, pointer));
86
+ schema.allOf.forEach((subSchema) => {
87
+ errors.push(...core.validate(value, subSchema, pointer));
79
88
  });
80
89
  return errors;
81
90
  },
@@ -95,17 +104,24 @@ const KeywordValidation = {
95
104
  return undefined;
96
105
  }
97
106
  const errors = [];
98
- Object.keys(value)
99
- .forEach(property => {
107
+ Object.keys(value).forEach((property) => {
100
108
  if (schema.dependencies[property] === undefined) {
101
109
  return;
102
110
  }
111
+ // @draft >= 6 boolean schema
112
+ if (schema.dependencies[property] === true) {
113
+ return;
114
+ }
115
+ if (schema.dependencies[property] === false) {
116
+ errors.push(core.errors.missingDependencyError({ pointer }));
117
+ return;
118
+ }
103
119
  let dependencyErrors;
104
120
  const type = getTypeOf(schema.dependencies[property]);
105
121
  if (type === "array") {
106
122
  dependencyErrors = schema.dependencies[property]
107
- .filter(dependency => value[dependency] === undefined)
108
- .map(missingProperty => core.errors.missingDependencyError({ missingProperty, pointer }));
123
+ .filter((dependency) => value[dependency] === undefined)
124
+ .map((missingProperty) => core.errors.missingDependencyError({ missingProperty, pointer }));
109
125
  }
110
126
  else if (type === "object") {
111
127
  dependencyErrors = core.validate(value, schema.dependencies[property]);
@@ -117,7 +133,7 @@ const KeywordValidation = {
117
133
  });
118
134
  return errors.length > 0 ? errors : undefined;
119
135
  },
120
- "enum": (core, schema, value, pointer) => {
136
+ enum: (core, schema, value, pointer) => {
121
137
  const type = getTypeOf(value);
122
138
  if (type === "object" || type === "array") {
123
139
  const valueStr = JSON.stringify(value);
@@ -141,12 +157,19 @@ const KeywordValidation = {
141
157
  return undefined;
142
158
  },
143
159
  items: (core, schema, value, pointer) => {
160
+ // @draft >= 7 bool schema
161
+ if (schema.items === false) {
162
+ if (Array.isArray(value) && value.length === 0) {
163
+ return undefined;
164
+ }
165
+ return core.errors.invalidDataError({ pointer, value });
166
+ }
144
167
  const errors = [];
145
168
  for (let i = 0; i < value.length; i += 1) {
146
169
  const itemData = value[i];
147
170
  // @todo reevaluate: incomplete schema is created here
148
171
  const itemSchema = core.step(i, schema, value, pointer);
149
- if (itemSchema && itemSchema.type === "error") {
172
+ if (isJSONError(itemSchema)) {
150
173
  return [itemSchema];
151
174
  }
152
175
  const itemErrors = core.validate(itemData, itemSchema, `${pointer}/${i}`);
@@ -171,7 +194,11 @@ const KeywordValidation = {
171
194
  return undefined;
172
195
  }
173
196
  if (schema.maxItems < value.length) {
174
- return core.errors.maxItemsError({ maximum: schema.maxItems, length: value.length, pointer });
197
+ return core.errors.maxItemsError({
198
+ maximum: schema.maxItems,
199
+ length: value.length,
200
+ pointer
201
+ });
175
202
  }
176
203
  return undefined;
177
204
  },
@@ -181,7 +208,11 @@ const KeywordValidation = {
181
208
  }
182
209
  const lengthOfString = ucs2decode(value).length;
183
210
  if (schema.maxLength < lengthOfString) {
184
- return core.errors.maxLengthError({ maxLength: schema.maxLength, length: lengthOfString, pointer });
211
+ return core.errors.maxLengthError({
212
+ maxLength: schema.maxLength,
213
+ length: lengthOfString,
214
+ pointer
215
+ });
185
216
  }
186
217
  return undefined;
187
218
  },
@@ -202,7 +233,11 @@ const KeywordValidation = {
202
233
  }
203
234
  const lengthOfString = ucs2decode(value).length;
204
235
  if (schema.minLength > lengthOfString) {
205
- return core.errors.minLengthError({ minLength: schema.minLength, length: lengthOfString, pointer });
236
+ return core.errors.minLengthError({
237
+ minLength: schema.minLength,
238
+ length: lengthOfString,
239
+ pointer
240
+ });
206
241
  }
207
242
  return undefined;
208
243
  },
@@ -223,7 +258,11 @@ const KeywordValidation = {
223
258
  return undefined;
224
259
  }
225
260
  if (schema.minItems > value.length) {
226
- return core.errors.minItemsError({ minItems: schema.minItems, length: value.length, pointer });
261
+ return core.errors.minItemsError({
262
+ minItems: schema.minItems,
263
+ length: value.length,
264
+ pointer
265
+ });
227
266
  }
228
267
  return undefined;
229
268
  },
@@ -235,7 +274,8 @@ const KeywordValidation = {
235
274
  if (schema.minProperties > propertyCount) {
236
275
  return core.errors.minPropertiesError({
237
276
  minProperties: schema.minProperties,
238
- length: propertyCount, pointer
277
+ length: propertyCount,
278
+ pointer
239
279
  });
240
280
  }
241
281
  return undefined;
@@ -246,9 +286,10 @@ const KeywordValidation = {
246
286
  }
247
287
  // https://github.com/cfworker/cfworker/blob/master/packages/json-schema/src/validate.ts#L1061
248
288
  // https://github.com/ExodusMovement/schemasafe/blob/master/src/compile.js#L441
249
- if ((value * FPP) % (schema.multipleOf * FPP) / FPP !== 0) {
289
+ if (((value * FPP) % (schema.multipleOf * FPP)) / FPP !== 0) {
250
290
  return core.errors.multipleOfError({ multipleOf: schema.multipleOf, value, pointer });
251
291
  }
292
+ // also check https://stackoverflow.com/questions/1815367/catch-and-compute-overflow-during-multiplication-of-two-large-integers
252
293
  return undefined;
253
294
  },
254
295
  not: (core, schema, value, pointer) => {
@@ -263,7 +304,7 @@ const KeywordValidation = {
263
304
  return undefined;
264
305
  }
265
306
  schema = core.resolveOneOf(value, schema, pointer);
266
- if (schema && schema.type === "error") {
307
+ if (isJSONError(schema)) {
267
308
  return schema;
268
309
  }
269
310
  return undefined;
@@ -274,7 +315,8 @@ const KeywordValidation = {
274
315
  return core.errors.patternError({
275
316
  pattern: schema.pattern,
276
317
  description: schema.patternExample || schema.pattern,
277
- received: value, pointer
318
+ received: value,
319
+ pointer
278
320
  });
279
321
  }
280
322
  return undefined;
@@ -287,11 +329,11 @@ const KeywordValidation = {
287
329
  }
288
330
  const errors = [];
289
331
  const keys = Object.keys(value);
290
- const patterns = Object.keys(pp).map(expr => ({
332
+ const patterns = Object.keys(pp).map((expr) => ({
291
333
  regex: new RegExp(expr),
292
334
  patternSchema: pp[expr]
293
335
  }));
294
- keys.forEach(key => {
336
+ keys.forEach((key) => {
295
337
  let patternFound = false;
296
338
  for (let i = 0, l = patterns.length; i < l; i += 1) {
297
339
  if (patterns[i].regex.test(key)) {
@@ -308,7 +350,9 @@ const KeywordValidation = {
308
350
  if (patternFound === false && schema.additionalProperties === false) {
309
351
  // this is an arrangement with additionalProperties
310
352
  errors.push(core.errors.patternPropertiesError({
311
- key, pointer, patterns: Object.keys(pp).join(",")
353
+ key,
354
+ pointer,
355
+ patterns: Object.keys(pp).join(",")
312
356
  }));
313
357
  }
314
358
  });
@@ -319,7 +363,7 @@ const KeywordValidation = {
319
363
  const keys = Object.keys(schema.properties || {});
320
364
  for (let i = 0; i < keys.length; i += 1) {
321
365
  const key = keys[i];
322
- if (value[key] !== undefined) {
366
+ if (hasProperty(value, key)) {
323
367
  const itemSchema = core.step(key, schema, value, pointer);
324
368
  const keyErrors = core.validate(value[key], itemSchema, `${pointer}/${key}`);
325
369
  errors.push(...keyErrors);
@@ -348,8 +392,8 @@ const KeywordValidation = {
348
392
  if (Array.isArray(schema.required) === false) {
349
393
  return undefined;
350
394
  }
351
- return schema.required.map(property => {
352
- if (value[property] === undefined) {
395
+ return schema.required.map((property) => {
396
+ if (!hasProperty(value, property)) {
353
397
  return core.errors.requiredPropertyError({ key: property, pointer });
354
398
  }
355
399
  return undefined;
@@ -360,9 +404,12 @@ const KeywordValidation = {
360
404
  if (Array.isArray(schema.required) === false) {
361
405
  return undefined;
362
406
  }
363
- return schema.required.map(property => {
407
+ return schema.required.map((property) => {
364
408
  if (value[property] == null || value[property] === "") {
365
- return core.errors.valueNotEmptyError({ property, pointer: `${pointer}/${property}` });
409
+ return core.errors.valueNotEmptyError({
410
+ property,
411
+ pointer: `${pointer}/${property}`
412
+ });
366
413
  }
367
414
  return undefined;
368
415
  });