diff --git a/packages/core/src/mappers/renderer.ts b/packages/core/src/mappers/renderer.ts index 9883d5063..ceedad814 100644 --- a/packages/core/src/mappers/renderer.ts +++ b/packages/core/src/mappers/renderer.ts @@ -170,7 +170,25 @@ export const createDefaultValue = ( schema: JsonSchema, rootSchema: JsonSchema ) => { - const resolvedSchema = Resolve.schema(schema, schema.$ref, rootSchema); + const defaultValue = doCreateDefaultValue(schema, rootSchema); + + // preserve the backward compatibility where it is returning an empty object if we can't determine the default value + return defaultValue === undefined ? {} : defaultValue; +}; + +/** + * Create a default value based on the given schema. + * @param schema the schema for which to create a default value. + * @returns the default value to use, undefined if none was found + */ +export const doCreateDefaultValue = ( + schema: JsonSchema, + rootSchema: JsonSchema +) => { + const resolvedSchema = + typeof schema.$ref === 'string' + ? Resolve.schema(rootSchema, schema.$ref, rootSchema) + : schema; if (resolvedSchema.default !== undefined) { return extractDefaults(resolvedSchema, rootSchema); } @@ -183,22 +201,56 @@ export const createDefaultValue = ( return convertDateToString(new Date(), resolvedSchema.format); } return ''; - } else if ( - hasType(resolvedSchema, 'integer') || - hasType(resolvedSchema, 'number') - ) { + } + if (hasType(resolvedSchema, 'integer') || hasType(resolvedSchema, 'number')) { return 0; - } else if (hasType(resolvedSchema, 'boolean')) { + } + if (hasType(resolvedSchema, 'boolean')) { return false; - } else if (hasType(resolvedSchema, 'array')) { + } + if (hasType(resolvedSchema, 'array')) { return []; - } else if (hasType(resolvedSchema, 'object')) { + } + if (hasType(resolvedSchema, 'object')) { return extractDefaults(resolvedSchema, rootSchema); - } else if (hasType(resolvedSchema, 'null')) { + } + if (hasType(resolvedSchema, 'null')) { return null; - } else { - return {}; } + + const combinators: CombinatorKeyword[] = ['oneOf', 'anyOf', 'allOf']; + for (const combinator of combinators) { + if (schema[combinator] && Array.isArray(schema[combinator])) { + const combinatorDefault = createDefaultValueForCombinatorSchema( + schema[combinator], + rootSchema + ); + if (combinatorDefault !== undefined) { + return combinatorDefault; + } + } + } + + // no default value found + return undefined; +}; + +const createDefaultValueForCombinatorSchema = ( + combinatorSchemas: JsonSchema[], + rootSchema: JsonSchema +): any => { + if (combinatorSchemas.length > 0) { + for (const combinatorSchema of combinatorSchemas) { + const result = doCreateDefaultValue(combinatorSchema, rootSchema); + if (result !== undefined) { + // return the first one with type information + return result; + } + } + } + + // no default value found + return undefined; }; /** @@ -214,10 +266,26 @@ export const extractDefaults = (schema: JsonSchema, rootSchema: JsonSchema) => { const resolvedProperty = property.$ref ? Resolve.schema(rootSchema, property.$ref, rootSchema) : property; - if (resolvedProperty.default !== undefined) { + if (resolvedProperty && resolvedProperty.default !== undefined) { result[key] = cloneDeep(resolvedProperty.default); } } + // there could be more properties in allOf schemas + if (schema.allOf && Array.isArray(schema.allOf)) { + schema.allOf.forEach((allOfSchema) => { + if (allOfSchema && allOfSchema.properties) { + for (const key in allOfSchema.properties) { + const property = allOfSchema.properties[key]; + const resolvedProperty = property.$ref + ? Resolve.schema(rootSchema, property.$ref, rootSchema) + : property; + if (resolvedProperty && resolvedProperty.default !== undefined) { + result[key] = cloneDeep(resolvedProperty.default); + } + } + } + }); + } return result; } return cloneDeep(schema.default); diff --git a/packages/core/test/mappers/renderer.test.ts b/packages/core/test/mappers/renderer.test.ts index 8e686e884..f8eee2982 100644 --- a/packages/core/test/mappers/renderer.test.ts +++ b/packages/core/test/mappers/renderer.test.ts @@ -654,6 +654,134 @@ test('createDefaultValue', (t) => { bool: true, array: ['a', 'b', 'c'], }); + + const schemaOneOf: JsonSchema = { + oneOf: [ + { + type: 'string', + default: 'oneOfString', + }, + { + type: 'number', + default: 42, + }, + ], + }; + const rootSchemaOneOf: JsonSchema = { + definitions: {}, + }; + const defaultValueOneOf = createDefaultValue(schemaOneOf, rootSchemaOneOf); + t.is(defaultValueOneOf, 'oneOfString'); + + const schemaAnyOf: JsonSchema = { + anyOf: [ + { + type: 'number', + }, + { + type: 'string', + default: 'anyOfString', + }, + ], + }; + const rootSchemaAnyOf: JsonSchema = { + definitions: {}, + }; + const defaultValueAnyOf = createDefaultValue(schemaAnyOf, rootSchemaAnyOf); + t.is(defaultValueAnyOf, 0); + + console.log('testcase allof'); + const schemaAllOf: JsonSchema = { + allOf: [ + { + properties: { + foo: { + type: 'string', + default: 'foo', + }, + }, + }, + { + properties: { + bar: { + type: 'number', + default: 42, + }, + }, + }, + ], + }; + const rootSchemaAllOf: JsonSchema = { + definitions: {}, + }; + const defaultValueAllOf = createDefaultValue(schemaAllOf, rootSchemaAllOf); + t.deepEqual(defaultValueAllOf, { foo: 'foo', bar: 42 }); + + const schemaOneOfEmpty: JsonSchema = { + oneOf: [ + { + type: 'string', + }, + { + type: 'number', + }, + ], + }; + const rootSchemaOneOfEmpty: JsonSchema = { + definitions: {}, + }; + const defaultValueOneOfEmpty = createDefaultValue( + schemaOneOfEmpty, + rootSchemaOneOfEmpty + ); + t.deepEqual(defaultValueOneOfEmpty, ''); + + const schemaAnyOfEmpty: JsonSchema = { + anyOf: [ + { + type: 'string', + }, + { + type: 'number', + }, + ], + }; + const rootSchemaAnyOfEmpty: JsonSchema = { + definitions: {}, + }; + const defaultValueAnyOfEmpty = createDefaultValue( + schemaAnyOfEmpty, + rootSchemaAnyOfEmpty + ); + t.deepEqual(defaultValueAnyOfEmpty, ''); + + const schemaAllOfEmpty: JsonSchema = { + allOf: [ + { + properties: { + foo: { + type: 'string', + }, + }, + }, + { + properties: { + bar: { + type: 'number', + }, + }, + }, + ], + }; + const rootSchemaAllOfEmpty: JsonSchema = { + definitions: {}, + }; + const defaultValueAllOfEmpty = createDefaultValue( + schemaAllOfEmpty, + rootSchemaAllOfEmpty + ); + console.log('defaultValueAllOfEmpty', defaultValueAllOfEmpty); + t.deepEqual(defaultValueAllOfEmpty, {}); }); test(`mapStateToJsonFormsRendererProps should use registered UI schema given ownProps schema`, (t) => {