json-schema-library 11.4.0 → 11.5.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.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "json-schema-library",
3
- "version": "11.4.0",
3
+ "version": "11.5.0",
4
4
  "description": "Customizable and hackable json-validator and json-schema utilities for traversal, data generation and validation",
5
5
  "types": "./dist/index.d.cts",
6
6
  "exports": {
@@ -18,6 +18,16 @@
18
18
  },
19
19
  "./package.json": "./package.json"
20
20
  },
21
+ "typesVersions": {
22
+ "*": {
23
+ "formats": [
24
+ "./dist/formats.d.cts"
25
+ ],
26
+ "remotes": [
27
+ "./dist/remotes.d.cts"
28
+ ]
29
+ }
30
+ },
21
31
  "scripts": {
22
32
  "coverage": "nyc npm run test --reporter=lcov",
23
33
  "dist": "tsdown -f esm -f cjs --minify --report --attw --unused; pnpm run dist:iife;",
package/src/SchemaNode.ts CHANGED
@@ -41,7 +41,7 @@ import { getNode } from "./getNode";
41
41
  import { getNodeChild } from "./getNodeChild";
42
42
  import { DataNode } from "./methods/toDataNodes";
43
43
 
44
- const { DYNAMIC_PROPERTIES, REGEX_FLAGS, DECLARATOR_ONEOF } = settings;
44
+ const { DYNAMIC_PROPERTIES, REGEX_FLAGS, DECLARATOR_ONEOF, VALID_ANNOTATION_KEYWORDS } = settings;
45
45
 
46
46
  export function isSchemaNode(value: unknown): value is SchemaNode {
47
47
  return isObject(value) && Array.isArray(value?.reducers) && Array.isArray(value?.resolvers);
@@ -624,7 +624,10 @@ export function addKeywords(node: SchemaNode) {
624
624
  .map((keyword) => execKeyword(keyword, node));
625
625
 
626
626
  keys.filter(
627
- (key) => !key.startsWith("x-") && node.context.keywords.find((keyword) => keyword.keyword === key) == null
627
+ (key) =>
628
+ !key.startsWith("x-") &&
629
+ !VALID_ANNOTATION_KEYWORDS.includes(key) &&
630
+ node.context.keywords.find((keyword) => keyword.keyword === key) == null
628
631
  ).forEach((keyword) => {
629
632
  errors.push(
630
633
  node.createAnnotation("unknown-keyword-warning", {
@@ -84,6 +84,30 @@ describe("compileSchema draft-version", () => {
84
84
  });
85
85
  });
86
86
 
87
+ describe("compileSchema remotes", () => {
88
+ it("should add list of remotes to returned SchemaNode node", () => {
89
+ const node = compileSchema(
90
+ {
91
+ type: "object",
92
+ required: ["string", "number"],
93
+ properties: {
94
+ string: { $ref: "https://remote-a.com/schema" },
95
+ number: { $ref: "https://remote-b.com/schema" }
96
+ }
97
+ },
98
+ {
99
+ drafts: [draft2020],
100
+ remotes: [
101
+ { $id: "https://remote-a.com/schema", type: "string", default: "a" },
102
+ { $id: "https://remote-b.com/schema", type: "number", default: 9 }
103
+ ]
104
+ }
105
+ );
106
+ const data = node.getData();
107
+ assert.deepEqual(data, { string: "a", number: 9 });
108
+ });
109
+ });
110
+
87
111
  describe("compileSchema vocabulary", () => {
88
112
  it("should add remote schema on compile", () => {
89
113
  const remote = compileSchema({
@@ -67,6 +67,7 @@ export type CompileOptions = {
67
67
  * Set node and its remote schemata as remote schemata for this node and schema to resolve $ref
68
68
  */
69
69
  remote?: SchemaNode;
70
+ remotes?: JsonSchema[];
70
71
  /**
71
72
  * Enables `format`-keyword assertions when this is set tor `true` or sets assertion as defined by
72
73
  * the given meta-schema. Set to `false` to deactivate format validation.
@@ -94,6 +95,18 @@ function getDraft(drafts: Draft[], $schema: string) {
94
95
  * node will be reused for each task, but will create a compiledNode for bound data.
95
96
  */
96
97
  export function compileSchema(schema: JsonSchema | BooleanSchema, options: CompileOptions = {}) {
98
+ let remote = options.remote;
99
+ if (Array.isArray(options.remotes) && options.remotes.length > 0 && !options.remote) {
100
+ const remotes = [...options.remotes];
101
+ remotes.forEach((remote, index) => {
102
+ if (remote.$id == null) {
103
+ throw new Error(`required $id on remotes[${index}] is missing`);
104
+ }
105
+ });
106
+ remote = compileSchema(remotes.shift()!);
107
+ remotes.forEach((r) => remote?.addRemoteSchema(r.$id, r));
108
+ }
109
+
97
110
  let formatAssertion = options.formatAssertion ?? true;
98
111
  const drafts = options.drafts ?? defaultDrafts;
99
112
  const draft = getDraft(drafts, isJsonSchema(schema) ? (options.draft ?? schema.$schema) : undefined);
@@ -110,7 +123,7 @@ export function compileSchema(schema: JsonSchema | BooleanSchema, options: Compi
110
123
  context: {
111
124
  remotes: {},
112
125
  dynamicAnchors: {},
113
- ...(options.remote?.context ?? {}),
126
+ ...(remote?.context ?? {}),
114
127
  anchors: {},
115
128
  refs: {},
116
129
  ...copy(pick(draft, "methods", "keywords", "version", "formats", "errors")),
@@ -125,7 +138,7 @@ export function compileSchema(schema: JsonSchema | BooleanSchema, options: Compi
125
138
  node.context.rootNode = node;
126
139
  node.context.remotes[(isJsonSchema(schema) ? schema.$id : undefined) ?? "#"] = node;
127
140
 
128
- if (options.remote) {
141
+ if (remote) {
129
142
  const metaSchema = getRef(node, node.schema.$schema);
130
143
  if (isSchemaNode(metaSchema) && metaSchema.schema.$vocabulary) {
131
144
  const vocabs = Object.keys(metaSchema.schema.$vocabulary);
@@ -4,6 +4,7 @@ import { oneOfFuzzyKeyword } from "./keywords/oneOf";
4
4
  import { render } from "./errors/render";
5
5
 
6
6
  /**
7
+ * @deprecated
7
8
  * @draft-editor https://json-schema.org/draft/2020-12/release-notes
8
9
  *
9
10
  * Uses Draft 2020-12 and changes resolveOneOf to be fuzzy
@@ -20,3 +20,16 @@ describe("keyword : type : validation", () => {
20
20
  });
21
21
  });
22
22
  });
23
+
24
+ describe("keyword : type : reduce", () => {
25
+ it("should reduce array-type to the last matching data-type", () => {
26
+ const { node } = compileSchema({ type: ["null", "integer"] }).reduceNode(123);
27
+ assert.equal(node?.type, "integer");
28
+ assert.equal(node?.schema.type, "integer");
29
+ });
30
+ it("should reduce array-type to the first matching data-type", () => {
31
+ const { node } = compileSchema({ type: ["null", "integer"] }).reduceNode(null);
32
+ assert.equal(node?.type, "null");
33
+ assert.equal(node?.schema.type, "null");
34
+ });
35
+ });
package/src/settings.ts CHANGED
@@ -18,6 +18,8 @@ export default {
18
18
  "propertyDependencies"
19
19
  ],
20
20
  REGEX_FLAGS: "u",
21
+ /** additional keywords that should not produce an unknown-keyword-warning */
22
+ VALID_ANNOTATION_KEYWORDS: ["title", "description", "default"],
21
23
  /**
22
24
  * properties to keep from a $ref-schema when resolving a $ref (recursively)
23
25
  * this allows to overwrite specified properties locally on a $ref-definition
@@ -78,4 +78,41 @@ describe("mergeSchema", () => {
78
78
  );
79
79
  assert.deepEqual(schema.items.anyOf, [{ type: "string" }, { type: "number" }]);
80
80
  });
81
+
82
+ describe("type", () => {
83
+ it("should return last type if they conflict", () => {
84
+ const schema = mergeSchema({ type: "array" }, { type: "integer" });
85
+ assert.deepEqual(schema.type, "integer");
86
+ });
87
+
88
+ it("should not merge mixed data types with string first", () => {
89
+ const schema = mergeSchema({ type: "array" }, { type: ["array", "object"] });
90
+ assert.deepEqual(schema.type, "array");
91
+ });
92
+
93
+ it("should not merge mixed data types with string second", () => {
94
+ const schema = mergeSchema({ type: ["array", "object"] }, { type: "array" });
95
+ assert.deepEqual(schema.type, "array");
96
+ });
97
+
98
+ it("should merge mixed data types if they have no type in common", () => {
99
+ const schema = mergeSchema({ type: ["array", "object"] }, { type: "integer" });
100
+ assert.deepEqual(schema.type, ["array", "object", "integer"]);
101
+ });
102
+
103
+ it("should merge return last type if they do not match", () => {
104
+ const schema = mergeSchema({ type: "object" }, { type: "array" });
105
+ assert.deepEqual(schema.type, "array");
106
+ });
107
+
108
+ it("should merge array types", () => {
109
+ const schema = mergeSchema({ type: ["object"] }, { type: ["array"] });
110
+ assert.deepEqual(schema.type, ["object", "array"]);
111
+ });
112
+
113
+ it("should merge array types without duplicated", () => {
114
+ const schema = mergeSchema({ type: ["integer", "object"] }, { type: ["object", "array"] });
115
+ assert.deepEqual(schema.type, ["integer", "object", "array"]);
116
+ });
117
+ });
81
118
  });
@@ -32,7 +32,10 @@ export function mergeSchema2(a: unknown, b: unknown, property?: string): unknown
32
32
  return newObject;
33
33
  }
34
34
 
35
- if (Array.isArray(a) && Array.isArray(b)) {
35
+ const aIsArray = Array.isArray(a);
36
+ const bIsArray = Array.isArray(b);
37
+
38
+ if (aIsArray && bIsArray) {
36
39
  if (property === "required" || property === "anyOf") {
37
40
  return a.concat(b).filter((item, index, array) => array.indexOf(item) === index);
38
41
  }
@@ -66,11 +69,29 @@ export function mergeSchema2(a: unknown, b: unknown, property?: string): unknown
66
69
  return [...result, ...append].filter((item, index, array) => array.indexOf(item) === index);
67
70
  }
68
71
 
69
- if (Array.isArray(b)) {
72
+ // mixed data-type for type keyword
73
+ if (property === "type" && (aIsArray || bIsArray)) {
74
+ // we merge to the specific type
75
+ if (aIsArray && a.includes(b)) {
76
+ return b;
77
+ }
78
+ if (bIsArray && b.includes(a)) {
79
+ return a;
80
+ }
81
+ // extend the types if they do not match
82
+ if (aIsArray) {
83
+ return [...a, b];
84
+ }
85
+ if (bIsArray) {
86
+ return [...b, a];
87
+ }
88
+ }
89
+
90
+ if (bIsArray) {
70
91
  return b;
71
92
  }
72
93
 
73
- if (Array.isArray(a)) {
94
+ if (aIsArray) {
74
95
  return a;
75
96
  }
76
97
 
@@ -263,7 +263,7 @@ describe("validateSchema", () => {
263
263
  assert.equal(schemaAnnotations.length, 1);
264
264
  assert.equal(schemaAnnotations[0].data.pointer, "#/properties/headline/options");
265
265
  });
266
- it("should return not unknown keywords starting with 'x-'", () => {
266
+ it("should not return unknown keywords starting with 'x-'", () => {
267
267
  const { schemaAnnotations } = compileSchema({
268
268
  properties: {
269
269
  headline: {
@@ -274,6 +274,15 @@ describe("validateSchema", () => {
274
274
  });
275
275
  assert.equal(schemaAnnotations.length, 0);
276
276
  });
277
+ it("should not return valid annotation keywords", () => {
278
+ const { schemaAnnotations } = compileSchema({
279
+ type: "string",
280
+ title: "input",
281
+ description: "any string",
282
+ default: "hey"
283
+ });
284
+ assert.equal(schemaAnnotations.length, 0);
285
+ });
277
286
  it("should return removed keywords from old drafts as annotation", () => {
278
287
  const { schemaAnnotations } = compileSchema({
279
288
  properties: {