diff --git a/packages/core/src/utils.js b/packages/core/src/utils.js index a03d002bda..b0adb99e0f 100644 --- a/packages/core/src/utils.js +++ b/packages/core/src/utils.js @@ -572,7 +572,7 @@ export function optionsList(schema) { }); } else { const altSchemas = schema.oneOf || schema.anyOf; - return altSchemas.map((schema, i) => { + return altSchemas.map(schema => { const value = toConstant(schema); const label = schema.title || String(value); return { @@ -665,12 +665,40 @@ export function stubExistingAdditionalProperties( return schema; } +const resolveCondition = (schema, rootSchema, formdata) => { + let { + if: expression, + then, + else: otherwise, + ...resolvedSchemaLessConditional + } = schema; + + const conditionalSchema = isValid(expression, formdata, rootSchema) + ? then + : otherwise; + + if (conditionalSchema) { + return retrieveSchema( + mergeSchemas( + resolvedSchemaLessConditional, + retrieveSchema(conditionalSchema, rootSchema, formdata) + ), + rootSchema, + formdata + ); + } else { + return retrieveSchema(resolvedSchemaLessConditional, rootSchema, formdata); + } +}; + export function resolveSchema(schema, rootSchema = {}, formData = {}) { if (schema.hasOwnProperty("$ref")) { return resolveReference(schema, rootSchema, formData); } else if (schema.hasOwnProperty("dependencies")) { const resolvedSchema = resolveDependencies(schema, rootSchema, formData); return retrieveSchema(resolvedSchema, rootSchema, formData); + } else if (schema.hasOwnProperty("if")) { + return resolveCondition(schema, rootSchema, formData); } else if (schema.hasOwnProperty("allOf")) { return { ...schema, diff --git a/packages/core/test/utils_test.js b/packages/core/test/utils_test.js index 94df9f8590..7450002cb5 100644 --- a/packages/core/test/utils_test.js +++ b/packages/core/test/utils_test.js @@ -2403,6 +2403,210 @@ describe("utils", () => { }); }); }); + + describe("ifThenElse", () => { + it("should resolve if, then", () => { + const schema = { + type: "object", + properties: { + country: { + default: "United States of America", + enum: ["United States of America", "Canada"], + }, + }, + if: { + properties: { country: { const: "United States of America" } }, + }, + then: { + properties: { postal_code: { pattern: "[0-9]{5}(-[0-9]{4})?" } }, + }, + else: { + properties: { + postal_code: { pattern: "[A-Z][0-9][A-Z] [0-9][A-Z][0-9]" }, + }, + }, + }; + const definitions = {}; + const formData = { + country: "United States of America", + postal_code: "20500", + }; + expect(retrieveSchema(schema, { definitions }, formData)).eql({ + type: "object", + properties: { + country: { + default: "United States of America", + enum: ["United States of America", "Canada"], + }, + postal_code: { pattern: "[0-9]{5}(-[0-9]{4})?" }, + }, + }); + }); + it("should resolve if, else", () => { + const schema = { + type: "object", + properties: { + country: { + default: "United States of America", + enum: ["United States of America", "Canada"], + }, + }, + if: { + properties: { country: { const: "United States of America" } }, + }, + then: { + properties: { postal_code: { pattern: "[0-9]{5}(-[0-9]{4})?" } }, + }, + else: { + properties: { + postal_code: { pattern: "[A-Z][0-9][A-Z] [0-9][A-Z][0-9]" }, + }, + }, + }; + const definitions = {}; + const formData = { + country: "Canada", + postal_code: "K1M 1M4", + }; + expect(retrieveSchema(schema, { definitions }, formData)).eql({ + type: "object", + properties: { + country: { + default: "United States of America", + enum: ["United States of America", "Canada"], + }, + postal_code: { pattern: "[A-Z][0-9][A-Z] [0-9][A-Z][0-9]" }, + }, + }); + }); + it("should resolve multiple conditions", () => { + const schema = { + type: "object", + properties: { + animal: { + enum: ["Cat", "Fish"], + }, + }, + allOf: [ + { + if: { + properties: { animal: { const: "Cat" } }, + }, + then: { + properties: { + food: { type: "string", enum: ["meat", "grass", "fish"] }, + }, + }, + required: ["food"], + }, + { + if: { + properties: { animal: { const: "Fish" } }, + }, + then: { + properties: { + food: { + type: "string", + enum: ["insect", "worms"], + }, + water: { + type: "string", + enum: ["lake", "sea"], + }, + }, + required: ["food", "water"], + }, + }, + { + required: ["animal"], + }, + ], + }; + const definitions = {}; + const formData = { + animal: "Cat", + }; + + expect(retrieveSchema(schema, { definitions }, formData)).eql({ + type: "object", + properties: { + animal: { + enum: ["Cat", "Fish"], + }, + food: { type: "string", enum: ["meat", "grass", "fish"] }, + }, + required: ["animal", "food"], + }); + }); + it.only("should resolve $ref", () => { + const schema = { + type: "object", + properties: { + animal: { + enum: ["Cat", "Fish"], + }, + }, + allOf: [ + { + if: { + properties: { animal: { const: "Cat" } }, + }, + then: { + $ref: "#/definitions/cat", + }, + required: ["food"], + }, + { + if: { + properties: { animal: { const: "Fish" } }, + }, + then: { + $ref: "#/definitions/fish", + }, + }, + { + required: ["animal"], + }, + ], + }; + + const definitions = { + cat: { + properties: { + food: { type: "string", enum: ["meat", "grass", "fish"] }, + }, + }, + fish: { + properties: { + food: { + type: "string", + enum: ["insect", "worms"], + }, + water: { + type: "string", + enum: ["lake", "sea"], + }, + }, + required: ["food", "water"], + }, + }; + + const formData = { + animal: "Cat", + }; + + expect(retrieveSchema(schema, { definitions }, formData)).eql({ + type: "object", + properties: { + animal: { + enum: ["Cat", "Fish"], + }, + food: { type: "string", enum: ["meat", "grass", "fish"] }, + }, + required: ["animal", "food"], + }); + }); + }); }); describe("shouldRender", () => { diff --git a/packages/playground/src/samples/ifThenElse.js b/packages/playground/src/samples/ifThenElse.js new file mode 100644 index 0000000000..0c834f2629 --- /dev/null +++ b/packages/playground/src/samples/ifThenElse.js @@ -0,0 +1,45 @@ +module.exports = { + schema: { + type: "object", + properties: { + animal: { + enum: ["Cat", "Fish"], + }, + }, + allOf: [ + { + if: { + properties: { animal: { const: "Cat" } }, + }, + then: { + properties: { + food: { type: "string", enum: ["meat", "grass", "fish"] }, + }, + required: ["food"], + }, + }, + { + if: { + properties: { animal: { const: "Fish" } }, + }, + then: { + properties: { + food: { + type: "string", + enum: ["insect", "worms"], + }, + water: { + type: "string", + enum: ["lake", "sea"], + }, + }, + required: ["food", "water"], + }, + }, + { + required: ["animal"], + }, + ], + }, + formData: {}, +}; diff --git a/packages/playground/src/samples/index.js b/packages/playground/src/samples/index.js index 991f5215f8..236c176490 100644 --- a/packages/playground/src/samples/index.js +++ b/packages/playground/src/samples/index.js @@ -26,6 +26,7 @@ import nullable from "./nullable"; import nullField from "./null"; import errorSchema from "./errorSchema"; import defaults from "./defaults"; +import ifThenElse from "./ifThenElse"; export const samples = { Simple: simple, @@ -52,6 +53,7 @@ export const samples = { "Any Of": anyOf, "One Of": oneOf, "All Of": allOf, + "If Then Else": ifThenElse, "Null fields": nullField, Nullable: nullable, ErrorSchema: errorSchema,