json-schema-library 11.3.1 → 11.4.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 (54) hide show
  1. package/CHANGELOG.md +21 -2
  2. package/README.md +44 -35
  3. package/dist/chunk-350yNsax.cjs +1 -0
  4. package/dist/formats.cjs +2 -0
  5. package/dist/formats.cjs.map +1 -0
  6. package/dist/formats.d.cts +8 -0
  7. package/dist/formats.d.mts +8 -0
  8. package/dist/formats.mjs +2 -0
  9. package/dist/formats.mjs.map +1 -0
  10. package/dist/index.cjs +2 -1
  11. package/dist/index.cjs.map +1 -0
  12. package/dist/index.d.cts +4 -4
  13. package/dist/index.d.mts +4 -4
  14. package/dist/index.mjs +2 -1
  15. package/dist/index.mjs.map +1 -0
  16. package/dist/jlib.js +2 -2
  17. package/dist/jlibFormats.iife.js +1 -0
  18. package/dist/jlibRemote.iife.js +1 -0
  19. package/dist/{remotes/index.cjs → remotes.cjs} +2 -1
  20. package/dist/remotes.cjs.map +1 -0
  21. package/dist/remotes.d.cts +8 -0
  22. package/dist/remotes.d.mts +8 -0
  23. package/dist/{remotes/index.mjs → remotes.mjs} +2 -1
  24. package/dist/remotes.mjs.map +1 -0
  25. package/dist/{types-DVyFDxCv.d.mts → types-BDjKcTVR.d.cts} +4 -2
  26. package/dist/{types-ZgoQMSny.d.cts → types-CqkCJmt8.d.mts} +4 -2
  27. package/package.json +21 -15
  28. package/src/SchemaNode.ts +12 -18
  29. package/src/compileSchema.ts +0 -3
  30. package/src/errors/errors.ts +3 -2
  31. package/src/formats/additionalFormats.ts +118 -0
  32. package/src/formats/formats.ts +3 -111
  33. package/src/keywords/$defs.ts +3 -0
  34. package/src/keywords/additionalProperties.ts +1 -0
  35. package/src/keywords/allOf.ts +2 -5
  36. package/src/keywords/anyOf.ts +2 -4
  37. package/src/keywords/dependencies.ts +2 -0
  38. package/src/keywords/dependentSchemas.ts +2 -3
  39. package/src/keywords/format.ts +8 -0
  40. package/src/keywords/ifthenelse.ts +4 -9
  41. package/src/keywords/oneOf.ts +2 -5
  42. package/src/keywords/patternProperties.ts +5 -1
  43. package/src/keywords/prefixItems.ts +14 -9
  44. package/src/keywords/properties.ts +2 -3
  45. package/src/keywords/propertyDependencies.ts +2 -3
  46. package/src/keywords/propertyNames.ts +1 -1
  47. package/src/utils/collectValidationErrors.ts +9 -0
  48. package/src/validateSchema.test.ts +29 -30
  49. package/tsconfig.json +1 -0
  50. package/tsconfig.test.json +1 -0
  51. package/tsdown.config.ts +5 -2
  52. package/tsdown.iife.config.ts +29 -8
  53. package/dist/remotes/index.d.cts +0 -7
  54. package/dist/remotes/index.d.mts +0 -7
@@ -8,6 +8,7 @@ import {
8
8
  } from "../Keyword";
9
9
  import { SchemaNode } from "../types";
10
10
  import { validateNode } from "../validateNode";
11
+ import { collectValidationErrors } from "src/utils/collectValidationErrors";
11
12
 
12
13
  const KEYWORD = "allOf";
13
14
 
@@ -41,11 +42,7 @@ export function parseAllOf(node: SchemaNode) {
41
42
  node[KEYWORD] = schema[KEYWORD].map((s, index) =>
42
43
  node.compileSchema(s, `${evaluationPath}/${KEYWORD}/${index}`, `${schemaLocation}/${KEYWORD}/${index}`)
43
44
  );
44
-
45
- return node[KEYWORD].reduce((errors, node) => {
46
- if (node.schemaValidation) errors.push(...node.schemaValidation);
47
- return errors;
48
- }, [] as ValidationAnnotation[]);
45
+ return collectValidationErrors([], ...node[KEYWORD]);
49
46
  }
