@squiz/dx-json-schema-lib 1.2.13-alpha.2 → 1.2.13-alpha.4

Sign up to get free protection for your applications and to get access to all the features.
Files changed (52) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/jest.config.ts +7 -0
  3. package/lib/JsonValidationService.d.ts +4 -6
  4. package/lib/JsonValidationService.js +90 -28
  5. package/lib/JsonValidationService.js.map +1 -1
  6. package/lib/JsonValidationService.spec.js +276 -65
  7. package/lib/JsonValidationService.spec.js.map +1 -1
  8. package/lib/errors/SchemaValidationError.d.ts +1 -1
  9. package/lib/errors/SchemaValidationError.js +8 -1
  10. package/lib/errors/SchemaValidationError.js.map +1 -1
  11. package/lib/formatted-text/v1/formattedText.d.ts +18 -16
  12. package/lib/formatted-text/v1/formattedText.json +59 -88
  13. package/lib/formatted-text/v1/higherOrderFormattedTextToBaseFormattedText.d.ts +8 -0
  14. package/lib/formatted-text/v1/higherOrderFormattedTextToBaseFormattedText.js +21 -0
  15. package/lib/formatted-text/v1/higherOrderFormattedTextToBaseFormattedText.js.map +1 -0
  16. package/lib/formatted-text/v1/higherOrderFormattedTextToBaseFormattedText.spec.d.ts +1 -0
  17. package/lib/formatted-text/v1/higherOrderFormattedTextToBaseFormattedText.spec.js +52 -0
  18. package/lib/formatted-text/v1/higherOrderFormattedTextToBaseFormattedText.spec.js.map +1 -0
  19. package/lib/index.d.ts +1 -0
  20. package/lib/index.js +1 -0
  21. package/lib/index.js.map +1 -1
  22. package/lib/manifest/v1/Draft-07.json +155 -0
  23. package/lib/manifest/v1/DxComponentInputSchema.json +11 -8
  24. package/lib/manifest/v1/DxComponentInputSchema.spec.js +23 -9
  25. package/lib/manifest/v1/DxComponentInputSchema.spec.js.map +1 -1
  26. package/lib/manifest/v1/__test__/schemas/badFunctionInputComponent.json +2 -1
  27. package/lib/manifest/v1/__test__/schemas/badNestedFunctionInput.json +2 -1
  28. package/lib/manifest/v1/__test__/schemas/nonObjectFunctionInputComponent.json +2 -1
  29. package/lib/manifest/v1/__test__/schemas/validComponentJson.json +48 -0
  30. package/lib/manifest/v1/v1.d.ts +5 -7
  31. package/lib/manifest/v1/v1.spec.js +22 -3
  32. package/lib/manifest/v1/v1.spec.js.map +1 -1
  33. package/package.json +5 -3
  34. package/src/JsonValidationService.spec.ts +379 -70
  35. package/src/JsonValidationService.ts +112 -34
  36. package/src/errors/SchemaValidationError.ts +10 -2
  37. package/src/formatted-text/v1/formattedText.json +65 -89
  38. package/src/formatted-text/v1/formattedText.ts +19 -17
  39. package/src/formatted-text/v1/higherOrderFormattedTextToBaseFormattedText.spec.ts +58 -0
  40. package/src/formatted-text/v1/higherOrderFormattedTextToBaseFormattedText.ts +36 -0
  41. package/src/index.ts +1 -0
  42. package/src/manifest/v1/Draft-07.json +155 -0
  43. package/src/manifest/v1/DxComponentInputSchema.json +11 -8
  44. package/src/manifest/v1/DxComponentInputSchema.spec.ts +63 -35
  45. package/src/manifest/v1/__test__/schemas/badFunctionInputComponent.json +2 -1
  46. package/src/manifest/v1/__test__/schemas/badNestedFunctionInput.json +2 -1
  47. package/src/manifest/v1/__test__/schemas/nonObjectFunctionInputComponent.json +2 -1
  48. package/src/manifest/v1/__test__/schemas/validComponentJson.json +48 -0
  49. package/src/manifest/v1/v1.spec.ts +37 -3
  50. package/src/manifest/v1/v1.ts +5 -7
  51. package/tsconfig.json +2 -1
  52. package/tsconfig.tsbuildinfo +1 -1
