@squiz/dx-json-schema-lib 1.79.0 → 1.80.0
Sign up to get free protection for your applications and to get access to all the features.
- package/CHANGELOG.md +6 -0
- package/jest.config.ts +13 -2
- package/lib/JsonSchemaService.d.ts +34 -0
- package/lib/JsonSchemaService.js +140 -0
- package/lib/JsonSchemaService.js.map +1 -0
- package/lib/JsonSchemaService.spec.d.ts +1 -0
- package/lib/JsonSchemaService.spec.js +1807 -0
- package/lib/JsonSchemaService.spec.js.map +1 -0
- package/lib/JsonValidationService.d.ts +11 -33
- package/lib/JsonValidationService.js +43 -194
- package/lib/JsonValidationService.js.map +1 -1
- package/lib/JsonValidationService.spec.js +19 -1800
- package/lib/JsonValidationService.spec.js.map +1 -1
- package/lib/index.d.ts +1 -0
- package/lib/index.js +1 -0
- package/lib/index.js.map +1 -1
- package/lib/jsonTypeResolution/TypeResolver.d.ts +0 -1
- package/lib/jsonTypeResolution/TypeResolver.js +0 -3
- package/lib/jsonTypeResolution/TypeResolver.js.map +1 -1
- package/lib/jsonTypeResolution/TypeResolver.spec.js +10 -0
- package/lib/jsonTypeResolution/TypeResolver.spec.js.map +1 -1
- package/lib/manifest/v1/DxComponentInputSchema.spec.js +18 -0
- package/lib/manifest/v1/DxComponentInputSchema.spec.js.map +1 -1
- package/lib/manifest/v1/DxContentMetaSchema.json +24 -32
- package/lib/manifest/v1/JobV1.d.ts +23 -18
- package/lib/manifest/v1/__test__/schemas/invalidUiMetadata.json +31 -0
- package/lib/manifest/v1/v1.d.ts +23 -18
- package/lib/manifest/v1/v1.spec.js +3 -66
- package/lib/manifest/v1/v1.spec.js.map +1 -1
- package/lib/processValidationResult.d.ts +2 -0
- package/lib/processValidationResult.js +24 -0
- package/lib/processValidationResult.js.map +1 -0
- package/lib/validators/customKeywordValidators.d.ts +2 -0
- package/lib/validators/customKeywordValidators.js +47 -0
- package/lib/validators/customKeywordValidators.js.map +1 -0
- package/lib/validators/customKeywordValidators.spec.d.ts +1 -0
- package/lib/validators/customKeywordValidators.spec.js +100 -0
- package/lib/validators/customKeywordValidators.spec.js.map +1 -0
- package/package.json +1 -1
- package/src/JsonSchemaService.spec.ts +2198 -0
- package/src/JsonSchemaService.ts +159 -0
- package/src/JsonValidationService.spec.ts +26 -2195
- package/src/JsonValidationService.ts +64 -226
- package/src/index.ts +1 -0
- package/src/jsonTypeResolution/TypeResolver.spec.ts +12 -0
- package/src/jsonTypeResolution/TypeResolver.ts +0 -4
- package/src/manifest/v1/DxComponentInputSchema.spec.ts +21 -0
- package/src/manifest/v1/DxContentMetaSchema.json +24 -32
- package/src/manifest/v1/JobV1.ts +23 -23
- package/src/manifest/v1/__test__/schemas/invalidUiMetadata.json +31 -0
- package/src/manifest/v1/v1.spec.ts +3 -73
- package/src/manifest/v1/v1.ts +23 -23
- package/src/processValidationResult.ts +20 -0
- package/src/validators/customKeywordValidators.spec.ts +171 -0
- package/src/validators/customKeywordValidators.ts +53 -0
- 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
|
13
|
-
import
|
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 {
|
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 {
|
21
|
-
import {
|
22
|
-
|
23
|
-
|
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
|
-
*
|
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
|
-
|
312
|
-
return processValidationResult(validationResult);
|
173
|
+
return this.componentManifestSchemaService.validateInput(manifest);
|
313
174
|
}
|
314
175
|
case 'JobV1': {
|
315
|
-
|
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
|
185
|
+
return this.componentInputSchemaService.validateInput(contentSchema);
|
325
186
|
}
|
326
187
|
|
327
188
|
validateComponentInput(functionInputSchema: JSONSchema, inputValue: unknown) {
|
328
|
-
|
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
|
-
|
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
|
});
|
@@ -74,10 +74,6 @@ export class TypeResolver<P extends AnyPrimitiveType, R extends AnyResolvableTyp
|
|
74
74
|
return this.primitives.has(type);
|
75
75
|
}
|
76
76
|
|
77
|
-
isPrimitiveSchema(schema: JSONSchema): schema is P {
|
78
|
-
return this.isPrimitiveType(schema.title);
|
79
|
-
}
|
80
|
-
|
81
77
|
isResolvableSchema(schema: JSONSchema): schema is R {
|
82
78
|
return this.resolvables.has(schema.title);
|
83
79
|
}
|
@@ -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
|
-
"
|
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": {
|
package/src/manifest/v1/JobV1.ts
CHANGED
@@ -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).
|
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:
|
376
|
+
'failed validation: ui:metadata property collapsedByDefault is only valid for object properties.',
|
447
377
|
);
|
448
378
|
});
|
449
379
|
});
|