50
47
 
51
48
  function reduceAllOf({ node, data, key, pointer, path }: JsonSchemaReducerParams) {
@@ -2,6 +2,7 @@ import { mergeSchema } from "../utils/mergeSchema";
2
2
  import { Keyword, JsonSchemaReducerParams, JsonSchemaValidatorParams, ValidationAnnotation } from "../Keyword";
3
3
  import { SchemaNode } from "../types";
4
4
  import { validateNode } from "../validateNode";
5
+ import { collectValidationErrors } from "src/utils/collectValidationErrors";
5
6
 
6
7
  const KEYWORD = "anyOf";
7
8
 
@@ -35,10 +36,7 @@ export function parseAnyOf(node: SchemaNode) {
35
36
  node.compileSchema(s, `${evaluationPath}/${KEYWORD}/${index}`, `${schemaLocation}/${KEYWORD}/${index}`)
36
37
  );
37
38
 
38
- return node[KEYWORD].reduce((errors, node) => {
39
- if (node.schemaValidation) errors.push(...node.schemaValidation);
40
- return errors;
41
- }, [] as ValidationAnnotation[]);
39
+ return collectValidationErrors([], ...node[KEYWORD]);
42
40
  }
43
41
 
44
42
  function reduceAnyOf({ node, data, pointer, path }: JsonSchemaReducerParams) {
@@ -7,6 +7,7 @@ import { validateDependentRequired } from "./dependentRequired";
7
7
  import { validateDependentSchemas } from "./dependentSchemas";
8
8
  import sanitizeErrors from "../utils/sanitizeErrors";
9
9
  import { isListOfStrings } from "../utils/isListOfStrings";
10
+ import { collectValidationErrors } from "src/utils/collectValidationErrors";
10
11
 
11
12
  const KEYWORD = "dependencies";
12
13
 
@@ -42,6 +43,7 @@ export function parseDependencies(node: SchemaNode) {
42
43
  `${node.evaluationPath}/${KEYWORD}/${property}`,
43
44
  `${node.schemaLocation}/${KEYWORD}/${property}`
44
45
  );
46
+ collectValidationErrors(errors, node.dependentSchemas[property]);
45
47
  } else if (isListOfStrings(schema)) {
46
48
  node.dependentRequired = node.dependentRequired ?? {};
47
49
  node.dependentRequired[property] = schema;
@@ -4,6 +4,7 @@ import { isSchemaNode, SchemaNode, JsonSchema, isBooleanSchema } from "../types"
4
4
  import { Keyword, JsonSchemaReducerParams, JsonSchemaValidatorParams, ValidationAnnotation } from "../Keyword";
5
5
  import { validateNode } from "../validateNode";
6
6
  import sanitizeErrors from "../utils/sanitizeErrors";
7
+ import { collectValidationErrors } from "src/utils/collectValidationErrors";
7
8
 
8
9
  const KEYWORD = "dependentSchemas";
9
10
 
@@ -46,9 +47,7 @@ export function parseDependentSchemas(node: SchemaNode) {
46
47
  `${node.evaluationPath}/${KEYWORD}/${property}`,
47
48
  `${node.schemaLocation}/${KEYWORD}/${property}`
48
49
  );
49
- if (parsedSchemas[property].schemaValidation) {
50
- errors.push(...parsedSchemas[property].schemaValidation);
51
- }
50
+ collectValidationErrors(errors, parsedSchemas[property]);
52
51
  } else if (isBooleanSchema(schema)) {
53
52
  parsedSchemas[property] = schema;
54
53
  } else {
@@ -24,6 +24,14 @@ function parseFormat(node: SchemaNode) {
24
24
  message: `Keyword '${KEYWORD}' must be a string - received '${typeof format}'`
25
25
  });
26
26
  }
27
+ if (node.context.formats[format] == null) {
28
+ return node.createAnnotation("unknown-format-warning", {
29
+ pointer: `${node.schemaLocation}/${KEYWORD}`,
30
+ schema: node.schema,
31
+ value: format,
32
+ message: `Keyword '${KEYWORD}' must be a string - received '${typeof format}'`
33
+ });
34
+ }
27
35
  }
28
36
 
