@squiz/dx-json-schema-lib 1.2.13-alpha.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/CHANGELOG.md +8 -0
- package/jest.config.ts +20 -0
- package/jsonCompiler.ts +22 -0
- package/lib/JsonValidationService.d.ts +9 -0
- package/lib/JsonValidationService.js +54 -0
- package/lib/JsonValidationService.js.map +1 -0
- package/lib/JsonValidationService.spec.d.ts +1 -0
- package/lib/JsonValidationService.spec.js +143 -0
- package/lib/JsonValidationService.spec.js.map +1 -0
- package/lib/errors/SchemaValidationError.d.ts +4 -0
- package/lib/errors/SchemaValidationError.js +12 -0
- package/lib/errors/SchemaValidationError.js.map +1 -0
- package/lib/formatted-text/v1/formattedText.d.ts +51 -0
- package/lib/formatted-text/v1/formattedText.js +9 -0
- package/lib/formatted-text/v1/formattedText.js.map +1 -0
- package/lib/formatted-text/v1/formattedText.json +134 -0
- package/lib/formatted-text/v1/formattedTextModels.d.ts +1 -0
- package/lib/formatted-text/v1/formattedTextModels.js +28 -0
- package/lib/formatted-text/v1/formattedTextModels.js.map +1 -0
- package/lib/formatted-text/v1/formattedTextSchemas.d.ts +2 -0
- package/lib/formatted-text/v1/formattedTextSchemas.js +9 -0
- package/lib/formatted-text/v1/formattedTextSchemas.js.map +1 -0
- package/lib/index.d.ts +7 -0
- package/lib/index.js +37 -0
- package/lib/index.js.map +1 -0
- package/lib/manifest/v1/DxComponentIcons.json +2279 -0
- package/lib/manifest/v1/DxComponentInputSchema.json +20 -0
- package/lib/manifest/v1/DxComponentInputSchema.spec.d.ts +1 -0
- package/lib/manifest/v1/DxComponentInputSchema.spec.js +113 -0
- package/lib/manifest/v1/DxComponentInputSchema.spec.js.map +1 -0
- package/lib/manifest/v1/DxContentMetaSchema.json +165 -0
- package/lib/manifest/v1/__test__/schemas/badFunctionInputComponent.json +39 -0
- package/lib/manifest/v1/__test__/schemas/badNestedFunctionInput.json +39 -0
- package/lib/manifest/v1/__test__/schemas/nonObjectFunctionInputComponent.json +39 -0
- package/lib/manifest/v1/__test__/schemas/validComponent.json +40 -0
- package/lib/manifest/v1/manifestModels.d.ts +1 -0
- package/lib/manifest/v1/manifestModels.js +28 -0
- package/lib/manifest/v1/manifestModels.js.map +1 -0
- package/lib/manifest/v1/manifestSchemas.d.ts +2 -0
- package/lib/manifest/v1/manifestSchemas.js +9 -0
- package/lib/manifest/v1/manifestSchemas.js.map +1 -0
- package/lib/manifest/v1/subSchemas.d.ts +4 -0
- package/lib/manifest/v1/subSchemas.js +13 -0
- package/lib/manifest/v1/subSchemas.js.map +1 -0
- package/lib/manifest/v1/v1.d.ts +460 -0
- package/lib/manifest/v1/v1.js +9 -0
- package/lib/manifest/v1/v1.js.map +1 -0
- package/lib/manifest/v1/v1.json +362 -0
- package/lib/manifest/v1/v1.spec.d.ts +1 -0
- package/lib/manifest/v1/v1.spec.js +35 -0
- package/lib/manifest/v1/v1.spec.js.map +1 -0
- package/package.json +36 -0
- package/src/JsonValidationService.spec.ts +162 -0
- package/src/JsonValidationService.ts +54 -0
- package/src/errors/SchemaValidationError.ts +9 -0
- package/src/formatted-text/v1/formattedText.json +145 -0
- package/src/formatted-text/v1/formattedText.ts +54 -0
- package/src/formatted-text/v1/formattedTextModels.ts +1 -0
- package/src/formatted-text/v1/formattedTextSchemas.ts +3 -0
- package/src/index.ts +10 -0
- package/src/manifest/v1/DxComponentIcons.json +2279 -0
- package/src/manifest/v1/DxComponentInputSchema.json +20 -0
- package/src/manifest/v1/DxComponentInputSchema.spec.ts +136 -0
- package/src/manifest/v1/DxContentMetaSchema.json +165 -0
- package/src/manifest/v1/__test__/schemas/badFunctionInputComponent.json +39 -0
- package/src/manifest/v1/__test__/schemas/badNestedFunctionInput.json +39 -0
- package/src/manifest/v1/__test__/schemas/nonObjectFunctionInputComponent.json +39 -0
- package/src/manifest/v1/__test__/schemas/validComponent.json +40 -0
- package/src/manifest/v1/manifestModels.ts +1 -0
- package/src/manifest/v1/manifestSchemas.ts +3 -0
- package/src/manifest/v1/subSchemas.ts +5 -0
- package/src/manifest/v1/v1.json +369 -0
- package/src/manifest/v1/v1.spec.ts +39 -0
- package/src/manifest/v1/v1.ts +2731 -0
- package/tsconfig.json +16 -0
- package/tsconfig.tsbuildinfo +1 -0
@@ -0,0 +1,362 @@
|
|
1
|
+
{
|
2
|
+
"$schema": "http://json-schema.org/draft-07/schema#",
|
3
|
+
"type": "object",
|
4
|
+
"title": "ComponentManifest",
|
5
|
+
"additionalProperties": false,
|
6
|
+
"properties": {
|
7
|
+
"$schema": { "type": "string", "description": "the manifest schema version" },
|
8
|
+
"namespace": {
|
9
|
+
"type": "string",
|
10
|
+
"description": "Namesapce of the component",
|
11
|
+
"$ref": "#/definitions/name-pattern"
|
12
|
+
},
|
13
|
+
"name": { "type": "string", "description": "Name of the component", "$ref": "#/definitions/name-pattern" },
|
14
|
+
"displayName": { "type": "string", "description": "Display name of the component" },
|
15
|
+
"description": { "type": "string", "description": "Description of the component" },
|
16
|
+
"mainFunction": {
|
17
|
+
"type": "string",
|
18
|
+
"description": "Name of the main function to be executed at the root of the access url"
|
19
|
+
},
|
20
|
+
"icon": {
|
21
|
+
"$ref": "DxComponentIcons.json"
|
22
|
+
},
|
23
|
+
"version": {
|
24
|
+
"type": "string",
|
25
|
+
"description": "Semver version number",
|
26
|
+
"minLength": 5,
|
27
|
+
"maxLength": 14,
|
28
|
+
"pattern": "^(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)\\.(?:0|[1-9]\\d*)$"
|
29
|
+
},
|
30
|
+
"environment": {
|
31
|
+
"type": "array",
|
32
|
+
"description": "an array of environmental variables needed to execute correctly. These variables are set through your local environment variables during testing or through the component set during production run time",
|
33
|
+
"items": {
|
34
|
+
"type": "object",
|
35
|
+
"properties": {
|
36
|
+
"name": {
|
37
|
+
"type": "string",
|
38
|
+
"$ref": "#/definitions/name-pattern",
|
39
|
+
"description": "Environmental variable name"
|
40
|
+
},
|
41
|
+
"required": {
|
42
|
+
"type": "boolean",
|
43
|
+
"default": true
|
44
|
+
}
|
45
|
+
},
|
46
|
+
"required": ["name"]
|
47
|
+
}
|
48
|
+
},
|
49
|
+
"staticFiles": {
|
50
|
+
"type": "object",
|
51
|
+
"properties": {
|
52
|
+
"locationRoot": {
|
53
|
+
"type": "string",
|
54
|
+
"description": "The location the system begins looking static files. All static files will be referenced from here."
|
55
|
+
}
|
56
|
+
},
|
57
|
+
"required": ["locationRoot"]
|
58
|
+
},
|
59
|
+
"functions": {
|
60
|
+
"type": "array",
|
61
|
+
"minItems": 0,
|
62
|
+
"items": {
|
63
|
+
"title": "ComponentFunction",
|
64
|
+
"description": "Component function definition, this object provides the runtime with input validation and what to execute",
|
65
|
+
"type": "object",
|
66
|
+
"properties": {
|
67
|
+
"name": {
|
68
|
+
"type": "string",
|
69
|
+
"$ref": "#/definitions/name-pattern",
|
70
|
+
"description": "Function name, this will be used as part of the url to access this function"
|
71
|
+
},
|
72
|
+
"entry": {
|
73
|
+
"type": "string",
|
74
|
+
"description": "File path to the javascript file to execute. The file path must be relative to the manifest file and cannot be in a folder above the manifest file."
|
75
|
+
},
|
76
|
+
"input": {
|
77
|
+
"$ref": "DxComponentInputSchema.json"
|
78
|
+
},
|
79
|
+
"output": {
|
80
|
+
"oneOf": [
|
81
|
+
{
|
82
|
+
"title": "HtmlResponse",
|
83
|
+
"type": "object",
|
84
|
+
"additionalProperties": false,
|
85
|
+
"description": "The HtmlResponse type is for returning html content. The response out of the function must be a string. This response object also includes references to resources (staticFiles) the component needs to execute correctly. It is up to the integrating system to respect this.",
|
86
|
+
"properties": {
|
87
|
+
"responseType": { "const": "html" },
|
88
|
+
"staticFiles": {
|
89
|
+
"type": "array",
|
90
|
+
"description": "A list of static resources that are required for the component to execute correctly",
|
91
|
+
"items": {
|
92
|
+
"type": "object",
|
93
|
+
"title": "HtmlStaticFile",
|
94
|
+
"additionalProperties": false,
|
95
|
+
"properties": {
|
96
|
+
"location": {
|
97
|
+
"description": "The location property is used to inform integrating system if the static resource should be included in the header of footer of the document",
|
98
|
+
"oneOf": [
|
99
|
+
{
|
100
|
+
"const": "header"
|
101
|
+
},
|
102
|
+
{
|
103
|
+
"const": "footer"
|
104
|
+
}
|
105
|
+
]
|
106
|
+
},
|
107
|
+
"file": {
|
108
|
+
"oneOf": [
|
109
|
+
{
|
110
|
+
"type": "object",
|
111
|
+
"title": "JsFile",
|
112
|
+
"additionalProperties": false,
|
113
|
+
"properties": {
|
114
|
+
"type": {
|
115
|
+
"const": "js"
|
116
|
+
},
|
117
|
+
"filepath": {
|
118
|
+
"type": "string"
|
119
|
+
},
|
120
|
+
"defer": {
|
121
|
+
"type": "boolean"
|
122
|
+
},
|
123
|
+
"async": {
|
124
|
+
"type": "boolean"
|
125
|
+
},
|
126
|
+
"integrity": {
|
127
|
+
"type": "string"
|
128
|
+
},
|
129
|
+
"crossorigin": {
|
130
|
+
"oneOf": [
|
131
|
+
{
|
132
|
+
"const": "anonymous"
|
133
|
+
},
|
134
|
+
{
|
135
|
+
"const": "use-credentials"
|
136
|
+
}
|
137
|
+
]
|
138
|
+
},
|
139
|
+
"referrerpolicy": {
|
140
|
+
"oneOf": [
|
141
|
+
{
|
142
|
+
"const": "no-referrer"
|
143
|
+
},
|
144
|
+
{
|
145
|
+
"const": "no-referrer-when-downgrade"
|
146
|
+
},
|
147
|
+
{
|
148
|
+
"const": "origin"
|
149
|
+
},
|
150
|
+
{
|
151
|
+
"const": "origin-when-cross-origin"
|
152
|
+
},
|
153
|
+
{
|
154
|
+
"const": "same-origin"
|
155
|
+
},
|
156
|
+
{
|
157
|
+
"const": "strict-origin"
|
158
|
+
},
|
159
|
+
{
|
160
|
+
"const": "strict-origin-when-cross-origin"
|
161
|
+
},
|
162
|
+
{
|
163
|
+
"const": "unsafe-url"
|
164
|
+
}
|
165
|
+
]
|
166
|
+
}
|
167
|
+
},
|
168
|
+
"required": ["filepath", "type"]
|
169
|
+
},
|
170
|
+
{
|
171
|
+
"type": "object",
|
172
|
+
"title": "CssFile",
|
173
|
+
"additionalProperties": false,
|
174
|
+
"properties": {
|
175
|
+
"type": {
|
176
|
+
"const": "css"
|
177
|
+
},
|
178
|
+
"filepath": {
|
179
|
+
"type": "string"
|
180
|
+
},
|
181
|
+
"crossorigin": {
|
182
|
+
"oneOf": [
|
183
|
+
{
|
184
|
+
"const": "anonymous"
|
185
|
+
},
|
186
|
+
{
|
187
|
+
"const": "use-credentials"
|
188
|
+
}
|
189
|
+
]
|
190
|
+
},
|
191
|
+
"referrerpolicy": {
|
192
|
+
"oneOf": [
|
193
|
+
{
|
194
|
+
"const": "no-referrer"
|
195
|
+
},
|
196
|
+
{
|
197
|
+
"const": "no-referrer-when-downgrade"
|
198
|
+
},
|
199
|
+
{
|
200
|
+
"const": "origin"
|
201
|
+
},
|
202
|
+
{
|
203
|
+
"const": "origin-when-cross-origin"
|
204
|
+
},
|
205
|
+
{
|
206
|
+
"const": "same-origin"
|
207
|
+
},
|
208
|
+
{
|
209
|
+
"const": "strict-origin"
|
210
|
+
},
|
211
|
+
{
|
212
|
+
"const": "strict-origin-when-cross-origin"
|
213
|
+
},
|
214
|
+
{
|
215
|
+
"const": "unsafe-url"
|
216
|
+
}
|
217
|
+
]
|
218
|
+
}
|
219
|
+
},
|
220
|
+
"required": ["filepath"]
|
221
|
+
}
|
222
|
+
]
|
223
|
+
}
|
224
|
+
},
|
225
|
+
"required": ["location", "file"]
|
226
|
+
}
|
227
|
+
},
|
228
|
+
"headers": {
|
229
|
+
"title": "ResponseHeaders",
|
230
|
+
"type": "object",
|
231
|
+
"description": "Response headers to set",
|
232
|
+
"additionalProperties": {
|
233
|
+
"type": "string"
|
234
|
+
}
|
235
|
+
}
|
236
|
+
},
|
237
|
+
"required": ["responseType"]
|
238
|
+
},
|
239
|
+
{
|
240
|
+
"title": "JsonResponse",
|
241
|
+
"type": "object",
|
242
|
+
"additionalProperties": false,
|
243
|
+
"properties": {
|
244
|
+
"responseType": { "const": "json" },
|
245
|
+
"definition": { "$ref": "http://json-schema.org/draft-07/schema#" },
|
246
|
+
"headers": {
|
247
|
+
"title": "ResponseHeaders",
|
248
|
+
"type": "object",
|
249
|
+
"description": "Response headers to set",
|
250
|
+
"additionalProperties": {
|
251
|
+
"type": "string"
|
252
|
+
}
|
253
|
+
}
|
254
|
+
},
|
255
|
+
"required": ["responseType", "definition"]
|
256
|
+
}
|
257
|
+
]
|
258
|
+
}
|
259
|
+
},
|
260
|
+
"required": ["name", "entry", "input", "output"]
|
261
|
+
}
|
262
|
+
},
|
263
|
+
"previews": {
|
264
|
+
"type": "object",
|
265
|
+
"description": "A map of previews which provide configuration to preview the component in isolation",
|
266
|
+
"propertyNames": {
|
267
|
+
"$ref": "#/definitions/name-pattern"
|
268
|
+
},
|
269
|
+
"minProperties": 1,
|
270
|
+
"additionalProperties": {
|
271
|
+
"title": "Preview Definition",
|
272
|
+
"type": "object",
|
273
|
+
"description": "Name for the preview and how it will be accessed",
|
274
|
+
"required": ["functionData"],
|
275
|
+
"properties": {
|
276
|
+
"functionData": {
|
277
|
+
"description": "A map of the functions to their respective preview information. Properties must match name of a function defined in functions list",
|
278
|
+
"type": "object",
|
279
|
+
"propertyNames": {
|
280
|
+
"$ref": "#/definitions/name-pattern"
|
281
|
+
},
|
282
|
+
"minProperties": 1,
|
283
|
+
"additionalProperties": {
|
284
|
+
"type": "object",
|
285
|
+
"title": "Function preview configuration",
|
286
|
+
"description": "Data inputs for the preview of functions in the component",
|
287
|
+
"properties": {
|
288
|
+
"inputData": {
|
289
|
+
"type": "object",
|
290
|
+
"description": "Property for defining the input data for this preview component function",
|
291
|
+
"properties": {
|
292
|
+
"type": {
|
293
|
+
"description": "An enum of 'file' or 'inline' for how the input data is defined",
|
294
|
+
"enum": ["file", "inline"]
|
295
|
+
}
|
296
|
+
},
|
297
|
+
"required": ["type"],
|
298
|
+
"oneOf": [
|
299
|
+
{
|
300
|
+
"type": "object",
|
301
|
+
"properties": {
|
302
|
+
"type": {
|
303
|
+
"enum": ["file"]
|
304
|
+
},
|
305
|
+
"path": {
|
306
|
+
"type": "string",
|
307
|
+
"format": "uri-reference",
|
308
|
+
"description": "Path to input data file"
|
309
|
+
}
|
310
|
+
},
|
311
|
+
"required": ["type", "path"]
|
312
|
+
},
|
313
|
+
{
|
314
|
+
"type": "object",
|
315
|
+
"properties": {
|
316
|
+
"type": {
|
317
|
+
"enum": ["inline"]
|
318
|
+
},
|
319
|
+
"value": {
|
320
|
+
"type": "object",
|
321
|
+
"description": "Input data value"
|
322
|
+
}
|
323
|
+
},
|
324
|
+
"required": ["type", "value"]
|
325
|
+
}
|
326
|
+
]
|
327
|
+
},
|
328
|
+
"headers": {
|
329
|
+
"title": "ResponseHeaders",
|
330
|
+
"type": "object",
|
331
|
+
"description": "Response headers to set",
|
332
|
+
"additionalProperties": {
|
333
|
+
"type": "string"
|
334
|
+
}
|
335
|
+
},
|
336
|
+
"wrapper": {
|
337
|
+
"type": "object",
|
338
|
+
"description": "Define the extra static files which provide a container for the component function",
|
339
|
+
"properties": {
|
340
|
+
"path": {
|
341
|
+
"type": "string",
|
342
|
+
"format": "uri-reference",
|
343
|
+
"description": "File path to the wrapper container"
|
344
|
+
}
|
345
|
+
},
|
346
|
+
"required": ["path"]
|
347
|
+
}
|
348
|
+
}
|
349
|
+
}
|
350
|
+
}
|
351
|
+
}
|
352
|
+
}
|
353
|
+
}
|
354
|
+
},
|
355
|
+
"required": ["name", "namespace", "displayName", "description", "version", "functions", "$schema", "mainFunction"],
|
356
|
+
"definitions": {
|
357
|
+
"name-pattern": {
|
358
|
+
"type": "string",
|
359
|
+
"pattern": "^[a-zA-Z0-9_\\-]+$"
|
360
|
+
}
|
361
|
+
}
|
362
|
+
}
|
@@ -0,0 +1 @@
|
|
1
|
+
export {};
|
@@ -0,0 +1,35 @@
|
|
1
|
+
"use strict";
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
3
|
+
const promises_1 = require("fs/promises");
|
4
|
+
const path_1 = require("path");
|
5
|
+
const SchemaValidationError_1 = require("../../errors/SchemaValidationError");
|
6
|
+
const JsonValidationService_1 = require("../../JsonValidationService");
|
7
|
+
async function fetchTestManifest(filename) {
|
8
|
+
const contents = await (0, promises_1.readFile)((0, path_1.resolve)(__dirname, '__test__', 'schemas', filename), {
|
9
|
+
encoding: 'utf-8',
|
10
|
+
});
|
11
|
+
return JSON.parse(contents);
|
12
|
+
}
|
13
|
+
describe('manifest/v1', () => {
|
14
|
+
let validationService;
|
15
|
+
beforeAll(() => {
|
16
|
+
validationService = new JsonValidationService_1.JsonValidationService();
|
17
|
+
});
|
18
|
+
it('succeeds on a valid manifest', async () => {
|
19
|
+
const manifest = await fetchTestManifest('validComponent.json');
|
20
|
+
expect(validationService.validateManifest(manifest, 'v1')).toBeTruthy();
|
21
|
+
});
|
22
|
+
it('errors on invalid property types in function input', async () => {
|
23
|
+
const manifest = await fetchTestManifest('badFunctionInputComponent.json');
|
24
|
+
expect(() => validationService.validateManifest(manifest, 'v1')).toThrowError(SchemaValidationError_1.SchemaValidationError);
|
25
|
+
});
|
26
|
+
it('errors on invalid property types in nested function input', async () => {
|
27
|
+
const manifest = await fetchTestManifest('badNestedFunctionInput.json');
|
28
|
+
expect(() => validationService.validateManifest(manifest, 'v1')).toThrowError(SchemaValidationError_1.SchemaValidationError);
|
29
|
+
});
|
30
|
+
it('errors on non-object top level input', async () => {
|
31
|
+
const manifest = await fetchTestManifest('nonObjectFunctionInputComponent.json');
|
32
|
+
expect(() => validationService.validateManifest(manifest, 'v1')).toThrowError(SchemaValidationError_1.SchemaValidationError);
|
33
|
+
});
|
34
|
+
});
|
35
|
+
//# sourceMappingURL=v1.spec.js.map
|
@@ -0,0 +1 @@
|
|
1
|
+
{"version":3,"file":"v1.spec.js","sourceRoot":"","sources":["../../../src/manifest/v1/v1.spec.ts"],"names":[],"mappings":";;AAAA,0CAAuC;AACvC,+BAA+B;AAC/B,8EAA2E;AAC3E,uEAAoE;AAEpE,KAAK,UAAU,iBAAiB,CAAC,QAAgB;IAC/C,MAAM,QAAQ,GAAG,MAAM,IAAA,mBAAQ,EAAC,IAAA,cAAO,EAAC,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,CAAC,EAAE;QACnF,QAAQ,EAAE,OAAO;KAClB,CAAC,CAAC;IACH,OAAO,IAAI,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC;AAC9B,CAAC;AAED,QAAQ,CAAC,aAAa,EAAE,GAAG,EAAE;IAC3B,IAAI,iBAAwC,CAAC;IAE7C,SAAS,CAAC,GAAG,EAAE;QACb,iBAAiB,GAAG,IAAI,6CAAqB,EAAE,CAAC;IAClD,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,8BAA8B,EAAE,KAAK,IAAI,EAAE;QAC5C,MAAM,QAAQ,GAAG,MAAM,iBAAiB,CAAC,qBAAqB,CAAC,CAAC;QAChE,MAAM,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,UAAU,EAAE,CAAC;IAC1E,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,oDAAoD,EAAE,KAAK,IAAI,EAAE;QAClE,MAAM,QAAQ,GAAG,MAAM,iBAAiB,CAAC,gCAAgC,CAAC,CAAC;QAC3E,MAAM,CAAC,GAAG,EAAE,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,6CAAqB,CAAC,CAAC;IACvG,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,2DAA2D,EAAE,KAAK,IAAI,EAAE;QACzE,MAAM,QAAQ,GAAG,MAAM,iBAAiB,CAAC,6BAA6B,CAAC,CAAC;QACxE,MAAM,CAAC,GAAG,EAAE,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,6CAAqB,CAAC,CAAC;IACvG,CAAC,CAAC,CAAC;IAEH,EAAE,CAAC,sCAAsC,EAAE,KAAK,IAAI,EAAE;QACpD,MAAM,QAAQ,GAAG,MAAM,iBAAiB,CAAC,sCAAsC,CAAC,CAAC;QACjF,MAAM,CAAC,GAAG,EAAE,CAAC,iBAAiB,CAAC,gBAAgB,CAAC,QAAQ,EAAE,IAAI,CAAC,CAAC,CAAC,YAAY,CAAC,6CAAqB,CAAC,CAAC;IACvG,CAAC,CAAC,CAAC;AACL,CAAC,CAAC,CAAC"}
|
package/package.json
ADDED
@@ -0,0 +1,36 @@
|
|
1
|
+
{
|
2
|
+
"name": "@squiz/dx-json-schema-lib",
|
3
|
+
"version": "1.2.13-alpha.0",
|
4
|
+
"description": "",
|
5
|
+
"main": "lib/index.js",
|
6
|
+
"scripts": {
|
7
|
+
"compile": "tsc",
|
8
|
+
"precompile": "ts-node jsonCompiler.ts",
|
9
|
+
"lint": "eslint ./src --ext .ts",
|
10
|
+
"test": "jest -c jest.config.ts",
|
11
|
+
"test:update-snapshots": "jest -c jest.config.ts --updateSnapshot",
|
12
|
+
"clean": "rimraf \"tsconfig.tsbuildinfo\" \"./lib\""
|
13
|
+
},
|
14
|
+
"author": "",
|
15
|
+
"license": "ISC",
|
16
|
+
"devDependencies": {
|
17
|
+
"@types/express": "4.17.14",
|
18
|
+
"@types/jest": "28.1.8",
|
19
|
+
"@types/node": "17.0.27",
|
20
|
+
"dotenv": "16.0.3",
|
21
|
+
"eslint": "8.22.0",
|
22
|
+
"jest": "28.1.3",
|
23
|
+
"json-schema-to-typescript": "11.0.2",
|
24
|
+
"rimraf": "3.0.2",
|
25
|
+
"ts-jest": "28.0.8",
|
26
|
+
"ts-loader": "9.3.1",
|
27
|
+
"ts-node": "10.9.1",
|
28
|
+
"typescript": "4.9.3"
|
29
|
+
},
|
30
|
+
"dependencies": {
|
31
|
+
"ajv": "8.11.2",
|
32
|
+
"ajv-formats": "2.1.1",
|
33
|
+
"better-ajv-errors": "1.2.0"
|
34
|
+
},
|
35
|
+
"gitHead": "811fbf59292dd00a2051036f1115cba5781828dd"
|
36
|
+
}
|
@@ -0,0 +1,162 @@
|
|
1
|
+
import { JsonValidationService } from './JsonValidationService';
|
2
|
+
import { SchemaValidationError } from './errors/SchemaValidationError';
|
3
|
+
|
4
|
+
import validManifest from './manifest/v1/__test__/schemas/validComponent.json';
|
5
|
+
|
6
|
+
describe('JsonValidationService', () => {
|
7
|
+
let jsonValidationService: JsonValidationService;
|
8
|
+
beforeEach(() => {
|
9
|
+
jsonValidationService = new JsonValidationService();
|
10
|
+
});
|
11
|
+
|
12
|
+
describe('validateManifest', () => {
|
13
|
+
it('should return true for a valid manifest', () => {
|
14
|
+
const result = jsonValidationService.validateManifest(validManifest, 'v1');
|
15
|
+
expect(result).toBe(true);
|
16
|
+
});
|
17
|
+
|
18
|
+
it('should throw a SchemaValidationError for an invalid manifest', () => {
|
19
|
+
const invalidManifest = { ...validManifest };
|
20
|
+
|
21
|
+
delete (invalidManifest as any).name;
|
22
|
+
|
23
|
+
expect(() => {
|
24
|
+
jsonValidationService.validateManifest(invalidManifest, 'v1');
|
25
|
+
}).toThrow(SchemaValidationError);
|
26
|
+
expect(() => {
|
27
|
+
jsonValidationService.validateManifest(invalidManifest, 'v1');
|
28
|
+
}).toMatchInlineSnapshot(`[Function]`);
|
29
|
+
});
|
30
|
+
|
31
|
+
it('should throw an error for an invalid manifest version', () => {
|
32
|
+
expect(() => {
|
33
|
+
jsonValidationService.validateManifest(validManifest, 'invalid-version' as any);
|
34
|
+
}).toThrow(Error);
|
35
|
+
});
|
36
|
+
});
|
37
|
+
|
38
|
+
describe('validateContentSchema', () => {
|
39
|
+
it('should return true for a valid content schema', () => {
|
40
|
+
const contentSchema = {
|
41
|
+
type: 'object',
|
42
|
+
|
43
|
+
properties: {
|
44
|
+
'my-input': { type: 'number' },
|
45
|
+
},
|
46
|
+
|
47
|
+
required: ['my-input'],
|
48
|
+
};
|
49
|
+
const result = jsonValidationService.validateContentSchema(contentSchema);
|
50
|
+
expect(result).toBe(true);
|
51
|
+
});
|
52
|
+
|
53
|
+
// TODO DEVX-658
|
54
|
+
it.skip('should return false for an invalid content schema, where the required property is missed from a child object', () => {
|
55
|
+
const contentSchema = {
|
56
|
+
type: 'object',
|
57
|
+
|
58
|
+
properties: {
|
59
|
+
'my-input': {
|
60
|
+
type: 'object',
|
61
|
+
|
62
|
+
properties: {
|
63
|
+
'my-deep-input': { type: 'object' },
|
64
|
+
},
|
65
|
+
},
|
66
|
+
},
|
67
|
+
|
68
|
+
required: ['my-input'],
|
69
|
+
};
|
70
|
+
expect(() => {
|
71
|
+
jsonValidationService.validateContentSchema(contentSchema);
|
72
|
+
}).toThrow(SchemaValidationError);
|
73
|
+
});
|
74
|
+
|
75
|
+
it('should throw a SchemaValidationError for an invalid content schema, top level type must be object', () => {
|
76
|
+
const contentSchema = {
|
77
|
+
type: 'array',
|
78
|
+
|
79
|
+
items: {
|
80
|
+
type: 'object',
|
81
|
+
properties: {
|
82
|
+
'my-input': { type: 'number' },
|
83
|
+
},
|
84
|
+
required: ['my-input'],
|
85
|
+
},
|
86
|
+
};
|
87
|
+
expect(() => {
|
88
|
+
jsonValidationService.validateContentSchema(contentSchema);
|
89
|
+
}).toThrow(SchemaValidationError);
|
90
|
+
});
|
91
|
+
|
92
|
+
// TODO DEVX-658
|
93
|
+
it.skip('should throw a SchemaValidationError for an invalid content schema missing the required property', () => {
|
94
|
+
const contentSchema = {
|
95
|
+
type: 'object',
|
96
|
+
|
97
|
+
properties: {
|
98
|
+
'my-input': { type: 'number' },
|
99
|
+
},
|
100
|
+
};
|
101
|
+
expect(() => {
|
102
|
+
jsonValidationService.validateContentSchema(contentSchema);
|
103
|
+
}).toThrow(SchemaValidationError);
|
104
|
+
});
|
105
|
+
});
|
106
|
+
|
107
|
+
describe('validateComponentInput', () => {
|
108
|
+
const functionInputSchema = {
|
109
|
+
type: 'object',
|
110
|
+
|
111
|
+
properties: {
|
112
|
+
'my-input': { type: 'number' },
|
113
|
+
},
|
114
|
+
|
115
|
+
required: ['my-input'],
|
116
|
+
};
|
117
|
+
|
118
|
+
it('should return true for valid input values', () => {
|
119
|
+
const inputValue = {
|
120
|
+
'my-input': 123,
|
121
|
+
};
|
122
|
+
const result = jsonValidationService.validateComponentInput(functionInputSchema, inputValue);
|
123
|
+
expect(result).toBe(true);
|
124
|
+
});
|
125
|
+
|
126
|
+
it('should throw a SchemaValidationError for invalid input type', () => {
|
127
|
+
const inputValue = {
|
128
|
+
'my-input': '123',
|
129
|
+
};
|
130
|
+
expect(() => {
|
131
|
+
jsonValidationService.validateComponentInput(functionInputSchema, inputValue);
|
132
|
+
}).toThrow(SchemaValidationError);
|
133
|
+
expect(() => {
|
134
|
+
jsonValidationService.validateComponentInput(functionInputSchema, inputValue);
|
135
|
+
}).toMatchInlineSnapshot(`[Function]`);
|
136
|
+
});
|
137
|
+
|
138
|
+
it('should throw a SchemaValidationError for invalid input missing properties', () => {
|
139
|
+
const inputValue = {};
|
140
|
+
expect(() => {
|
141
|
+
jsonValidationService.validateComponentInput(functionInputSchema, inputValue);
|
142
|
+
}).toThrow(SchemaValidationError);
|
143
|
+
expect(() => {
|
144
|
+
jsonValidationService.validateComponentInput(functionInputSchema, inputValue);
|
145
|
+
}).toMatchInlineSnapshot(`[Function]`);
|
146
|
+
});
|
147
|
+
|
148
|
+
// TODO DEVX-658
|
149
|
+
it.skip('should throw a SchemaValidationError for invalid input additional properties', () => {
|
150
|
+
const inputValue = {
|
151
|
+
'my-input': 123,
|
152
|
+
'my-input-2': 123,
|
153
|
+
};
|
154
|
+
expect(() => {
|
155
|
+
jsonValidationService.validateComponentInput(functionInputSchema, inputValue);
|
156
|
+
}).toThrow(SchemaValidationError);
|
157
|
+
expect(() => {
|
158
|
+
jsonValidationService.validateComponentInput(functionInputSchema, inputValue);
|
159
|
+
}).toMatchInlineSnapshot();
|
160
|
+
});
|
161
|
+
});
|
162
|
+
});
|
@@ -0,0 +1,54 @@
|
|
1
|
+
import Ajv, { AnySchemaObject, SchemaObject } from 'ajv';
|
2
|
+
import addFormats from 'ajv-formats';
|
3
|
+
import betterAjvErrors from 'better-ajv-errors';
|
4
|
+
|
5
|
+
import DxComponentInputSchema from './manifest/v1/DxComponentInputSchema.json';
|
6
|
+
import DxComponentIcons from './manifest/v1/DxComponentIcons.json';
|
7
|
+
import DxContentMetaSchema from './manifest/v1/DxContentMetaSchema.json';
|
8
|
+
import v1 from './manifest/v1/v1.json';
|
9
|
+
import { SchemaValidationError } from './errors/SchemaValidationError';
|
10
|
+
|
11
|
+
export class JsonValidationService {
|
12
|
+
ajv: Ajv;
|
13
|
+
constructor() {
|
14
|
+
this.ajv = new Ajv({
|
15
|
+
strict: true,
|
16
|
+
schemas: { DxContentMetaSchema, v1, DxComponentIcons, DxComponentInputSchema },
|
17
|
+
allowUnionTypes: true,
|
18
|
+
allErrors: true,
|
19
|
+
formats: {
|
20
|
+
'multi-line': true,
|
21
|
+
},
|
22
|
+
});
|
23
|
+
|
24
|
+
addFormats(this.ajv);
|
25
|
+
}
|
26
|
+
validateManifest(manifest: unknown, version: 'v1') {
|
27
|
+
switch (version) {
|
28
|
+
case 'v1':
|
29
|
+
return this.doValidate(v1, manifest);
|
30
|
+
|
31
|
+
default:
|
32
|
+
throw new Error('Invalid manifest version');
|
33
|
+
}
|
34
|
+
}
|
35
|
+
|
36
|
+
validateContentSchema(contentSchema: AnySchemaObject) {
|
37
|
+
// This schema matches the input definition in the manifest
|
38
|
+
return this.doValidate(DxComponentInputSchema, contentSchema);
|
39
|
+
}
|
40
|
+
|
41
|
+
validateComponentInput(functionInputSchema: SchemaObject, inputValue: unknown) {
|
42
|
+
return this.doValidate(functionInputSchema, inputValue);
|
43
|
+
}
|
44
|
+
|
45
|
+
private doValidate(schema: string | SchemaObject, value: unknown): true {
|
46
|
+
const result = this.ajv.validate(schema, value);
|
47
|
+
if (!result) {
|
48
|
+
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
|
49
|
+
const betterErrors = betterAjvErrors(schema, value, this.ajv.errors!, { format: 'js' });
|
50
|
+
throw new SchemaValidationError(betterErrors);
|
51
|
+
}
|
52
|
+
return result;
|
53
|
+
}
|
54
|
+
}
|