@@ -1,8 +1,16 @@
1
1
  import { IOutputError } from 'better-ajv-errors';
2
2
 
3
3
  export class SchemaValidationError extends Error {
4
- constructor(validationErrors: IOutputError[], error?: Error) {
5
- super(`failed validation: ${validationErrors.map((e) => e.error).join(', ')}`);
4
+ constructor(validationErrors: IOutputError[] | string, error?: Error) {
5
+ let message: string;
6
+
7
+ if (typeof validationErrors != 'string') {
8
+ message = validationErrors.map((e) => e.error).join(', ');
9
+ } else {
10
+ message = validationErrors;
11
+ }
12
+
13
+ super(`failed validation: ${message}`);
6
14
  this.stack = error?.stack;
7
15
  this.name = 'SchemaValidationError';
8
16
  }
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "$schema": "http://json-schema.org/draft-07/schema#",
3
3
  "type": "array",
4
- "additionalProperties": false,
5
4
  "title": "FormattedText",
6
5
 
7
6
  "items": {
@@ -27,6 +26,7 @@
27
26
  "TextNode": {
28
27
  "type": "object",
29
28
  "additionalProperties": false,
29
+
30
30
  "properties": {
31
31
  "type": { "const": "text" },
32
32
  "value": { "type": "string" }
@@ -34,112 +34,88 @@
34
34
  "required": ["type", "value"]
35
35
  },
36
36
 
37
- "FormattedNode": {
37
+ "FormattingOptions": {
38
38
  "type": "object",
39
+
39
40
  "properties": {
40
- "type": { "type": "string" },
41
- "formattingOptions": {
42
- "type": "object",
43
- "additionalProperties": false,
44
- "properties": {
45
- "alignment": {
46
- "type": "string",
47
- "enum": ["center", "left", "right", "justify"]
48
- }
49
- }
41
+ "alignment": {
42
+ "type": "string",
43
+ "enum": ["center", "left", "right", "justify"]
50
44
  }
51
- },
52
- "required": ["type"],
45
+ }
46
+ },
53
47
 
54
- "additionalProperties": false
48
+ "WithChildrenNode": {
49
+ "type": "array",
50
+ "items": {
51
+ "$ref": "#/definitions/FormattedNodes"
52
+ }
55
53
  },
56
54
 
57
- "FormattedNodeWithChildren": {
55
+ "FormattedTextTag": {
58
56
  "type": "object",
59
57
  "additionalProperties": false,
60
- "allOf": [
61
- { "$ref": "#/definitions/FormattedNode" },
62
- {
63
- "type": "object",
64
- "additionalProperties": false,
65
- "properties": {
66
- "children": {
67
- "type": "array",
68
- "items": {
69
- "$ref": "#/definitions/FormattedNodes"
70
- }
71
- }
72
- },
73
- "required": ["children"]
74
- }
75
- ]
76
- },
77
58
 
78
- "FormattedTextTag": {
79
- "allOf": [
80
- { "$ref": "#/definitions/FormattedNodeWithChildren" },
81
- {
59
+ "properties": {
60
+ "children": {
61
+ "$ref": "#/definitions/WithChildrenNode"
62
+ },
63
+ "formattingOptions": { "$ref": "#/definitions/FormattingOptions" },
64
+ "type": { "const": "tag" },
65
+ "tag": { "type": "string", "minLength": 1 },
66
+ "attributes": {
82
67
  "type": "object",
83
- "additionalProperties": false,
68
+ "additionalProperties": {
69
+ "type": "string"
70
+ }
71
+ },
72
+ "font": {
73
+ "title": "FormattedNodeFontProperties",
74
+ "type": "object",
75
+
84
76
  "properties": {
85
- "type": { "const": "tag" },
86
- "tag": { "type": "string", "minLength": 1 },
87
- "attributes": {
88
- "type": "object",
89
- "additionalProperties": { "type": "string" }
90
- },
91
- "font": {
92
- "title": "FormattedNodeFontProperties",
93
- "type": "object",
94
- "additionalProperties": false,
95
- "properties": {
96
- "bold": { "type": "boolean" },
97
- "underline": { "type": "boolean" },
98
- "italics": { "type": "boolean" },
99
- "color": { "type": "string" },
100
- "size": { "type": "string" },
101
- "fontFamily": { "type": "string" }
102
- }
103
- }
104
- },
105
- "required": ["type", "tag"]
77
+ "bold": { "type": "boolean" },
78
+ "underline": { "type": "boolean" },
79
+ "italics": { "type": "boolean" },
80
+ "color": { "type": "string" },
81
+ "size": { "type": "string" },
82
+ "fontFamily": { "type": "string" }
83
+ }
106
84
  }
107
- ]
85
+ },
86
+ "required": ["type", "tag", "children"]
108
87
  },
109
88
 
110
89
  "FormattedTextLinkToMatrixAsset": {
111
- "allOf": [
112
- { "$ref": "#/definitions/FormattedNodeWithChildren" },
113
- {
114
- "type": "object",
115
- "additionalProperties": false,
116
- "properties": {
117
- "type": { "const": "link-to-matrix-asset" },
118
- "matrixIdentifier": { "type": "string", "minLength": 1 },
119
- "matrixDomain": { "type": "string", "minLength": 1 },
120
- "matrixAssetId": { "type": "string", "minLength": 1 },
121
- "target": { "type": "string", "enum": ["_blank", "_self", "_parent", "_top"] }
122
- },
123
- "required": ["type", "matrixDomain", "matrixAssetId", "target"]
124
- }
125
- ]
90
+ "type": "object",
91
+ "additionalProperties": false,
92
+
93
+ "properties": {
94
+ "children": {
95
+ "$ref": "#/definitions/WithChildrenNode"
96
+ },
97
+ "formattingOptions": { "$ref": "#/definitions/FormattingOptions" },
98
+ "type": { "const": "link-to-matrix-asset" },
99
+ "matrixIdentifier": { "type": "string", "minLength": 1 },
100
+ "matrixDomain": { "type": "string", "minLength": 1 },
101
+ "matrixAssetId": { "type": "string", "minLength": 1 },
102
+ "target": { "type": "string", "enum": ["_blank", "_self", "_parent", "_top"] }
103
+ },
104
+ "required": ["type", "matrixDomain", "matrixAssetId", "target", "children"]
126
105
  },
127
106
 
128
107
  "FormattedTextMatrixImage": {
129
- "allOf": [
130
- { "$ref": "#/definitions/FormattedNode" },
131
- {
132
- "type": "object",
133
- "additionalProperties": false,
134
- "properties": {
135
- "type": { "const": "matrix-image" },
136
- "matrixIdentifier": { "type": "string", "minLength": 1 },
137
- "matrixDomain": { "type": "string", "minLength": 1 },
138
- "matrixAssetId": { "type": "string", "minLength": 1 }
139
- },
140
- "required": ["type", "matrixDomain", "matrixAssetId"]
141
- }
142
- ]
108
+ "type": "object",
109
+ "additionalProperties": false,
110
+
111
+ "properties": {
112
+ "type": { "const": "matrix-image" },
113
+ "matrixIdentifier": { "type": "string", "minLength": 1 },
114
+ "matrixDomain": { "type": "string", "minLength": 1 },
115
+ "matrixAssetId": { "type": "string", "minLength": 1 },
116
+ "formattingOptions": { "$ref": "#/definitions/FormattingOptions" }
117
+ },
118
+ "required": ["type", "matrixDomain", "matrixAssetId"]
143
119
  }
144
120
  }
145
121
  }
@@ -7,38 +7,39 @@
7
7
 
8
8
  export type FormattedNodes = HigherOrderFormattedNodes | BaseFormattedNodes;
9
9
  export type HigherOrderFormattedNodes = FormattedTextLinkToMatrixAsset | FormattedTextMatrixImage;
10
- export type FormattedTextLinkToMatrixAsset = FormattedNodeWithChildren & {
10
+ export type WithChildrenNode = FormattedNodes[];
11
+ export type BaseFormattedNodes = FormattedTextTag | TextNode;
12
+ export type FormattedText = FormattedNodes[];
13
+
14
+ export interface FormattedTextLinkToMatrixAsset {
15
+ children: WithChildrenNode;
16
+ formattingOptions?: FormattingOptions;
11
17
  type: 'link-to-matrix-asset';
12
18
  matrixIdentifier?: string;
13
19
  matrixDomain: string;
14
20
  matrixAssetId: string;
15
21
  target: '_blank' | '_self' | '_parent' | '_top';
16
- };
17
- export type FormattedNodeWithChildren = FormattedNode & {
18
- children: FormattedNodes[];
19
- };
20
- export type FormattedTextMatrixImage = FormattedNode & {
22
+ }
23
+ export interface FormattingOptions {
24
+ alignment?: 'center' | 'left' | 'right' | 'justify';
25
+ [k: string]: unknown;
26
+ }
27
+ export interface FormattedTextMatrixImage {
21
28
  type: 'matrix-image';
22
29
  matrixIdentifier?: string;
23
30
  matrixDomain: string;
24
31
  matrixAssetId: string;
25
- };
26
- export type BaseFormattedNodes = FormattedTextTag | TextNode;
27
- export type FormattedTextTag = FormattedNodeWithChildren & {
32
+ formattingOptions?: FormattingOptions;
33
+ }
34
+ export interface FormattedTextTag {
35
+ children: WithChildrenNode;
36
+ formattingOptions?: FormattingOptions;
28
37
  type: 'tag';
29
38
  tag: string;
30
39
  attributes?: {
31
40
  [k: string]: string;
32
41
  };
33
42
  font?: FormattedNodeFontProperties;
34
- };
35
- export type FormattedText = FormattedNodes[];
36
-
37
- export interface FormattedNode {
38
- type: string;
39
- formattingOptions?: {
40
- alignment?: 'center' | 'left' | 'right' | 'justify';
41
- };
42
43
  }
43
44
  export interface FormattedNodeFontProperties {
44
45
  bold?: boolean;
@@ -47,6 +48,7 @@ export interface FormattedNodeFontProperties {
47
48
  color?: string;
48
49
  size?: string;
49
50
  fontFamily?: string;
51
+ [k: string]: unknown;
50
52
  }
51
53
  export interface TextNode {
52
54
  type: 'text';
@@ -0,0 +1,58 @@
1
+ import { FormattedNodes, HigherOrderFormattedNodes } from './formattedText';
2
+ import {
3
+ HigherOrderFormattedNodesMap,
4
+ higherOrderFormattedTextToBaseFormattedText,
5
+ } from './higherOrderFormattedTextToBaseFormattedText';
6
+
7
+ describe('higherOrderFormattedTextToBaseFormattedText', () => {
8
+ const mockResolvers: HigherOrderFormattedNodesMap = {
9
+ 'link-to-matrix-asset': jest.fn((node: HigherOrderFormattedNodes) => {
10
+ return Promise.resolve({ type: 'text', value: `Link to asset ${node.matrixAssetId}` });
11
+ }),
12
+ 'matrix-image': jest.fn((node: HigherOrderFormattedNodes) => {
13
+ return Promise.resolve({ type: 'text', value: `Image ${node.matrixAssetId}` });
14
+ }),
15
+ };
16
+
17
+ const testFormattedNodes: FormattedNodes = {
18
+ tag: 'p',
19
+ type: 'tag',
20
+ children: [
21
+ { type: 'text', value: 'This is some ' },
22
+ {
23
+ type: 'link-to-matrix-asset',
24
+ matrixIdentifier: 'abc123',
25
+ matrixDomain: 'example.com',
26
+ matrixAssetId: '12345',
27
+ target: '_blank',
28
+ children: [{ type: 'text', value: 'linked text' }],
29
+ },
30
+ { type: 'text', value: ' with an image ' },
31
+ {
32
+ type: 'matrix-image',
33
+ matrixIdentifier: 'abc123',
34
+ matrixDomain: 'example.com',
35
+ matrixAssetId: '54321',
36
+ },
37
+ { type: 'text', value: '.' },
38
+ ],
39
+ };
40
+
41
+ const expectedFormattedNodes: FormattedNodes = {
42
+ tag: 'p',
43
+ type: 'tag',
44
+ children: [
45
+ { type: 'text', value: 'This is some ' },
46
+ { type: 'text', value: 'Link to asset 12345' },
47
+ { type: 'text', value: ' with an image ' },
48
+ { type: 'text', value: 'Image 54321' },
49
+ { type: 'text', value: '.' },
50
+ ],
51
+ };
52
+
53
+ it('should replace higher order nodes with their resolved counterparts', async () => {
54
+ const result = await higherOrderFormattedTextToBaseFormattedText(testFormattedNodes, mockResolvers);
55
+
56
+ expect(result).toEqual(expectedFormattedNodes);
57
+ });
58
+ });
@@ -0,0 +1,36 @@
1
+ import { BaseFormattedNodes, FormattedNodes, HigherOrderFormattedNodes } from './formattedText';
2
+
3
+ export interface FormattedNodeWithChildren {
4
+ children: FormattedNodes[];
5
+ }
6
+
7
+ export type HigherOrderFormattedNodesMap = {
8
+ [key in HigherOrderFormattedNodes['type']]: (node: HigherOrderFormattedNodes) => Promise<BaseFormattedNodes>;
9
+ };
10
+
11
+ export async function higherOrderFormattedTextToBaseFormattedText(
12
+ node: FormattedNodes,
13
+ resolvers: HigherOrderFormattedNodesMap,
14
+ ): Promise<FormattedNodes> {
15
+ if (node.type in resolvers && nodeIsHigherOrder(node)) {
16
+ return await resolvers[node.type](node);
17
+ }
18
+
19
+ if (hasChildren(node)) {
20
+ const updatedChildren = await Promise.all(
21
+ node.children.map((child) => higherOrderFormattedTextToBaseFormattedText(child, resolvers)),
22
+ );
23
+
24
+ return { ...node, children: updatedChildren };
25
+ }
26
+
27
+ return node;
28
+ }
29
+
30
+ function hasChildren(node: any): node is FormattedNodeWithChildren {
31
+ return node && typeof node == 'object' && 'children' in node && Array.isArray(node.children);
32
+ }
33
+
34
+ function nodeIsHigherOrder(node: FormattedNodes): node is HigherOrderFormattedNodes {
35
+ return node.type === 'link-to-matrix-asset' || node.type === 'matrix-image';
36
+ }
package/src/index.ts CHANGED
@@ -3,6 +3,7 @@ export * as MANIFEST_SCHEMAS from './manifest/v1/manifestSchemas';
3
3
 
4
4
  export * as FORMATTED_TEXT_MODELS from './formatted-text/v1/formattedTextModels';
5
5
  export * as FORMATTED_TEXT_SCHEMAS from './formatted-text/v1/formattedTextSchemas';
6
+ export * from './formatted-text/v1/higherOrderFormattedTextToBaseFormattedText';
6
7
 
7
8
  export * as SUB_SCHEMAS from './manifest/v1/subSchemas';
8
9
 
@@ -0,0 +1,155 @@
1
+ {
2
+ "$schema": "http://json-schema.org/draft-07/schema#",
3
+ "$id": "http://json-schema.org/draft-07/schema#",
4
+ "title": "Core schema meta-schema",
5
+ "definitions": {
6
+ "schemaArray": {
7
+ "type": "array",
8
+ "minItems": 1,
9
+ "items": { "$ref": "#" }
10
+ },
11
+ "nonNegativeInteger": {
12
+ "type": "integer",
13
+ "minimum": 0
14
+ },
15
+ "nonNegativeIntegerDefault0": {
16
+ "allOf": [{ "$ref": "#/definitions/nonNegativeInteger" }, { "default": 0 }]
17
+ },
18
+ "simpleTypes": {
19
+ "enum": ["array", "boolean", "integer", "null", "number", "object", "string"]
20
+ },
21
+ "stringArray": {
22
+ "type": "array",
23
+ "items": { "type": "string" },
24
+ "uniqueItems": true,
25
+ "default": []
26
+ }
27
+ },
28
+ "type": ["object", "boolean"],
29
+ "properties": {
30
+ "$id": {
31
+ "type": "string",
32
+ "format": "uri-reference"
33
+ },
34
+ "$schema": {
35
+ "type": "string",
36
+ "format": "uri"
37
+ },
38
+ "$ref": {
39
+ "type": "string",
40
+ "format": "uri-reference"
41
+ },
42
+ "$comment": {
43
+ "type": "string"
44
+ },
45
+ "title": {
46
+ "type": "string"
47
+ },
48
+ "description": {
49
+ "type": "string"
50
+ },
51
+ "default": true,
52
+ "readOnly": {
53
+ "type": "boolean",
54
+ "default": false
55
+ },
56
+ "writeOnly": {
57
+ "type": "boolean",
58
+ "default": false
59
+ },
60
+ "examples": {
61
+ "type": "array",
62
+ "items": true
63
+ },
64
+ "multipleOf": {
65
+ "type": "number",
66
+ "exclusiveMinimum": 0
67
+ },
68
+ "maximum": {
69
+ "type": "number"
70
+ },
71
+ "exclusiveMaximum": {
72
+ "type": "number"
73
+ },
74
+ "minimum": {
75
+ "type": "number"
76
+ },
77
+ "exclusiveMinimum": {
78
+ "type": "number"
79
+ },
80
+ "maxLength": { "$ref": "#/definitions/nonNegativeInteger" },
81
+ "minLength": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
82
+ "pattern": {
83
+ "type": "string",
84
+ "format": "regex"
85
+ },
86
+ "additionalItems": { "$ref": "#" },
87
+ "items": {
88
+ "anyOf": [{ "$ref": "#" }, { "$ref": "#/definitions/schemaArray" }],
89
+ "default": true
90
+ },
91
+ "maxItems": { "$ref": "#/definitions/nonNegativeInteger" },
92
+ "minItems": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
93
+ "uniqueItems": {
94
+ "type": "boolean",
95
+ "default": false
96
+ },
97
+ "contains": { "$ref": "#" },
98
+ "maxProperties": { "$ref": "#/definitions/nonNegativeInteger" },
99
+ "minProperties": { "$ref": "#/definitions/nonNegativeIntegerDefault0" },
100
+ "required": { "$ref": "#/definitions/stringArray" },
101
+ "additionalProperties": { "$ref": "#" },
102
+ "definitions": {
103
+ "type": "object",
104
+ "additionalProperties": { "$ref": "#" },
105
+ "default": {}
106
+ },
107
+ "properties": {
108
+ "type": "object",
109
+ "additionalProperties": { "$ref": "#" },
110
+ "default": {}
111
+ },
112
+ "patternProperties": {
113
+ "type": "object",
114
+ "additionalProperties": { "$ref": "#" },
115
+ "propertyNames": { "format": "regex" },
116
+ "default": {}
117
+ },
118
+ "dependencies": {
119
+ "type": "object",
120
+ "additionalProperties": {
121
+ "anyOf": [{ "$ref": "#" }, { "$ref": "#/definitions/stringArray" }]
122
+ }
123
+ },
124
+ "propertyNames": { "$ref": "#" },
125
+ "const": true,
126
+ "enum": {
127
+ "type": "array",
128
+ "items": true,
129
+ "minItems": 1,
130
+ "uniqueItems": true
131
+ },
132
+ "type": {
133
+ "anyOf": [
134
+ { "$ref": "#/definitions/simpleTypes" },
135
+ {
136
+ "type": "array",
137
+ "items": { "$ref": "#/definitions/simpleTypes" },
138
+ "minItems": 1,
139
+ "uniqueItems": true
140
+ }
141
+ ]
142
+ },
143
+ "format": { "type": "string" },
144
+ "contentMediaType": { "type": "string" },
145
+ "contentEncoding": { "type": "string" },
146
+ "if": { "$ref": "#" },
147
+ "then": { "$ref": "#" },
148
+ "else": { "$ref": "#" },
149
+ "allOf": { "$ref": "#/definitions/schemaArray" },
150
+ "anyOf": { "$ref": "#/definitions/schemaArray" },
151
+ "oneOf": { "$ref": "#/definitions/schemaArray" },
152
+ "not": { "$ref": "#" }
153
+ },
154
+ "default": true
155
+ }
@@ -4,17 +4,20 @@
4
4
  "title": "DxComponentInputSchema",
5
5
  "description": "Input schema for a DxComponent",
6
6
  "type": "object",
7
- "properties": {
8
- "type": {
9
- "const": "object"
10
- },
11
- "additionalProperties": {
12
- "default": false
13
- }
14
- },
15
7
  "allOf": [
16
8
  {
17
9
  "$ref": "DxContentMetaSchema.json"
10
+ },
11
+ {
12
+ "type": "object",
13
+ "properties": {
14
+ "type": {
15
+ "const": "object"
16
+ },
17
+ "additionalProperties": {
18
+ "default": false
19
+ }
20
+ }
18
21
  }
19
22
  ]
20
23
  }