@squiz/dx-json-schema-lib 1.2.13-alpha.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (76) hide show
  1. package/CHANGELOG.md +8 -0
  2. package/jest.config.ts +20 -0
  3. package/jsonCompiler.ts +22 -0
  4. package/lib/JsonValidationService.d.ts +9 -0
  5. package/lib/JsonValidationService.js +54 -0
  6. package/lib/JsonValidationService.js.map +1 -0
  7. package/lib/JsonValidationService.spec.d.ts +1 -0
  8. package/lib/JsonValidationService.spec.js +143 -0
  9. package/lib/JsonValidationService.spec.js.map +1 -0
  10. package/lib/errors/SchemaValidationError.d.ts +4 -0
  11. package/lib/errors/SchemaValidationError.js +12 -0
  12. package/lib/errors/SchemaValidationError.js.map +1 -0
  13. package/lib/formatted-text/v1/formattedText.d.ts +51 -0
  14. package/lib/formatted-text/v1/formattedText.js +9 -0
  15. package/lib/formatted-text/v1/formattedText.js.map +1 -0
  16. package/lib/formatted-text/v1/formattedText.json +134 -0
  17. package/lib/formatted-text/v1/formattedTextModels.d.ts +1 -0
  18. package/lib/formatted-text/v1/formattedTextModels.js +28 -0
  19. package/lib/formatted-text/v1/formattedTextModels.js.map +1 -0
  20. package/lib/formatted-text/v1/formattedTextSchemas.d.ts +2 -0
  21. package/lib/formatted-text/v1/formattedTextSchemas.js +9 -0
  22. package/lib/formatted-text/v1/formattedTextSchemas.js.map +1 -0
  23. package/lib/index.d.ts +7 -0
  24. package/lib/index.js +37 -0
  25. package/lib/index.js.map +1 -0
  26. package/lib/manifest/v1/DxComponentIcons.json +2279 -0
  27. package/lib/manifest/v1/DxComponentInputSchema.json +20 -0
  28. package/lib/manifest/v1/DxComponentInputSchema.spec.d.ts +1 -0
  29. package/lib/manifest/v1/DxComponentInputSchema.spec.js +113 -0
  30. package/lib/manifest/v1/DxComponentInputSchema.spec.js.map +1 -0
  31. package/lib/manifest/v1/DxContentMetaSchema.json +165 -0
  32. package/lib/manifest/v1/__test__/schemas/badFunctionInputComponent.json +39 -0
  33. package/lib/manifest/v1/__test__/schemas/badNestedFunctionInput.json +39 -0
  34. package/lib/manifest/v1/__test__/schemas/nonObjectFunctionInputComponent.json +39 -0
  35. package/lib/manifest/v1/__test__/schemas/validComponent.json +40 -0
  36. package/lib/manifest/v1/manifestModels.d.ts +1 -0
  37. package/lib/manifest/v1/manifestModels.js +28 -0
  38. package/lib/manifest/v1/manifestModels.js.map +1 -0
  39. package/lib/manifest/v1/manifestSchemas.d.ts +2 -0
  40. package/lib/manifest/v1/manifestSchemas.js +9 -0
  41. package/lib/manifest/v1/manifestSchemas.js.map +1 -0
  42. package/lib/manifest/v1/subSchemas.d.ts +4 -0
  43. package/lib/manifest/v1/subSchemas.js +13 -0
  44. package/lib/manifest/v1/subSchemas.js.map +1 -0
  45. package/lib/manifest/v1/v1.d.ts +460 -0
  46. package/lib/manifest/v1/v1.js +9 -0
  47. package/lib/manifest/v1/v1.js.map +1 -0
  48. package/lib/manifest/v1/v1.json +362 -0
  49. package/lib/manifest/v1/v1.spec.d.ts +1 -0
  50. package/lib/manifest/v1/v1.spec.js +35 -0
  51. package/lib/manifest/v1/v1.spec.js.map +1 -0
  52. package/package.json +36 -0
  53. package/src/JsonValidationService.spec.ts +162 -0
  54. package/src/JsonValidationService.ts +54 -0
  55. package/src/errors/SchemaValidationError.ts +9 -0
  56. package/src/formatted-text/v1/formattedText.json +145 -0
  57. package/src/formatted-text/v1/formattedText.ts +54 -0
  58. package/src/formatted-text/v1/formattedTextModels.ts +1 -0
  59. package/src/formatted-text/v1/formattedTextSchemas.ts +3 -0
  60. package/src/index.ts +10 -0
  61. package/src/manifest/v1/DxComponentIcons.json +2279 -0
  62. package/src/manifest/v1/DxComponentInputSchema.json +20 -0
  63. package/src/manifest/v1/DxComponentInputSchema.spec.ts +136 -0
  64. package/src/manifest/v1/DxContentMetaSchema.json +165 -0
  65. package/src/manifest/v1/__test__/schemas/badFunctionInputComponent.json +39 -0
  66. package/src/manifest/v1/__test__/schemas/badNestedFunctionInput.json +39 -0
  67. package/src/manifest/v1/__test__/schemas/nonObjectFunctionInputComponent.json +39 -0
  68. package/src/manifest/v1/__test__/schemas/validComponent.json +40 -0
  69. package/src/manifest/v1/manifestModels.ts +1 -0
  70. package/src/manifest/v1/manifestSchemas.ts +3 -0
  71. package/src/manifest/v1/subSchemas.ts +5 -0
  72. package/src/manifest/v1/v1.json +369 -0
  73. package/src/manifest/v1/v1.spec.ts +39 -0
  74. package/src/manifest/v1/v1.ts +2731 -0
  75. package/tsconfig.json +16 -0
  76. 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
+ }