@squiz/dx-json-schema-lib 1.79.0 → 1.80.1

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.
Files changed (59) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/jest.config.ts +13 -2
  3. package/lib/JsonSchemaService.d.ts +34 -0
  4. package/lib/JsonSchemaService.js +140 -0
  5. package/lib/JsonSchemaService.js.map +1 -0
  6. package/lib/JsonSchemaService.spec.d.ts +1 -0
  7. package/lib/JsonSchemaService.spec.js +1807 -0
  8. package/lib/JsonSchemaService.spec.js.map +1 -0
  9. package/lib/JsonValidationService.d.ts +11 -33
  10. package/lib/JsonValidationService.js +43 -194
  11. package/lib/JsonValidationService.js.map +1 -1
  12. package/lib/JsonValidationService.spec.js +19 -1800
  13. package/lib/JsonValidationService.spec.js.map +1 -1
  14. package/lib/index.d.ts +1 -0
  15. package/lib/index.js +1 -0
  16. package/lib/index.js.map +1 -1
  17. package/lib/jsonTypeResolution/TypeResolver.d.ts +4 -2
  18. package/lib/jsonTypeResolution/TypeResolver.js +0 -3
  19. package/lib/jsonTypeResolution/TypeResolver.js.map +1 -1
  20. package/lib/jsonTypeResolution/TypeResolver.spec.js +10 -0
  21. package/lib/jsonTypeResolution/TypeResolver.spec.js.map +1 -1
  22. package/lib/manifest/v1/DxComponentInputSchema.spec.js +18 -0
  23. package/lib/manifest/v1/DxComponentInputSchema.spec.js.map +1 -1
  24. package/lib/manifest/v1/DxContentMetaSchema.json +24 -32
  25. package/lib/manifest/v1/JobV1.d.ts +23 -18
  26. package/lib/manifest/v1/__test__/schemas/invalidUiMetadata.json +31 -0
  27. package/lib/manifest/v1/v1.d.ts +23 -18
  28. package/lib/manifest/v1/v1.spec.js +3 -66
  29. package/lib/manifest/v1/v1.spec.js.map +1 -1
  30. package/lib/primitiveTypes/SquizImage.d.ts +2 -1
  31. package/lib/primitiveTypes/SquizImage.js.map +1 -1
  32. package/lib/processValidationResult.d.ts +2 -0
  33. package/lib/processValidationResult.js +24 -0
  34. package/lib/processValidationResult.js.map +1 -0
  35. package/lib/validators/customKeywordValidators.d.ts +2 -0
  36. package/lib/validators/customKeywordValidators.js +47 -0
  37. package/lib/validators/customKeywordValidators.js.map +1 -0
  38. package/lib/validators/customKeywordValidators.spec.d.ts +1 -0
  39. package/lib/validators/customKeywordValidators.spec.js +100 -0
  40. package/lib/validators/customKeywordValidators.spec.js.map +1 -0
  41. package/package.json +2 -5
  42. package/src/JsonSchemaService.spec.ts +2198 -0
  43. package/src/JsonSchemaService.ts +159 -0
  44. package/src/JsonValidationService.spec.ts +26 -2195
  45. package/src/JsonValidationService.ts +64 -226
  46. package/src/index.ts +1 -0
  47. package/src/jsonTypeResolution/TypeResolver.spec.ts +12 -0
  48. package/src/jsonTypeResolution/TypeResolver.ts +5 -5
  49. package/src/manifest/v1/DxComponentInputSchema.spec.ts +21 -0
  50. package/src/manifest/v1/DxContentMetaSchema.json +24 -32
  51. package/src/manifest/v1/JobV1.ts +23 -23
  52. package/src/manifest/v1/__test__/schemas/invalidUiMetadata.json +31 -0
  53. package/src/manifest/v1/v1.spec.ts +3 -73
  54. package/src/manifest/v1/v1.ts +23 -23
  55. package/src/primitiveTypes/SquizImage.ts +2 -1
  56. package/src/processValidationResult.ts +20 -0
  57. package/src/validators/customKeywordValidators.spec.ts +171 -0
  58. package/src/validators/customKeywordValidators.ts +53 -0
  59. package/tsconfig.tsbuildinfo +1 -1
