@squiz/dx-json-schema-lib 1.2.13-alpha.1 → 1.2.13-alpha.3
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 +7 -0
- package/lib/JsonValidationService.d.ts +4 -6
- package/lib/JsonValidationService.js +90 -28
- package/lib/JsonValidationService.js.map +1 -1
- package/lib/JsonValidationService.spec.js +276 -65
- package/lib/JsonValidationService.spec.js.map +1 -1
- package/lib/errors/SchemaValidationError.d.ts +1 -1
- package/lib/errors/SchemaValidationError.js +8 -1
- package/lib/errors/SchemaValidationError.js.map +1 -1
- package/lib/formatted-text/v1/formattedText.d.ts +18 -16
- package/lib/formatted-text/v1/formattedText.json +59 -88
- package/lib/formatted-text/v1/higherOrderFormattedTextToBaseFormattedText.d.ts +8 -0
- package/lib/formatted-text/v1/higherOrderFormattedTextToBaseFormattedText.js +21 -0
- package/lib/formatted-text/v1/higherOrderFormattedTextToBaseFormattedText.js.map +1 -0
- package/lib/formatted-text/v1/higherOrderFormattedTextToBaseFormattedText.spec.d.ts +1 -0
- package/lib/formatted-text/v1/higherOrderFormattedTextToBaseFormattedText.spec.js +52 -0
- package/lib/formatted-text/v1/higherOrderFormattedTextToBaseFormattedText.spec.js.map +1 -0
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/manifest/v1/Draft-07.json +155 -0
- package/lib/manifest/v1/DxComponentInputSchema.json +11 -8
- package/lib/manifest/v1/DxComponentInputSchema.spec.js +23 -9
- package/lib/manifest/v1/DxComponentInputSchema.spec.js.map +1 -1
- package/lib/manifest/v1/__test__/schemas/badFunctionInputComponent.json +2 -1
- package/lib/manifest/v1/__test__/schemas/badNestedFunctionInput.json +2 -1
- package/lib/manifest/v1/__test__/schemas/nonObjectFunctionInputComponent.json +2 -1
- package/lib/manifest/v1/__test__/schemas/validComponentJson.json +48 -0
- package/lib/manifest/v1/v1.d.ts +5 -7
- package/lib/manifest/v1/v1.spec.js +22 -3
- package/lib/manifest/v1/v1.spec.js.map +1 -1
- package/package.json +4 -3
- package/src/JsonValidationService.spec.ts +379 -70
- package/src/JsonValidationService.ts +112 -34
- package/src/errors/SchemaValidationError.ts +10 -2
- package/src/formatted-text/v1/formattedText.json +65 -89
- package/src/formatted-text/v1/formattedText.ts +19 -17
- package/src/formatted-text/v1/higherOrderFormattedTextToBaseFormattedText.spec.ts +58 -0
- package/src/formatted-text/v1/higherOrderFormattedTextToBaseFormattedText.ts +36 -0
- package/src/index.ts +1 -0
- package/src/manifest/v1/Draft-07.json +155 -0
- package/src/manifest/v1/DxComponentInputSchema.json +11 -8
- package/src/manifest/v1/DxComponentInputSchema.spec.ts +63 -35
- package/src/manifest/v1/__test__/schemas/badFunctionInputComponent.json +2 -1
- package/src/manifest/v1/__test__/schemas/badNestedFunctionInput.json +2 -1
- package/src/manifest/v1/__test__/schemas/nonObjectFunctionInputComponent.json +2 -1
- package/src/manifest/v1/__test__/schemas/validComponentJson.json +48 -0
- package/src/manifest/v1/v1.spec.ts +37 -3
- package/src/manifest/v1/v1.ts +5 -7
- package/tsconfig.json +3 -1
- package/tsconfig.tsbuildinfo +1 -1
@@ -2,14 +2,33 @@ import { JsonValidationService } from './JsonValidationService';
|
|
2
2
|
import { SchemaValidationError } from './errors/SchemaValidationError';
|
3
3
|
|
4
4
|
import validManifest from './manifest/v1/__test__/schemas/validComponent.json';
|
5
|
+
import validManifestJson from './manifest/v1/__test__/schemas/validComponentJson.json';
|
6
|
+
import { FormattedText } from './formatted-text/v1/formattedText';
|
7
|
+
|
8
|
+
// eslint-disable-next-line @typescript-eslint/ban-types
|
9
|
+
function expectToThrowErrorMatchingTypeAndMessage(received: Function, errorType: Function, message: string) {
|
10
|
+
let error: null | Error = null;
|
11
|
+
|
12
|
+
try {
|
13
|
+
received();
|
14
|
+
} catch (e: any) {
|
15
|
+
error = e;
|
16
|
+
}
|
17
|
+
|
18
|
+
expect(error).toBeDefined();
|
19
|
+
expect(error?.message).toEqual(message);
|
20
|
+
expect(error).toBeInstanceOf(errorType);
|
21
|
+
}
|
5
22
|
|
6
23
|
describe('JsonValidationService', () => {
|
7
|
-
|
8
|
-
beforeEach(() => {
|
9
|
-
jsonValidationService = new JsonValidationService();
|
10
|
-
});
|
24
|
+
const jsonValidationService: JsonValidationService = new JsonValidationService();
|
11
25
|
|
12
26
|
describe('validateManifest', () => {
|
27
|
+
it('should return true for a valid JSON endpoint manifest', () => {
|
28
|
+
const result = jsonValidationService.validateManifest(validManifestJson, 'v1');
|
29
|
+
expect(result).toBe(true);
|
30
|
+
});
|
31
|
+
|
13
32
|
it('should return true for a valid manifest', () => {
|
14
33
|
const result = jsonValidationService.validateManifest(validManifest, 'v1');
|
15
34
|
expect(result).toBe(true);
|
@@ -20,18 +39,23 @@ describe('JsonValidationService', () => {
|
|
20
39
|
|
21
40
|
delete (invalidManifest as any).name;
|
22
41
|
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
42
|
+
expectToThrowErrorMatchingTypeAndMessage(
|
43
|
+
() => {
|
44
|
+
jsonValidationService.validateManifest(invalidManifest, 'v1');
|
45
|
+
},
|
46
|
+
SchemaValidationError,
|
47
|
+
`failed validation: The required property \`name\` is missing at \`#\``,
|
48
|
+
);
|
29
49
|
});
|
30
50
|
|
31
51
|
it('should throw an error for an invalid manifest version', () => {
|
32
|
-
|
33
|
-
|
34
|
-
|
52
|
+
expectToThrowErrorMatchingTypeAndMessage(
|
53
|
+
() => {
|
54
|
+
jsonValidationService.validateManifest(validManifest, 'invalid-version' as any);
|
55
|
+
},
|
56
|
+
SchemaValidationError,
|
57
|
+
'failed validation: Invalid manifest version',
|
58
|
+
);
|
35
59
|
});
|
36
60
|
});
|
37
61
|
|
@@ -50,8 +74,7 @@ describe('JsonValidationService', () => {
|
|
50
74
|
expect(result).toBe(true);
|
51
75
|
});
|
52
76
|
|
53
|
-
|
54
|
-
it.skip('should return false for an invalid content schema, where the required property is missed from a child object', () => {
|
77
|
+
it('should return false for an invalid content schema, where the required property is missed from a child object', () => {
|
55
78
|
const contentSchema = {
|
56
79
|
type: 'object',
|
57
80
|
|
@@ -67,9 +90,13 @@ describe('JsonValidationService', () => {
|
|
67
90
|
|
68
91
|
required: ['my-input'],
|
69
92
|
};
|
70
|
-
|
71
|
-
|
72
|
-
|
93
|
+
expectToThrowErrorMatchingTypeAndMessage(
|
94
|
+
() => {
|
95
|
+
jsonValidationService.validateContentSchema(contentSchema);
|
96
|
+
},
|
97
|
+
SchemaValidationError,
|
98
|
+
'failed validation: The required property `required` is missing at `#/properties/my-input`',
|
99
|
+
);
|
73
100
|
});
|
74
101
|
|
75
102
|
it('should throw a SchemaValidationError for an invalid content schema, top level type must be object', () => {
|
@@ -84,13 +111,16 @@ describe('JsonValidationService', () => {
|
|
84
111
|
required: ['my-input'],
|
85
112
|
},
|
86
113
|
};
|
87
|
-
|
88
|
-
|
89
|
-
|
114
|
+
expectToThrowErrorMatchingTypeAndMessage(
|
115
|
+
() => {
|
116
|
+
jsonValidationService.validateContentSchema(contentSchema);
|
117
|
+
},
|
118
|
+
SchemaValidationError,
|
119
|
+
`failed validation: Expected value at \`#/type\` to be \`object\`, but value given is \`array\``,
|
120
|
+
);
|
90
121
|
});
|
91
122
|
|
92
|
-
|
93
|
-
it.skip('should throw a SchemaValidationError for an invalid content schema missing the required property', () => {
|
123
|
+
it('should throw a SchemaValidationError for an invalid content schema missing the required property', () => {
|
94
124
|
const contentSchema = {
|
95
125
|
type: 'object',
|
96
126
|
|
@@ -98,65 +128,344 @@ describe('JsonValidationService', () => {
|
|
98
128
|
'my-input': { type: 'number' },
|
99
129
|
},
|
100
130
|
};
|
101
|
-
|
102
|
-
|
103
|
-
|
131
|
+
expectToThrowErrorMatchingTypeAndMessage(
|
132
|
+
() => {
|
133
|
+
jsonValidationService.validateContentSchema(contentSchema);
|
134
|
+
},
|
135
|
+
SchemaValidationError,
|
136
|
+
'failed validation: The required property `required` is missing at `#`',
|
137
|
+
);
|
104
138
|
});
|
105
139
|
});
|
106
140
|
|
107
141
|
describe('validateComponentInput', () => {
|
108
|
-
|
109
|
-
type
|
142
|
+
describe('FormattedText input', () => {
|
143
|
+
it('should handle type as an array', () => {
|
144
|
+
const functionInputSchema = {
|
145
|
+
type: 'object',
|
110
146
|
|
111
|
-
|
112
|
-
|
113
|
-
|
147
|
+
properties: {
|
148
|
+
'my-input': { type: ['number', 'string'] },
|
149
|
+
},
|
114
150
|
|
115
|
-
|
116
|
-
|
151
|
+
required: ['my-input'],
|
152
|
+
};
|
117
153
|
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
expect(result).toBe(true);
|
124
|
-
});
|
154
|
+
expect(
|
155
|
+
jsonValidationService.validateComponentInput(functionInputSchema, {
|
156
|
+
'my-input': 'formattedText',
|
157
|
+
}),
|
158
|
+
).toEqual(true);
|
125
159
|
|
126
|
-
|
127
|
-
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
160
|
+
expect(
|
161
|
+
jsonValidationService.validateComponentInput(functionInputSchema, {
|
162
|
+
'my-input': 123,
|
163
|
+
}),
|
164
|
+
).toEqual(true);
|
165
|
+
});
|
166
|
+
|
167
|
+
it('should handle type is an array of both string and FormattedText', () => {
|
168
|
+
const formattedText: FormattedText = [
|
169
|
+
{
|
170
|
+
tag: 'p',
|
171
|
+
type: 'tag',
|
172
|
+
children: [
|
173
|
+
{ type: 'text', value: 'This is some ' },
|
174
|
+
{ type: 'text', value: 'Link to asset 12345' },
|
175
|
+
{ type: 'text', value: ' with an image ' },
|
176
|
+
{ type: 'text', value: '.' },
|
177
|
+
],
|
178
|
+
},
|
179
|
+
];
|
180
|
+
const functionInputSchema = {
|
181
|
+
type: 'object',
|
182
|
+
|
183
|
+
properties: {
|
184
|
+
'my-input': { type: ['FormattedText', 'string'] },
|
185
|
+
},
|
186
|
+
|
187
|
+
required: ['my-input'],
|
188
|
+
};
|
189
|
+
|
190
|
+
expect(
|
191
|
+
jsonValidationService.validateComponentInput(functionInputSchema, {
|
192
|
+
'my-input': formattedText,
|
193
|
+
}),
|
194
|
+
).toEqual(true);
|
195
|
+
|
196
|
+
expect(
|
197
|
+
jsonValidationService.validateComponentInput(functionInputSchema, {
|
198
|
+
'my-input': 'hello',
|
199
|
+
}),
|
200
|
+
).toEqual(true);
|
201
|
+
});
|
202
|
+
|
203
|
+
it('should return true if input is formatted text', () => {
|
204
|
+
const functionInputSchema = {
|
205
|
+
type: 'object',
|
206
|
+
|
207
|
+
properties: {
|
208
|
+
'my-input': { type: 'FormattedText' },
|
209
|
+
},
|
210
|
+
|
211
|
+
required: ['my-input'],
|
212
|
+
};
|
213
|
+
|
214
|
+
const formattedText: FormattedText = [
|
215
|
+
{
|
216
|
+
tag: 'p',
|
217
|
+
type: 'tag',
|
218
|
+
children: [
|
219
|
+
{ type: 'text', value: 'This is some ' },
|
220
|
+
{ type: 'text', value: 'Link to asset 12345' },
|
221
|
+
{ type: 'text', value: ' with an image ' },
|
222
|
+
{ type: 'text', value: '.' },
|
223
|
+
],
|
224
|
+
},
|
225
|
+
];
|
226
|
+
const inputValue = {
|
227
|
+
'my-input': formattedText,
|
228
|
+
};
|
229
|
+
|
230
|
+
expect(jsonValidationService.validateComponentInput(functionInputSchema, inputValue)).toEqual(true);
|
231
|
+
});
|
232
|
+
|
233
|
+
it('should throw an error if the FormattedText input is not formatted text', () => {
|
234
|
+
const functionInputSchema = {
|
235
|
+
type: 'object',
|
236
|
+
|
237
|
+
properties: {
|
238
|
+
'my-input': { type: 'FormattedText' },
|
239
|
+
},
|
240
|
+
|
241
|
+
required: ['my-input'],
|
242
|
+
};
|
243
|
+
const inputValue = {
|
244
|
+
'my-input': 123,
|
245
|
+
};
|
246
|
+
expectToThrowErrorMatchingTypeAndMessage(
|
247
|
+
() => {
|
248
|
+
jsonValidationService.validateComponentInput(functionInputSchema, inputValue);
|
249
|
+
},
|
250
|
+
SchemaValidationError,
|
251
|
+
`failed validation: Expected \`123\` (number) in \`#/my-input\` to be of type \`array\``,
|
252
|
+
);
|
253
|
+
});
|
254
|
+
|
255
|
+
it('should throw an error if the FormattedText input is invalid formatted text', () => {
|
256
|
+
const functionInputSchema = {
|
257
|
+
type: 'object',
|
258
|
+
|
259
|
+
properties: {
|
260
|
+
'my-input': { type: 'FormattedText' },
|
261
|
+
},
|
262
|
+
|
263
|
+
required: ['my-input'],
|
264
|
+
};
|
265
|
+
const inputValue = {
|
266
|
+
'my-input': {
|
267
|
+
something: 'aa',
|
268
|
+
},
|
269
|
+
};
|
270
|
+
expectToThrowErrorMatchingTypeAndMessage(
|
271
|
+
() => {
|
272
|
+
jsonValidationService.validateComponentInput(functionInputSchema, inputValue);
|
273
|
+
},
|
274
|
+
SchemaValidationError,
|
275
|
+
`failed validation: Expected \`[object Object]\` (object) in \`#/my-input\` to be of type \`array\``,
|
276
|
+
);
|
277
|
+
});
|
278
|
+
|
279
|
+
it('should throw an error if the FormattedText input is invalid formatted text (deeply nested)', () => {
|
280
|
+
const functionInputSchema = {
|
281
|
+
type: 'object',
|
282
|
+
|
283
|
+
properties: {
|
284
|
+
'my-input': { type: 'FormattedText' },
|
285
|
+
},
|
286
|
+
|
287
|
+
required: ['my-input'],
|
288
|
+
};
|
289
|
+
const formattedText: FormattedText = [
|
290
|
+
{
|
291
|
+
tag: 'p',
|
292
|
+
type: 'tag',
|
293
|
+
children: [
|
294
|
+
{ type: 'text', value: 'This is some ' },
|
295
|
+
{ type: 'text', value: 'Link to asset 12345' },
|
296
|
+
{ type: 'text', value: ' with an image ' },
|
297
|
+
{ type: 'text', value: 123 as any }, // see here
|
298
|
+
{ type: 'text', value: '.' },
|
299
|
+
],
|
300
|
+
},
|
301
|
+
];
|
302
|
+
|
303
|
+
const inputValue = {
|
304
|
+
'my-input': formattedText,
|
305
|
+
};
|
306
|
+
expectToThrowErrorMatchingTypeAndMessage(
|
307
|
+
() => {
|
308
|
+
jsonValidationService.validateComponentInput(functionInputSchema, inputValue);
|
309
|
+
},
|
310
|
+
SchemaValidationError,
|
311
|
+
'failed validation: Object at `#/my-input/0` does not match any schema',
|
312
|
+
);
|
313
|
+
});
|
314
|
+
|
315
|
+
it('should validate an array of formatted texts', () => {
|
316
|
+
const formattedText: FormattedText = [
|
317
|
+
{
|
318
|
+
tag: 'p',
|
319
|
+
type: 'tag',
|
320
|
+
children: [{ type: 'text', value: 'hello' }],
|
321
|
+
},
|
322
|
+
];
|
323
|
+
|
324
|
+
const schema = {
|
325
|
+
type: 'object',
|
326
|
+
properties: {
|
327
|
+
arr: {
|
328
|
+
type: 'array',
|
329
|
+
items: { type: 'FormattedText' },
|
330
|
+
},
|
331
|
+
},
|
332
|
+
};
|
333
|
+
const value = {
|
334
|
+
arr: [formattedText],
|
335
|
+
};
|
336
|
+
|
337
|
+
expect(jsonValidationService.validateComponentInput(schema, value)).toEqual(true);
|
338
|
+
});
|
339
|
+
|
340
|
+
it('should throw an error if the FormattedText input is invalid formatted text (deeply, deeply nested)', () => {
|
341
|
+
const formattedText: FormattedText = [
|
342
|
+
{
|
343
|
+
tag: 'p',
|
344
|
+
type: 'tag',
|
345
|
+
children: [{ type: 'text', value: 'hello' }],
|
346
|
+
},
|
347
|
+
];
|
348
|
+
|
349
|
+
const inputValue: any = {
|
350
|
+
'my-input': formattedText,
|
351
|
+
deep: {
|
352
|
+
arr: [
|
353
|
+
{
|
354
|
+
prop: formattedText,
|
355
|
+
formattedTextArray: [formattedText, formattedText, { bad: 'data' }],
|
356
|
+
},
|
357
|
+
],
|
358
|
+
},
|
359
|
+
};
|
137
360
|
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
361
|
+
const functionInputSchema = {
|
362
|
+
type: 'object',
|
363
|
+
|
364
|
+
properties: {
|
365
|
+
'my-input': { type: 'FormattedText' },
|
366
|
+
deep: {
|
367
|
+
type: 'object',
|
368
|
+
properties: {
|
369
|
+
arr: {
|
370
|
+
type: 'array',
|
371
|
+
items: {
|
372
|
+
type: 'object',
|
373
|
+
required: ['prop', 'formattedTextArray'],
|
374
|
+
properties: {
|
375
|
+
prop: 'FormattedText',
|
376
|
+
formattedTextArray: {
|
377
|
+
type: 'array',
|
378
|
+
items: {
|
379
|
+
type: 'FormattedText',
|
380
|
+
},
|
381
|
+
},
|
382
|
+
},
|
383
|
+
},
|
384
|
+
},
|
385
|
+
},
|
386
|
+
required: ['arr'],
|
387
|
+
},
|
388
|
+
},
|
389
|
+
|
390
|
+
required: ['my-input', 'deep'],
|
391
|
+
};
|
392
|
+
|
393
|
+
expectToThrowErrorMatchingTypeAndMessage(
|
394
|
+
() => {
|
395
|
+
jsonValidationService.validateComponentInput(functionInputSchema, inputValue);
|
396
|
+
},
|
397
|
+
SchemaValidationError,
|
398
|
+
'failed validation: Expected `[object Object]` (object) in `#/deep/arr/0/formattedTextArray/2` to be of type `array`',
|
399
|
+
);
|
400
|
+
});
|
146
401
|
});
|
147
402
|
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
403
|
+
describe('standard inputs', () => {
|
404
|
+
const functionInputSchema = {
|
405
|
+
type: 'object',
|
406
|
+
|
407
|
+
properties: {
|
408
|
+
'my-input': { type: 'number' },
|
409
|
+
},
|
410
|
+
|
411
|
+
required: ['my-input'],
|
153
412
|
};
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
413
|
+
|
414
|
+
it('should return true for valid input values', () => {
|
415
|
+
const inputValue = {
|
416
|
+
'my-input': 123,
|
417
|
+
};
|
418
|
+
const result = jsonValidationService.validateComponentInput(functionInputSchema, inputValue);
|
419
|
+
expect(result).toBe(true);
|
420
|
+
});
|
421
|
+
|
422
|
+
it('should throw a SchemaValidationError for invalid input type', () => {
|
423
|
+
const inputValue = {
|
424
|
+
'my-input': '123',
|
425
|
+
};
|
426
|
+
expectToThrowErrorMatchingTypeAndMessage(
|
427
|
+
() => {
|
428
|
+
jsonValidationService.validateComponentInput(functionInputSchema, inputValue);
|
429
|
+
},
|
430
|
+
SchemaValidationError,
|
431
|
+
`failed validation: Expected \`123\` (string) in \`#/my-input\` to be of type \`number\``,
|
432
|
+
);
|
433
|
+
});
|
434
|
+
|
435
|
+
it('should throw a SchemaValidationError for invalid input missing properties', () => {
|
436
|
+
const inputValue = {};
|
437
|
+
expectToThrowErrorMatchingTypeAndMessage(
|
438
|
+
() => {
|
439
|
+
jsonValidationService.validateComponentInput(functionInputSchema, inputValue);
|
440
|
+
},
|
441
|
+
SchemaValidationError,
|
442
|
+
`failed validation: The required property \`my-input\` is missing at \`#\``,
|
443
|
+
);
|
444
|
+
});
|
445
|
+
|
446
|
+
it('should throw a SchemaValidationError with a truncated enum value list if there are more than 5 enum options', () => {
|
447
|
+
expectToThrowErrorMatchingTypeAndMessage(
|
448
|
+
() => {
|
449
|
+
jsonValidationService.validateComponentInput(
|
450
|
+
{ type: 'object', properties: { enum: { type: 'string', enum: ['a', 'b', 'c', 'd', 'e', 'f', 'g'] } } },
|
451
|
+
{ enum: 'z' },
|
452
|
+
);
|
453
|
+
},
|
454
|
+
SchemaValidationError,
|
455
|
+
'failed validation: Expected given value `z` in #/enum` to be one of `[a, b, c, d, e, ... 2 more]`',
|
456
|
+
);
|
457
|
+
});
|
458
|
+
|
459
|
+
// TODO DEVX-658
|
460
|
+
it.skip('should throw a SchemaValidationError for invalid input additional properties', () => {
|
461
|
+
const inputValue = {
|
462
|
+
'my-input': 123,
|
463
|
+
'my-input-2': 123,
|
464
|
+
};
|
465
|
+
expect(() => {
|
466
|
+
jsonValidationService.validateComponentInput(functionInputSchema, inputValue);
|
467
|
+
}).toThrowErrorMatchingInlineSnapshot();
|
468
|
+
});
|
160
469
|
});
|
161
470
|
});
|
162
471
|
});
|
@@ -1,54 +1,132 @@
|
|
1
|
-
import Ajv, { AnySchemaObject, SchemaObject } from 'ajv';
|
2
|
-
import addFormats from 'ajv-formats';
|
3
|
-
import betterAjvErrors from 'better-ajv-errors';
|
4
|
-
|
5
1
|
import DxComponentInputSchema from './manifest/v1/DxComponentInputSchema.json';
|
6
2
|
import DxComponentIcons from './manifest/v1/DxComponentIcons.json';
|
7
3
|
import DxContentMetaSchema from './manifest/v1/DxContentMetaSchema.json';
|
4
|
+
import Draft07Schema from './manifest/v1/Draft-07.json';
|
5
|
+
|
6
|
+
import FormattedText from './formatted-text/v1/formattedText.json';
|
7
|
+
|
8
8
|
import v1 from './manifest/v1/v1.json';
|
9
9
|
import { SchemaValidationError } from './errors/SchemaValidationError';
|
10
|
+
import { Draft07, JSONError, JSONSchema, Draft, DraftConfig } from 'json-schema-library';
|
11
|
+
|
12
|
+
import { draft07Config } from 'json-schema-library';
|
13
|
+
import { MANIFEST_MODELS } from '.';
|
14
|
+
|
15
|
+
const defaultConfig: DraftConfig = {
|
16
|
+
...draft07Config,
|
17
|
+
errors: {
|
18
|
+
...draft07Config.errors,
|
19
|
+
|
20
|
+
enumError(data) {
|
21
|
+
let values = '[]';
|
22
|
+
|
23
|
+
if (data.values && Array.isArray(data.values)) {
|
24
|
+
if (data.values.length < 5) {
|
25
|
+
values = `[${data.values.join(', ')}]`;
|
26
|
+
} else {
|
27
|
+
const firstFiveValues = data.values.slice(0, 5);
|
28
|
+
values = `[${firstFiveValues.join(', ')}, ... ${data.values.length - 5} more]`;
|
29
|
+
}
|
30
|
+
}
|
31
|
+
|
32
|
+
return {
|
33
|
+
type: 'error',
|
34
|
+
name: 'EnumError',
|
35
|
+
code: 'enum-error',
|
36
|
+
message: `Expected given value \`${data.value}\` in ${data.pointer}\` to be one of \`${values}\``,
|
37
|
+
data,
|
38
|
+
};
|
39
|
+
},
|
40
|
+
|
41
|
+
anyOfError(data) {
|
42
|
+
let value = `Value \`${data.value}\` at`;
|
43
|
+
|
44
|
+
if (typeof data.value == 'object') {
|
45
|
+
value = 'Object at';
|
46
|
+
}
|
47
|
+
|
48
|
+
if (Array.isArray(data.value)) {
|
49
|
+
value = 'Array at';
|
50
|
+
}
|
51
|
+
|
52
|
+
return {
|
53
|
+
type: 'error',
|
54
|
+
name: 'AnyOfError',
|
55
|
+
code: 'any-of-error',
|
56
|
+
message: `${value} \`${data.pointer}\` does not match any schema`,
|
57
|
+
data,
|
58
|
+
};
|
59
|
+
},
|
60
|
+
},
|
61
|
+
};
|
62
|
+
|
63
|
+
const FTSchema = new Draft07(FormattedText);
|
64
|
+
|
65
|
+
const ComponentInputSchema = new Draft(
|
66
|
+
{
|
67
|
+
...defaultConfig,
|
68
|
+
|
69
|
+
resolveRef(schema, rootSchema) {
|
70
|
+
const resolvedSchema = draft07Config.resolveRef(schema, rootSchema) as MANIFEST_MODELS.v1.CoreSchemaMetaSchema;
|
71
|
+
if (!resolvedSchema) {
|
72
|
+
return resolvedSchema;
|
73
|
+
}
|
74
|
+
if (resolvedSchema.type === 'FormattedText') {
|
75
|
+
return FTSchema.rootSchema;
|
76
|
+
} else if (Array.isArray(resolvedSchema.type) && resolvedSchema.type.includes('FormattedText')) {
|
77
|
+
return {
|
78
|
+
...schema,
|
79
|
+
...FTSchema.rootSchema,
|
80
|
+
type: resolvedSchema.type.filter((t) => t !== 'FormattedText').concat(FormattedText.type as 'array'),
|
81
|
+
};
|
82
|
+
} else {
|
83
|
+
return resolvedSchema;
|
84
|
+
}
|
85
|
+
},
|
86
|
+
},
|
87
|
+
DxComponentInputSchema,
|
88
|
+
);
|
89
|
+
ComponentInputSchema.addRemoteSchema('DxComponentInputSchema.json/DxContentMetaSchema.json', DxContentMetaSchema);
|
90
|
+
|
91
|
+
const v1Schema = new Draft07(v1, defaultConfig);
|
92
|
+
|
93
|
+
v1Schema.addRemoteSchema('DxComponentInputSchema.json/DxContentMetaSchema.json', DxContentMetaSchema);
|
94
|
+
v1Schema.addRemoteSchema('/DxComponentInputSchema.json', ComponentInputSchema.getSchema());
|
95
|
+
v1Schema.addRemoteSchema('/DxComponentIcons.json', DxComponentIcons);
|
96
|
+
v1Schema.addRemoteSchema('http://json-schema.org/draft-07/schema', Draft07Schema);
|
97
|
+
v1Schema.addRemoteSchema('http://json-schema.org/draft-07/schema#', Draft07Schema);
|
98
|
+
|
99
|
+
const ContentSchemaSchema = new Draft07(DxComponentInputSchema, defaultConfig);
|
100
|
+
ContentSchemaSchema.addRemoteSchema('DxComponentInputSchema.json/DxContentMetaSchema.json', DxContentMetaSchema);
|
10
101
|
|
11
102
|
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
103
|
validateManifest(manifest: unknown, version: 'v1') {
|
27
104
|
switch (version) {
|
28
|
-
case 'v1':
|
29
|
-
|
105
|
+
case 'v1': {
|
106
|
+
const validationResult = v1Schema.validate(manifest);
|
107
|
+
return this.processValidationResult(validationResult);
|
108
|
+
}
|
30
109
|
|
31
110
|
default:
|
32
|
-
throw new
|
111
|
+
throw new SchemaValidationError('Invalid manifest version');
|
33
112
|
}
|
34
113
|
}
|
35
|
-
|
36
|
-
|
37
|
-
// This schema matches the input definition in the manifest
|
38
|
-
return this.doValidate(DxComponentInputSchema, contentSchema);
|
114
|
+
validateContentSchema(contentSchema: JSONSchema) {
|
115
|
+
return this.processValidationResult(ContentSchemaSchema.validate(contentSchema));
|
39
116
|
}
|
40
117
|
|
41
|
-
validateComponentInput(functionInputSchema:
|
42
|
-
|
118
|
+
validateComponentInput(functionInputSchema: JSONSchema, inputValue: unknown) {
|
119
|
+
const inputSchema = ComponentInputSchema.compileSchema(functionInputSchema);
|
120
|
+
const errors = ComponentInputSchema.validate(inputValue, inputSchema);
|
121
|
+
|
122
|
+
return this.processValidationResult(errors);
|
43
123
|
}
|
44
124
|
|
45
|
-
private
|
46
|
-
|
47
|
-
|
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);
|
125
|
+
private processValidationResult(errors: JSONError[]): true {
|
126
|
+
if (errors.length > 0) {
|
127
|
+
throw new SchemaValidationError(errors.map((a) => a.message).join(',\n'));
|
51
128
|
}
|
52
|
-
|
129
|
+
|
130
|
+
return true;
|
53
131
|
}
|
54
132
|
}
|