29
37
  function validateFormat(options: JsonSchemaValidatorParams) {
@@ -2,6 +2,7 @@ import { mergeSchema } from "../utils/mergeSchema";
2
2
  import { Keyword, JsonSchemaReducerParams, JsonSchemaValidatorParams, ValidationAnnotation } from "../Keyword";
3
3
  import { isBooleanSchema, isJsonSchema, SchemaNode } from "../types";
4
4
  import { validateNode } from "../validateNode";
5
+ import { collectValidationErrors } from "src/utils/collectValidationErrors";
5
6
 
6
7
  export const ifKeyword: Keyword = {
7
8
  id: "if-then-else",
@@ -19,9 +20,7 @@ export function parseIfThenElse(node: SchemaNode) {
19
20
  if (schema.if != null) {
20
21
  if (isJsonSchema(schema.if) || isBooleanSchema(schema.if)) {
21
22
  node.if = node.compileSchema(schema.if, `${evaluationPath}/if`);
22
- if (node.if.schemaValidation) {
23
- errors.push(...node.if.schemaValidation);
24
- }
23
+ collectValidationErrors(errors, node.if);
25
24
  } else {
26
25
  errors.push(
27
26
  node.createError("schema-error", {
@@ -36,9 +35,7 @@ export function parseIfThenElse(node: SchemaNode) {
36
35
  if (schema.then != null) {
37
36
  if (isJsonSchema(schema.then) || isBooleanSchema(schema.then)) {
38
37
  node.then = node.compileSchema(schema.then, `${evaluationPath}/then`);
39
- if (node.then.schemaValidation) {
40
- errors.push(...node.then.schemaValidation);
41
- }
38
+ collectValidationErrors(errors, node.then);
42
39
  } else {
43
40
  errors.push(
44
41
  node.createError("schema-error", {
@@ -53,9 +50,7 @@ export function parseIfThenElse(node: SchemaNode) {
53
50
  if (schema.else != null) {
54
51
  if (isJsonSchema(schema.else) || isBooleanSchema(schema.else)) {
55
52
  node.else = node.compileSchema(schema.else, `${evaluationPath}/else`);
56
- if (node.else.schemaValidation) {
57
- errors.push(...node.else.schemaValidation);
58
- }
53
+ collectValidationErrors(errors, node.else);
59
54
  } else {
60
55
  errors.push(
61
56
  node.createError("schema-error", {
@@ -13,6 +13,7 @@ import sanitizeErrors from "../utils/sanitizeErrors";
13
13
  import { isObject } from "../utils/isObject";
14
14
  import { validateNode } from "../validateNode";
15
15
  import { joinDynamicId } from "../SchemaNode";
16
+ import { collectValidationErrors } from "src/utils/collectValidationErrors";
16
17
 
17
18
  const KEYWORD = "oneOf";
18
19
  const { DECLARATOR_ONEOF } = settings;
@@ -57,11 +58,7 @@ export function parseOneOf(node: SchemaNode) {
57
58
  node[KEYWORD] = schema[KEYWORD].map((s, index) =>
58
59
  node.compileSchema(s, `${evaluationPath}/${KEYWORD}/${index}`, `${schemaLocation}/${KEYWORD}/${index}`)
59
60
  );
60
-
61
- return node[KEYWORD].reduce((errors, node) => {
62
- if (node.schemaValidation) errors.push(...node.schemaValidation);
63
- return errors;
64
- }, [] as ValidationAnnotation[]);
61
+ return collectValidationErrors([], ...node[KEYWORD]);
65
62
  }
66
63
 
67
64
  function reduceOneOf({ node, data, pointer, path }: Omit<JsonSchemaReducerParams, "key">) {
@@ -6,11 +6,13 @@ import {
6
6
  JsonSchemaReducerParams,
7
7
  JsonSchemaResolverParams,
8
8
  JsonSchemaValidatorParams,
9
- ValidationReturnType
9
+ ValidationReturnType,
10
+ ValidationAnnotation
10
11
  } from "../Keyword";
11
12
  import { getValue } from "../utils/getValue";
12
13
  import { validateNode } from "../validateNode";
13
14
  import settings from "../settings";
15
+ import { collectValidationErrors } from "src/utils/collectValidationErrors";
14
16
 
15
17
  const { REGEX_FLAGS } = settings;
16
18
 
@@ -45,6 +47,8 @@ export function parsePatternProperties(node: SchemaNode) {
45
47
  `${node.schemaLocation}/patternProperties/${pattern}`
46
48
  )
47
49
  }));
50
+
51
+ return collectValidationErrors([], ...node.patternProperties.map(({ node }) => node));
48
52
  }
49
53
 
50
54
  function patternPropertyResolver({ node, key }: JsonSchemaResolverParams) {
@@ -1,14 +1,17 @@
1
1
  import { SchemaNode } from "../types";
2
2
  import { Keyword, JsonSchemaResolverParams, JsonSchemaValidatorParams, ValidationReturnType } from "../Keyword";
3
3
  import { validateNode } from "../validateNode";
4
+ import { collectValidationErrors } from "src/utils/collectValidationErrors";
5
+
6
+ const KEYWORD = "prefixItems";
4
7
 
5
8
  export const prefixItemsKeyword: Keyword = {
6
- id: "prefixItems",
7
- keyword: "prefixItems",
9
+ id: KEYWORD,
10
+ keyword: KEYWORD,
8
11
  parse: parseItems,
9
- addResolve: (node) => node.prefixItems != null,
12
+ addResolve: (node) => node[KEYWORD] != null,
10
13
  resolve: prefixItemsResolver,
11
- addValidate: ({ schema }) => schema.prefixItems != null,
14
+ addValidate: (node) => node[KEYWORD] != null,
12
15
  validate: validatePrefixItems
13
16
  };
14
17
 
@@ -24,10 +27,11 @@ export function parseItems(node: SchemaNode) {
24
27
  node.prefixItems = schema.prefixItems.map((itemSchema, index) =>
25
28
  node.compileSchema(
26
29
  itemSchema,
27
- `${evaluationPath}/prefixItems/${index}`,
28
- `${node.schemaLocation}/prefixItems/${index}`
30
+ `${evaluationPath}/${KEYWORD}/${index}`,
31
+ `${node.schemaLocation}/${KEYWORD}/${index}`
29
32
  )
30
33
  );
34
+ return collectValidationErrors([], ...node.prefixItems);
31
35
  }
32
36
  }
33
37
 
@@ -37,12 +41,13 @@ function validatePrefixItems({ node, data, pointer = "#", path }: JsonSchemaVali
37
41
  }
38
42
 
39
43
  const errors: ValidationReturnType = [];
40
- if (node.prefixItems) {
44
+ const prefixItems = node[KEYWORD];
45
+ if (prefixItems) {
41
46
  // note: schema is valid when data does not have enough elements as defined by array-list
42
- for (let i = 0; i < Math.min(node.prefixItems.length, data.length); i += 1) {
47
+ for (let i = 0; i < Math.min(prefixItems.length, data.length); i += 1) {
43
48
  const itemData = data[i];
44
49
  // @todo v1 reevaluate: incomplete schema is created here?
45
- const itemNode = node.prefixItems[i];
50
+ const itemNode = prefixItems[i];
46
51
  const result = validateNode(itemNode, itemData, `${pointer}/${i}`, path);
47
52
  errors.push(...result);
48
53
  }
@@ -9,6 +9,7 @@ import {
9
9
  } from "../Keyword";
10
10
  import { isObject } from "../utils/isObject";
11
11
  import { validateNode } from "../validateNode";
12
+ import { collectValidationErrors } from "src/utils/collectValidationErrors";
12
13
 
13
14
  export const propertiesKeyword: Keyword = {
14
15
  id: "property",
@@ -48,9 +49,7 @@ export function parseProperties(node: SchemaNode) {
48
49
  `${schemaLocation}/properties/${propertyName}`
49
50
  );
50
51
  parsedProperties[propertyName] = propertyNode;
51
- if (parsedProperties[propertyName].schemaValidation) {
52
- errors.push(...parsedProperties[propertyName].schemaValidation);
53
- }
52
+ collectValidationErrors(errors, parsedProperties[propertyName]);
54
53
  });
55
54
  node.properties = parsedProperties;
56
55
 
@@ -1,3 +1,4 @@
1
+ import { collectValidationErrors } from "src/utils/collectValidationErrors";
1
2
  import {
2
3
  Keyword,
3
4
  JsonSchemaValidatorParams,
@@ -115,9 +116,7 @@ function parsePropertyDependencies(node: SchemaNode) {
115
116
  `${node.evaluationPath}/${KEYWORD}/${propertyName}/${value}`,
116
117
  `${node.schemaLocation}/${KEYWORD}/${propertyName}/${value}`
117
118
  );
118
- if (parsed[propertyName][value].schemaValidation) {
119
- errors.push(...parsed[propertyName][value].schemaValidation);
120
- }
119
+ collectValidationErrors(errors, parsed[propertyName][value]);
121
120
  });
122
121
  });
123
122
  node[KEYWORD] = parsed;
@@ -35,7 +35,7 @@ export function parsePropertyNames(node: SchemaNode) {
35
35
  `${node.evaluationPath}/propertyNames`,
36
36
  `${node.schemaLocation}/propertyNames`
37
37
  );
38
- return node.schemaValidation;
38
+ return node.propertyNames.schemaValidation;
39
39
  }
40
40
 
41
41
  function validatePropertyNames({ node, data, pointer, path }: JsonSchemaValidatorParams) {
@@ -0,0 +1,9 @@
1
+ import { ValidationAnnotation } from "src/Keyword";
2
+ import { SchemaNode } from "src/SchemaNode";
3
+
4
+ export function collectValidationErrors(errors: ValidationAnnotation[], ...compiled: SchemaNode[]) {
5
+ return compiled.reduce((errors, node) => {
6
+ if (node.schemaValidation) errors.push(...node.schemaValidation);
7
+ return errors;
8
+ }, errors);
9
+ }
@@ -252,45 +252,36 @@ describe("validateSchema", () => {
252
252
 
253
253
  describe("annotations", () => {
254
254
  it("should return unknown keywords as annotation", () => {
255
- const { schemaAnnotations } = compileSchema(
256
- {
257
- properties: {
258
- headline: {
259
- options: {},
260
- type: "string"
261
- }
255
+ const { schemaAnnotations } = compileSchema({
256
+ properties: {
257
+ headline: {
258
+ options: {},
259
+ type: "string"
262
260
  }
263
- },
264
- { withSchemaAnnotations: true }
265
- );
261
+ }
262
+ });
266
263
  assert.equal(schemaAnnotations.length, 1);
267
264
  assert.equal(schemaAnnotations[0].data.pointer, "#/properties/headline/options");
268
265
  });
269
266
  it("should return not unknown keywords starting with 'x-'", () => {
270
- const { schemaAnnotations } = compileSchema(
271
- {
272
- properties: {
273
- headline: {
274
- "x-options": {},
275
- type: "string"
276
- }
267
+ const { schemaAnnotations } = compileSchema({
268
+ properties: {
269
+ headline: {
270
+ "x-options": {},
271
+ type: "string"
277
272
  }
278
- },
279
- { withSchemaAnnotations: true }
280
- );
273
+ }
274
+ });
281
275
  assert.equal(schemaAnnotations.length, 0);
282
276
  });
283
277
  it("should return removed keywords from old drafts as annotation", () => {
284
- const { schemaAnnotations } = compileSchema(
285
- {
286
- properties: {
287
- headline: {
288
- additionalItems: true
289
- }
278
+ const { schemaAnnotations } = compileSchema({
279
+ properties: {
280
+ headline: {
281
+ additionalItems: true
290
282
  }
291
- },
292
- { withSchemaAnnotations: true }
293
- );
283
+ }
284
+ });
294
285
  assert.equal(schemaAnnotations.length, 1);
295
286
  assert.equal(schemaAnnotations[0].data.pointer, "#/properties/headline/additionalItems");
296
287
  });
@@ -303,10 +294,18 @@ describe("validateSchema", () => {
303
294
  }
304
295
  }
305
296
  },
306
- { drafts: [draft07], withSchemaAnnotations: true }
297
+ { drafts: [draft07] }
307
298
  );
308
299
  assert.equal(schemaAnnotations.length, 1);
309
300
  assert.equal(schemaAnnotations[0].data.pointer, "#/properties/headline/prefixItems");
310
301
  });
302
+ it("should return unsupported formats as annotation", () => {
303
+ const { schemaAnnotations } = compileSchema({
304
+ type: "array",
305
+ prefixItems: [{ format: "email" }, { format: "textarea" }]
306
+ });
307
+ assert.equal(schemaAnnotations.length, 1);
308
+ assert.equal(schemaAnnotations[0].data.pointer, "#/prefixItems/1/format");
309
+ });
311
310
  });
312
311
  });
package/tsconfig.json CHANGED
@@ -13,6 +13,7 @@
13
13
  "baseUrl": ".",
14
14
  "paths": {
15
15
  "json-schema-library": ["./index.ts"],
16
+ "json-schema-library/formats": ["./src/formats/additionalFormats.ts"],
16
17
  "json-schema-library/remotes": ["./remotes/index.ts"],
17
18
  "json-schema-library/package.json": ["./package.json"]
18
19
  },
@@ -13,6 +13,7 @@
13
13
  "baseUrl": ".",
14
14
  "paths": {
15
15
  "json-schema-library": ["./index.ts"],
16
+ "json-schema-library/formats": ["./src/formats/additionalformats.ts"],
16
17
  "json-schema-library/remotes": ["./remotes/index.ts"],
17
18
  "json-schema-library/package.json": ["./package.json"]
18
19
  }
package/tsdown.config.ts CHANGED
@@ -2,7 +2,10 @@ import { defineConfig } from "tsdown";
2
2
 
3
3
  export default defineConfig({
4
4
  dts: true,
5
- entry: ["./index.ts", "./remotes/index.ts"],
5
+ entry: { index: "./index.ts", remotes: "./remotes/index.ts", formats: "./src/formats/additionalFormats.ts" },
6
6
  exports: true,
7
- globalName: "jlib"
7
+ globalName: "jlib",
8
+ sourcemap: true,
9
+ // Only bundle @hyperjump/json-schema-formats (used only in formats entry)
10
+ noExternal: ["@hyperjump/json-schema-formats"]
8
11
  });
@@ -1,10 +1,31 @@
1
1
  import { defineConfig } from "tsdown";
2
2
 
3
- export default defineConfig({
4
- entry: ["./index.ts"],
5
- format: "iife",
6
- globalName: "jlib",
7
- platform: "browser",
8
- // Bundle anything that's not a relative/absolute path (=> node_modules)
9
- noExternal: (id) => !id.startsWith(".") && !id.startsWith("/")
10
- });
3
+ export default defineConfig([
4
+ {
5
+ entry: { jlib: "./index.ts" },
6
+ format: "iife",
7
+ globalName: "jlib",
8
+ platform: "browser",
9
+ sourcemap: false,
10
+ // Bundle anything that's not a relative/absolute path (=> node_modules)
11
+ noExternal: (id) => !id.startsWith(".") && !id.startsWith("/")
12
+ },
13
+ {
14
+ entry: { jlibRemote: "./remotes/index.ts" },
15
+ format: "iife",
16
+ globalName: "jlibRemotes",
17
+ platform: "browser",
18
+ sourcemap: false,
19
+ // Bundle anything that's not a relative/absolute path (=> node_modules)
20
+ noExternal: (id) => !id.startsWith(".") && !id.startsWith("/")
21
+ },
22
+ {
23
+ entry: { jlibFormats: "./src/formats/additionalFormats.ts" },
24
+ format: "iife",
25
+ globalName: "jlibFormats",
26
+ platform: "browser",
27
+ sourcemap: false,
28
+ // Bundle anything that's not a relative/absolute path (=> node_modules)
29
+ noExternal: (id) => !id.startsWith(".") && !id.startsWith("/")
30
+ }
31
+ ]);
@@ -1,7 +0,0 @@
1
- import { c as JsonSchema } from "../types-ZgoQMSny.cjs";
2
-
3
- //#region remotes/index.d.ts
4
- /** JSON Schema meta-schemata */
5
- declare const remotes: JsonSchema[];
6
- //#endregion
7
- export { remotes };
@@ -1,7 +0,0 @@
1
- import { c as JsonSchema } from "../types-DVyFDxCv.mjs";
2
-
3
- //#region remotes/index.d.ts
4
- /** JSON Schema meta-schemata */
5
- declare const remotes: JsonSchema[];
6
- //#endregion
7
- export { remotes };