@@ -1,31 +1,46 @@
1
- import JSONQuery, { Input } from '@sagold/json-query';
2
-
3
1
  import DxComponentInputSchema from './manifest/v1/DxComponentInputSchema.json';
4
- import DxComponentIcons from './manifest/v1/DxComponentIcons.json';
5
2
  import DxContentMetaSchema from './manifest/v1/DxContentMetaSchema.json';
6
- import MatrixAssetSchema from './manifest/v1/MatrixAssetSchema.json';
7
-
8
- import Draft07Schema from './manifest/v1/Draft-07.json';
9
-
10
3
  import FormattedText from './formatted-text/v1/formattedText.json';
11
4
 
12
- import v1 from './manifest/v1/v1.json';
13
- import JobV1 from './manifest/v1/JobV1.json';
14
- import { SchemaValidationError, ValidationData, ValidationDataMap } from './errors/SchemaValidationError';
15
- import { Draft07, JSONError, JSONSchema, Draft, DraftConfig } from '@squiz/json-schema-library';
5
+ import { SchemaValidationError } from './errors/SchemaValidationError';
6
+ import { Draft07, JSONSchema, Draft, DraftConfig } from '@squiz/json-schema-library';
16
7
 
17
8
  import { draft07Config } from '@squiz/json-schema-library';
18
- import { MANIFEST_MODELS } from '.';
9
+ import {
10
+ BaseFormattedTextType,
11
+ ComponentInputFormattedTextType,
12
+ MANIFEST_MODELS,
13
+ SquizImageType,
14
+ SquizLinkType,
15
+ TypeResolverBuilder,
16
+ } from '.';
19
17
  import { customFormatValidators } from './validators/customFormatValidators';
20
- import { AnyPrimitiveType, AnyResolvableType, TypeResolver } from './jsonTypeResolution/TypeResolver';
21
- import { JsonResolutionError } from './errors/JsonResolutionError';
22
-
23
- const defaultConfig: DraftConfig = {
18
+ import { customKeywordValidators } from './validators/customKeywordValidators';
19
+ import {
20
+ ComponentInputMetaSchema,
21
+ JobV1MetaSchema,
22
+ JSONSchemaService,
23
+ ManifestV1MetaSchema,
24
+ } from './JsonSchemaService';
25
+
26
+ export const defaultConfig: DraftConfig = {
24
27
  ...draft07Config,
25
28
  validateFormat: {
26
29
  ...draft07Config.validateFormat,
27
30
  ...customFormatValidators,
28
31
  },
32
+ typeKeywords: {
33
+ ...draft07Config.typeKeywords,
34
+ array: draft07Config.typeKeywords.array.concat('ui:metadata'),
35
+ object: draft07Config.typeKeywords.object.concat('ui:metadata'),
36
+ boolean: draft07Config.typeKeywords.boolean.concat('ui:metadata'),
37
+ string: draft07Config.typeKeywords.string.concat('ui:metadata'),
38
+ number: draft07Config.typeKeywords.number.concat('ui:metadata'),
39
+ },
40
+ validateKeyword: {
41
+ ...draft07Config.validateKeyword,
42
+ ...customKeywordValidators,
43
+ },
29
44
  errors: {
30
45
  ...draft07Config.errors,
31
46
  enumError(data) {
@@ -95,6 +110,9 @@ const defaultConfig: DraftConfig = {
95
110
 
96
111
  const FTSchema = new Draft07(FormattedText, defaultConfig);
97
112
 
113
+ /**
114
+ * @deprecated Only used for FormattedText resolution
115
+ */
98
116
  export const ComponentInputSchema = new Draft(
99
117
  {
100
118
  ...defaultConfig,
@@ -122,236 +140,56 @@ export const ComponentInputSchema = new Draft(
122
140
  );
123
141
  ComponentInputSchema.addRemoteSchema('DxComponentInputSchema.json/DxContentMetaSchema.json', DxContentMetaSchema);
124
142
 
125
- export const RenderInputSchema = new Draft({
126
- ...defaultConfig,
127
- resolveRef(schema, rootSchema) {
128
- const resolvedSchema = draft07Config.resolveRef(schema, rootSchema) as MANIFEST_MODELS.v1.CoreSchemaMetaSchema;
129
- if (!resolvedSchema) {
130
- return resolvedSchema;
131
- }
132
-
133
- if (resolvedSchema.type === 'FormattedText') {
134
- return { type: 'string' };
135
- } else if (Array.isArray(resolvedSchema.type) && resolvedSchema.type.includes('FormattedText')) {
136
- return {
137
- ...schema,
138
- type: resolvedSchema.type.filter((t) => t !== 'FormattedText').concat('string'),
139
- };
140
- } else {
141
- return resolvedSchema;
142
- }
143
- },
144
- });
145
-
146
- const v1Schema = new Draft07(v1, defaultConfig);
147
-
148
- v1Schema.addRemoteSchema('DxComponentInputSchema.json/DxContentMetaSchema.json', DxContentMetaSchema);
149
- v1Schema.addRemoteSchema('/DxComponentInputSchema.json', ComponentInputSchema.getSchema());
150
- v1Schema.addRemoteSchema('/DxComponentIcons.json', DxComponentIcons);
151
- v1Schema.addRemoteSchema('/MatrixAssetSchema.json', MatrixAssetSchema);
152
- v1Schema.addRemoteSchema('http://json-schema.org/draft-07/schema', Draft07Schema);
153
- v1Schema.addRemoteSchema('http://json-schema.org/draft-07/schema#', Draft07Schema);
154
-
155
- const jobV1Schema = new Draft07(JobV1, defaultConfig);
156
- jobV1Schema.addRemoteSchema('/DxComponentInputSchema.json', ComponentInputSchema.getSchema());
157
-
158
- export const ComponentInputMetaSchema: MetaSchemaInput = {
159
- root: DxComponentInputSchema,
160
- remotes: {
161
- 'DxComponentInputSchema.json/DxContentMetaSchema.json': DxContentMetaSchema,
162
- },
163
- };
164
-
165
- export const RenderInputMetaSchema: MetaSchemaInput = {
166
- root: Draft07Schema,
167
- };
168
-
169
- export const ManifestV1MetaSchema: MetaSchemaInput = {
170
- root: v1,
171
- remotes: {
172
- 'DxComponentInputSchema.json/DxContentMetaSchema.json': DxContentMetaSchema,
173
- '/DxComponentInputSchema.json': DxComponentInputSchema,
174
- '/DxComponentIcons.json': DxComponentIcons,
175
- '/MatrixAssetSchema.json': MatrixAssetSchema,
176
- 'http://json-schema.org/draft-07/schema': Draft07Schema,
177
- 'http://json-schema.org/draft-07/schema#': Draft07Schema,
178
- },
179
- };
180
-
181
- interface MetaSchemaInput {
182
- root: JSONSchema;
183
- remotes?: Record<string, JSONSchema>;
184
- }
185
143
  /**
186
- * A service that can be used to validate and resolve JSON against a schema.
144
+ * Deprecated JSON Validation Service that wraps the JSONSchemaService
145
+ * @deprecated Use JSONSchemaService instead
187
146
  */
188
- export class JSONSchemaService<P extends AnyPrimitiveType, R extends AnyResolvableType> {
189
- schema: Draft;
190
- constructor(private typeResolver: TypeResolver<P, R>, metaSchema: MetaSchemaInput) {
191
- this.schema = new Draft(
192
- {
193
- ...defaultConfig,
194
- resolveRef: (schema, rootSchema) => this.doResolveRef(schema, rootSchema),
195
- validate: (core, data, schema, pointer) => defaultConfig.validate(core, data, schema, pointer),
196
- resolveOneOf: (core, data, schema, pointer) => defaultConfig.resolveOneOf(core, data, schema, pointer),
197
- },
198
- metaSchema.root,
199
- );
200
-
201
- for (const [key, value] of Object.entries(metaSchema.remotes || {})) {
202
- this.schema.addRemoteSchema(key, value);
203
- }
204
-
205
- for (const schema of this.typeResolver.validationSchemaDefinitions) {
206
- // Please find a better way of doing this.
207
- this.schema.addRemoteSchema(`/${schema.title}.json`, schema as JSONSchema);
208
- this.schema.addRemoteSchema(`#/${schema.title}.json`, schema as JSONSchema);
209
- }
210
- }
211
-
212
- private doResolveRef(schema: JSONSchema, rootSchema: JSONSchema): JSONSchema {
213
- const initialRef = draft07Config.resolveRef(schema, rootSchema);
214
- if (!initialRef) return initialRef;
215
- if (!this.typeResolver.isPrimitiveType(initialRef.type)) return initialRef;
216
-
217
- const validationSchemas = this.typeResolver.getValidationSchemaForPrimitive(initialRef.type);
218
- // All validation schemas are pre-compiled as remote schemas and are referenced below
219
- const fullValidationSchema = {
220
- oneOf: validationSchemas.map((schema) => ({ $ref: `${schema.title}.json` })),
221
- };
222
- return this.schema.compileSchema(fullValidationSchema);
223
- }
224
-
225
- /**
226
- * Validate an input value against a specified schema
227
- * @throws {SchemaValidationError} if the input is invalid
228
- * @returns true if the input is valid
229
- */
230
- public validateInput(input: unknown, inputSchema: JSONSchema = this.schema.rootSchema): true | never {
231
- inputSchema = this.schema.compileSchema(inputSchema);
232
- const errors = this.schema.validate(input, inputSchema);
233
- return processValidationResult(errors);
234
- }
235
-
236
- /**
237
- * Resolve an input object by replacing all resolvable shapes with their resolved values
238
- * @param input any input object which matches the input schema
239
- * @param inputSchema a JSONSchema which provides type information about the input object
240
- * @returns the input object with all resolvable shapes resolved
241
- */
242
- public async resolveInput(input: Input, inputSchema: JSONSchema) {
243
- const setters: Array<Promise<(input: Input) => Input>> = [];
244
- this.schema.each(
245
- input,
246
- async (schema, value, pointer) => {
247
- // Bug in library for Array item schemas which won't resolve the oneOf schema
248
- if (Array.isArray(schema?.oneOf)) {
249
- const oldSchema = schema;
250
- schema = this.schema.resolveOneOf(value, schema);
251
- schema.oneOfSchema = oldSchema;
252
- }
253
- if (!this.typeResolver.isResolvableSchema(schema)) return;
254
- // If its a resolvable schema, it should exist in a oneOf array with other schemas
255
- // Including a primitive schema
256
- const allPossibleSchemaTitles: Array<string> = schema.oneOfSchema.oneOf.map((o: JSONSchema) =>
257
- o.$ref.replace('.json', ''),
258
- );
259
- const primitiveSchema = allPossibleSchemaTitles.find((title) => this.typeResolver.isPrimitiveType(title));
260
- if (!primitiveSchema) return;
261
- const resolver = this.typeResolver.tryGetResolver(primitiveSchema, schema as any);
262
- if (!resolver) return;
263
- const setResolvedData = Promise.resolve()
264
- .then(() => resolver(value))
265
- .then((resolvedData) => (item: typeof input) => JSONQuery.set(item, pointer, resolvedData, 'replace' as any))
266
- .catch((e) => Promise.reject(new JsonResolutionError(e, pointer, value)));
267
- setters.push(setResolvedData);
268
- },
269
- inputSchema,
270
- );
271
-
272
- const potentialResolutionErrors = [];
273
- for (const resolveResult of await Promise.allSettled(setters)) {
274
- if (resolveResult.status === 'rejected') {
275
- potentialResolutionErrors.push(resolveResult.reason);
276
- continue;
277
- }
278
-
279
- input = resolveResult.value(input);
280
- }
281
- if (potentialResolutionErrors.length) {
282
- throw new Error(`Error(s) occurred when resolving JSON:\n${potentialResolutionErrors.join('\n')}`);
283
- }
284
- return input;
285
- }
286
-
287
- private assignResolvedData(input: Input, pointer: string, resolvedData: unknown): Input {
288
- // TODO: Revert to using JSONQuery.set once https://github.com/sagold/json-query/pull/27 is resolved.
289
- // Test "should resolve multiple primitive type array items in multi-level nested array structures" confirms
290
- // desired behaviour.
291
- JSONQuery.get(input, pointer, (unresolvedData) => {
292
- if (resolvedData === unresolvedData) {
293
- return;
294
- }
295
-
296
- for (const prop of Object.getOwnPropertyNames(unresolvedData)) {
297
- delete unresolvedData[prop];
298
- }
299
-
300
- Object.assign(unresolvedData, resolvedData);
301
- });
302
-
303
- return input;
304
- }
305
- }
306
-
307
147
  export class JsonValidationService {
148
+ private jobSchemaService = new JSONSchemaService(TypeResolverBuilder.new().build(), JobV1MetaSchema);
149
+ private componentInputSchemaService = new JSONSchemaService(
150
+ TypeResolverBuilder.new()
151
+ .addPrimitive(BaseFormattedTextType)
152
+ .addPrimitive(SquizImageType)
153
+ .addPrimitive(SquizLinkType)
154
+ .build(),
155
+ ComponentInputMetaSchema,
156
+ );
157
+ private renderInputSchemaService = new JSONSchemaService(
158
+ TypeResolverBuilder.new()
159
+ .addPrimitive(ComponentInputFormattedTextType)
160
+ .addPrimitive(SquizImageType)
161
+ .addPrimitive(SquizLinkType)
162
+ .build(),
163
+ ComponentInputMetaSchema,
164
+ );
165
+ private componentManifestSchemaService = new JSONSchemaService(
166
+ TypeResolverBuilder.new().build(),
167
+ ManifestV1MetaSchema,
168
+ );
169
+
308
170
  validateManifest(manifest: unknown, version: 'v1' | 'JobV1') {
309
171
  switch (version) {
310
172
  case 'v1': {
311
- const validationResult = v1Schema.validate(manifest);
312
- return processValidationResult(validationResult);
173
+ return this.componentManifestSchemaService.validateInput(manifest);
313
174
  }
314
175
  case 'JobV1': {
315
- const validationResult = jobV1Schema.validate(manifest);
316
- return processValidationResult(validationResult);
176
+ return this.jobSchemaService.validateInput(manifest);
317
177
  }
318
178
 
319
179
  default:
320
180
  throw new SchemaValidationError('Invalid manifest version');
321
181
  }
322
182
  }
183
+
323
184
  validateContentSchema(contentSchema: JSONSchema) {
324
- return processValidationResult(ComponentInputSchema.validate(contentSchema));
185
+ return this.componentInputSchemaService.validateInput(contentSchema);
325
186
  }
326
187
 
327
188
  validateComponentInput(functionInputSchema: JSONSchema, inputValue: unknown) {
328
- const inputSchema = ComponentInputSchema.compileSchema(functionInputSchema);
329
- const errors = ComponentInputSchema.validate(inputValue, inputSchema);
330
- return processValidationResult(errors);
189
+ return this.componentInputSchemaService.validateInput(inputValue, functionInputSchema);
331
190
  }
332
191
 
333
192
  validateRenderInput(functionInputSchema: JSONSchema, inputValue: unknown) {
334
- const inputSchema = RenderInputSchema.compileSchema(functionInputSchema);
335
- const errors = RenderInputSchema.validate(inputValue, inputSchema);
336
-
337
- return processValidationResult(errors);
338
- }
339
- }
340
-
341
- function processValidationResult(errors: JSONError[]): true {
342
- if (errors.length > 0) {
343
- const ValidationDataMap: ValidationDataMap = errors.reduce((acc: ValidationDataMap, error: JSONError) => {
344
- const pointer = error?.data?.pointer;
345
- const validationData: ValidationData = {
346
- message: error.message,
347
- data: error.data,
348
- };
349
- Object.keys(acc).includes(pointer) && Array.isArray(acc[pointer])
350
- ? acc[pointer].push(validationData)
351
- : (acc[pointer] = [validationData]);
352
- return acc;
353
- }, {});
354
- throw new SchemaValidationError(errors.map((a) => a.message).join(',\n'), undefined, ValidationDataMap);
193
+ return this.renderInputSchemaService.validateInput(inputValue, functionInputSchema);
355
194
  }
356
- return true;
357
195
  }
package/src/index.ts CHANGED
@@ -7,6 +7,7 @@ export * from './formatted-text/v1/resolveFormattedTextNodes';
7
7
 
8
8
  export * as SUB_SCHEMAS from './manifest/v1/subSchemas';
9
9
 
10
+ export * from './JsonSchemaService';
10
11
  export * from './JsonValidationService';
11
12
  export * from './errors/SchemaValidationError';
12
13
  export * from './errors/JsonResolutionError';
@@ -84,4 +84,16 @@ describe('getValidationSchemaForPrimitive', () => {
84
84
  }),
85
85
  ).toThrowError();
86
86
  });
87
+
88
+ it('should return undefined when try to get a resolver for missing primitive', () => {
89
+ const primitiveType = primitiveTypeFixture('MyPrimitive');
90
+ const resolvableType = resolvableTypeFixture('MyResolvable');
91
+ const resolver = new TypeResolver([primitiveType, primitiveTypeFixture('MyOtherPrimitive')], [resolvableType], {
92
+ MyOtherPrimitive: {
93
+ MyResolvable: () => null,
94
+ },
95
+ });
96
+
97
+ expect(resolver.tryGetResolver('DoesNotExist', resolvableType)).toEqual(undefined);
98
+ });
87
99
  });
@@ -35,7 +35,11 @@ export function ResolvableType<SHAPE, TITLE extends string>(
35
35
  }
36
36
  export type AnyResolvableType = ResolvableType<string, any>;
37
37
 
38
- export type Resolver<INPUT, OUTPUT> = (input: INPUT) => MaybePromise<OUTPUT>;
38
+ export type ResolverContext = {
39
+ baseUrl?: string;
40
+ };
41
+
42
+ export type Resolver<INPUT, OUTPUT> = (input: INPUT, ctx?: ResolverContext) => MaybePromise<OUTPUT>;
39
43
 
40
44
  /**
41
45
  * A JSON Type Resolver class which stores the primitive and resolvable JSON Schema types and their resolvers
@@ -74,10 +78,6 @@ export class TypeResolver<P extends AnyPrimitiveType, R extends AnyResolvableTyp
74
78
  return this.primitives.has(type);
75
79
  }
76
80
 
77
- isPrimitiveSchema(schema: JSONSchema): schema is P {
78
- return this.isPrimitiveType(schema.title);
79
- }
80
-
81
81
  isResolvableSchema(schema: JSONSchema): schema is R {
82
82
  return this.resolvables.has(schema.title);
83
83
  }
@@ -161,4 +161,25 @@ describe('DxComponentInputSchema', () => {
161
161
  }),
162
162
  ).not.toThrowError();
163
163
  });
164
+
165
+ describe('ui:metadata', () => {
166
+ describe('enumWidget', () => {
167
+ it.each(['radio', 'dropdown'])('should allow %s as a value', (v) => {
168
+ expect(() =>
169
+ jsonValidationService.validateContentSchema({
170
+ type: 'object',
171
+ required: [],
172
+ properties: {
173
+ myProp: {
174
+ enum: ['one', 'two', 'three'],
175
+ 'ui:metadata': {
176
+ enumWidget: v,
177
+ },
178
+ },
179
+ },
180
+ }),
181
+ ).not.toThrow();
182
+ });
183
+ });
184
+ });
164
185
  });
@@ -36,43 +36,34 @@
36
36
  "default": []
37
37
  },
38
38
  "ui:metadata": {
39
- "oneOf": [
40
- {
41
- "type": "object",
42
- "additionalProperties": false,
43
- "properties": {
44
- "collapsedByDefault": {
45
- "type": "boolean",
46
- "default": false
47
- }
48
- },
49
- "if": {
50
- "type": "object"
51
- }
52
- },
53
- {
54
- "type": "object",
55
- "additionalProperties": false,
56
- "properties": {
57
- "inlineEditable": {
58
- "type": "boolean",
59
- "default": false
60
- },
61
- "quickOption": {
62
- "type": "boolean",
63
- "default": false
64
- }
65
- },
66
- "if": {
67
- "type": ["number", "integer", "array", "boolean", "string"]
68
- }
69
- }
70
- ],
39
+ "type": "object",
40
+ "additionalProperties": false,
71
41
  "properties": {
72
42
  "autoReload": {
73
43
  "type": "boolean",
74
44
  "description": "Whether the component should automatically reload when the input is changed",
75
45
  "default": false
46
+ },
47
+ "inlineEditable": {
48
+ "type": "boolean",
49
+ "default": false,
50
+ "description": "Allow this property to be editable inline"
51
+ },
52
+ "collapsedByDefault": {
53
+ "type": "boolean",
54
+ "default": false,
55
+ "description": "Choose if an object grouping should be collapsed by default"
56
+ },
57
+ "quickOption": {
58
+ "type": "boolean",
59
+ "default": false,
60
+ "description": "Allow this property to be part of the Quick Options UI"
61
+ },
62
+ "enumWidget": {
63
+ "type": "string",
64
+ "enum": ["radio", "dropdown"],
65
+ "default": "dropdown",
66
+ "description": "Choose if the enum values are displayed as a radio button list or a dropdown"
76
67
  }
77
68
  }
78
69
  }
@@ -231,6 +222,7 @@
231
222
  "oneOf": { "$ref": "#/definitions/schemaArray" },
232
223
  "not": { "$ref": "#" }
233
224
  },
225
+ "ui:metadata": { "$ref": "#/definitions/ui:metadata" },
234
226
  "additionalProperties": false,
235
227
  "dependencies": {
236
228
  "properties": {
@@ -14,32 +14,10 @@ export type DxComponentInputSchema = CoreSchemaMetaSchema & {
14
14
  [k: string]: unknown;
15
15
  };
16
16
  export type CoreSchemaMetaSchema = CoreSchemaMetaSchema1 & CoreSchemaMetaSchema2;
17
- export type UiMetadata = {
18
- /**
19
- * Whether the component should automatically reload when the input is changed
20
- */
21
- autoReload?: boolean;
22
- [k: string]: unknown;
23
- } & (
24
- | {
25
- collapsedByDefault?: boolean;
26
- }
27
- | {
28
- inlineEditable?: boolean;
29
- quickOption?: boolean;
30
- }
31
- );
32
17
  export type CoreSchemaMetaSchema2 =
33
18
  | {
34
19
  $id?: string;
35
- 'ui:metadata'?:
36
- | {
37
- collapsedByDefault?: boolean;
38
- }
39
- | {
40
- inlineEditable?: boolean;
41
- quickOption?: boolean;
42
- };
20
+ 'ui:metadata'?: UiMetadata;
43
21
  $schema?: string;
44
22
  $ref?: string;
45
23
  $comment?: string;
@@ -469,3 +447,25 @@ export interface CoreSchemaMetaSchema1 {
469
447
  oneOf?: SchemaArray;
470
448
  not?: CoreSchemaMetaSchema2;
471
449
  }
450
+ export interface UiMetadata {
451
+ /**
452
+ * Whether the component should automatically reload when the input is changed
453
+ */
454
+ autoReload?: boolean;
455
+ /**
456
+ * Allow this property to be editable inline
457
+ */
458
+ inlineEditable?: boolean;
459
+ /**
460
+ * Choose if an object grouping should be collapsed by default
461
+ */
462
+ collapsedByDefault?: boolean;
463
+ /**
464
+ * Allow this property to be part of the Quick Options UI
465
+ */
466
+ quickOption?: boolean;
467
+ /**
468
+ * Choose if the enum values are displayed as a radio button list or a dropdown
469
+ */
470
+ enumWidget?: 'radio' | 'dropdown';
471
+ }
@@ -0,0 +1,31 @@
1
+ {
2
+ "$schema": "../../v1.json",
3
+ "description": "t",
4
+ "displayName": "t",
5
+ "name": "test",
6
+ "namespace": "other",
7
+ "mainFunction": "main",
8
+ "version": "1.0.4",
9
+ "environment": [],
10
+ "functions": [
11
+ {
12
+ "entry": "main.js",
13
+ "name": "main",
14
+ "output": {
15
+ "responseType": "html"
16
+ },
17
+ "input": {
18
+ "type": "object",
19
+ "properties": {
20
+ "prop": {
21
+ "type": "string",
22
+ "ui:metadata": {
23
+ "quickOption": true
24
+ }
25
+ }
26
+ },
27
+ "required": []
28
+ }
29
+ }
30
+ ]
31
+ }
@@ -21,9 +21,9 @@ function expectToThrowErrorMatchingTypeAndMessage(received: Function, errorType:
21
21
  error = e;
22
22
  }
23
23
 
24
- expect(error).toBeDefined();
25
- expect(error?.message).toEqual(message);
24
+ expect(error).toBeTruthy();
26
25
  expect(error).toBeInstanceOf(errorType);
26
+ expect(error?.message).toEqual(message);
27
27
  }
28
28
 
29
29
  describe('manifest/v1', () => {
@@ -276,76 +276,6 @@ describe('manifest/v1', () => {
276
276
  ),
277
277
  ).toEqual(true);
278
278
  });
279
- it('should fail if ui:metadata property object on object input group has flags other than collapsedByDefault', async () => {
280
- const manifest = await fetchTestManifest('validComponent.json');
281
- expectToThrowErrorMatchingTypeAndMessage(
282
- () => {
283
- validationService.validateManifest(
284
- {
285
- ...manifest,
286
- functions: [
287
- {
288
- name: 'main',
289
- entry: 'main.js',
290
- output: {
291
- responseType: 'html',
292
- },
293
- input: {
294
- type: 'object',
295
- properties: {
296
- 'users-contact': {
297
- type: 'object',
298
- description: 'A description of group can be provided if needed',
299
- required: ['fist-name', 'last-name', 'phone-number', 'email'],
300
- 'ui:metadata': {
301
- collapsedByDefault: true,
302
- quickOption: false,
303
- inlineEditable: true,
304
- },
305
- properties: {
306
- 'fist-name': {
307
- type: 'string',
308
- 'ui:metadata': {
309
- inlineEditable: true,
310
- quickOption: false,
311
- },
312
- },
313
- 'last-name': {
314
- type: 'string',
315
- 'ui:metadata': {
316
- inlineEditable: true,
317
- quickOption: false,
318
- },
319
- },
320
- 'phone-number': {
321
- type: 'number',
322
- 'ui:metadata': {
323
- inlineEditable: true,
324
- quickOption: false,
325
- },
326
- },
327
- email: {
328
- type: 'string',
329
- 'ui:metadata': {
330
- inlineEditable: true,
331
- quickOption: false,
332
- },
333
- },
334
- },
335
- },
336
- },
337
- required: ['users-contact'],
338
- },
339
- },
340
- ],
341
- },
342
- 'v1',
343
- );
344
- },
345
- SchemaValidationError,
346
- 'failed validation: Value `{"collapsedByDefault":true,"quickOption":false,"inlineEditable":true}` in `#/functions/0/input/properties/users-contact/ui:metadata` does not match any given oneof schema',
347
- );
348
- });
349
279
  it('should allow the autoReload field on the manifest', async () => {
350
280
  const manifest = await fetchTestManifest('validComponent.json');
351
281
 
@@ -443,7 +373,7 @@ describe('manifest/v1', () => {
443
373
  );
444
374
  },
445
375
  SchemaValidationError,
446
- 'failed validation: Value `{"inlineEditable":true,"quickOption":false,"collapsedByDefault":true}` in `#/functions/0/input/properties/users-contact/properties/fist-name/ui:metadata` does not match any given oneof schema',
376
+ 'failed validation: ui:metadata property collapsedByDefault is only valid for object properties.',
447
377
  );
448
378
  });
449
379
  });