diff --git a/app/client/src/constants/PropertyControlConstants.tsx b/app/client/src/constants/PropertyControlConstants.tsx index 5f631393f1..5a68d4c92b 100644 --- a/app/client/src/constants/PropertyControlConstants.tsx +++ b/app/client/src/constants/PropertyControlConstants.tsx @@ -88,6 +88,8 @@ type ValidationConfigParams = { ) => ValidationResponse; // Function in a FUNCTION type fnString?: string; // AUTO GENERATED, SHOULD NOT BE SET BY WIDGET DEVELOPER expected?: CodeEditorExpected; // FUNCTION type expected type and example + strict?: boolean; //for strict string validation of TEXT type + ignoreCase?: boolean; //to ignore the case of key }; export type ValidationConfig = { diff --git a/app/client/src/widgets/ChartWidget/widget/propertyConfig.ts b/app/client/src/widgets/ChartWidget/widget/propertyConfig.ts index d451062e8a..8e10e89904 100644 --- a/app/client/src/widgets/ChartWidget/widget/propertyConfig.ts +++ b/app/client/src/widgets/ChartWidget/widget/propertyConfig.ts @@ -96,6 +96,16 @@ export default [ name: "chart", type: ValidationTypes.OBJECT, params: { + allowedKeys: [ + { + name: "paletteColors", + type: ValidationTypes.TEXT, + params: { + strict: true, + ignoreCase: true, + }, + }, + ], default: {}, }, }, diff --git a/app/client/src/workers/validations.test.ts b/app/client/src/workers/validations.test.ts index dec9f2124b..6bcc6e1de7 100644 --- a/app/client/src/workers/validations.test.ts +++ b/app/client/src/workers/validations.test.ts @@ -96,6 +96,39 @@ describe("Validate Validators", () => { }); }); + it("correctly validates strict text", () => { + const validation = { + type: ValidationTypes.TEXT, + params: { + required: true, + default: "abc", + allowedValues: ["abc", "123", "mno", "test"], + strict: true, + }, + }; + const inputs = ["abc", "xyz", 123]; + const expected = [ + { + isValid: true, + parsed: "abc", + }, + { + isValid: false, + parsed: "abc", + message: "Value is not allowed", + }, + { + isValid: false, + parsed: "abc", + message: `${WIDGET_TYPE_VALIDATION_ERROR} string ( abc | 123 | mno | test )`, + }, + ]; + inputs.forEach((input, index) => { + const result = validate(validation, input, DUMMY_WIDGET); + expect(result).toStrictEqual(expected[index]); + }); + }); + it("correctly validates image url", () => { const config = { type: ValidationTypes.IMAGE_URL, @@ -939,6 +972,10 @@ describe("Validate Validators", () => { label: true, value: "true", }, + { + paletteColors1: "#ffffff", + palettecolors2: "#ffffff", + }, ]; const config = [ { @@ -987,6 +1024,28 @@ describe("Validate Validators", () => { ], }, }, + { + type: ValidationTypes.OBJECT, + params: { + allowedKeys: [ + { + name: "paletteColors1", + type: ValidationTypes.TEXT, + params: { + strict: true, + }, + }, + { + name: "paletteColors2", + type: ValidationTypes.TEXT, + params: { + strict: true, + ignoreCase: true, + }, + }, + ], + }, + }, ]; const expected = [ { @@ -1000,6 +1059,13 @@ describe("Validate Validators", () => { isValid: true, parsed: { label: true, value: "true" }, }, + { + isValid: true, + parsed: { + paletteColors1: "#ffffff", + palettecolors2: "#ffffff", + }, + }, ]; inputs.forEach((input, index) => { const result = validate(config[index], input, DUMMY_WIDGET); @@ -1015,6 +1081,10 @@ describe("Validate Validators", () => { { label: true, }, + { + paletteColors1: "#ffffff", + palettecolors2: 3, + }, ]; const config = [ { @@ -1063,6 +1133,28 @@ describe("Validate Validators", () => { ], }, }, + { + type: ValidationTypes.OBJECT, + params: { + allowedKeys: [ + { + name: "paletteColors1", + type: ValidationTypes.TEXT, + params: { + strict: true, + }, + }, + { + name: "paletteColors2", + type: ValidationTypes.TEXT, + params: { + strict: true, + ignoreCase: true, + }, + }, + ], + }, + }, ]; const expected = [ { @@ -1078,6 +1170,15 @@ describe("Validate Validators", () => { message: "Missing required key: value", parsed: { label: true }, }, + { + isValid: false, + message: + "Value of key: palettecolors2 is invalid: This value does not evaluate to type string", + parsed: { + paletteColors1: "#ffffff", + palettecolors2: "", + }, + }, ]; inputs.forEach((input, index) => { const result = validate(config[index], input, DUMMY_WIDGET); diff --git a/app/client/src/workers/validations.ts b/app/client/src/workers/validations.ts index ed1f78a09f..f2a8825128 100644 --- a/app/client/src/workers/validations.ts +++ b/app/client/src/workers/validations.ts @@ -32,6 +32,20 @@ const flat = (array: Record[], uniqueParam: string) => { }); return result; }; + +function getPropertyEntry( + obj: Record, + name: string, + ignoreCase = false, +) { + if (!ignoreCase) { + return name; + } else { + const keys = Object.getOwnPropertyNames(obj); + return keys.find((key) => key.toLowerCase() === name.toLowerCase()) || name; + } +} + function validatePlainObject( config: ValidationConfig, value: Record, @@ -42,24 +56,25 @@ function validatePlainObject( const _messages: string[] = []; const parsedValue: Record = value; config.params.allowedKeys.forEach((entry) => { - if (value.hasOwnProperty(entry.name)) { + const ignoreCase = !!entry.params?.ignoreCase; + const entryName = getPropertyEntry(value, entry.name, ignoreCase); + + if (value.hasOwnProperty(entryName)) { const { isValid, message, parsed } = validate( entry, - value[entry.name], + value[entryName], props, ); - parsedValue[entry.name] = parsed; + parsedValue[entryName] = parsed; if (!isValid) { - value[entry.name] = parsed; + value[entryName] = parsed; _valid = isValid; message && - _messages.push( - `Value of key: ${entry.name} is invalid: ${message}`, - ); + _messages.push(`Value of key: ${entryName} is invalid: ${message}`); } } else if (entry.params?.required) { _valid = false; - _messages.push(`Missing required key: ${entry.name}`); + _messages.push(`Missing required key: ${entryName}`); } }); if (_valid) { @@ -303,15 +318,17 @@ export const VALIDATORS: Record = { } const isValid = isString(parsed); + const stringValidationError = { + isValid: false, + parsed: config.params?.default || "", + message: `${WIDGET_TYPE_VALIDATION_ERROR} ${getExpectedType(config)}`, + }; if (!isValid) { try { - parsed = toString(parsed); + if (!config.params?.strict) parsed = toString(parsed); + else return stringValidationError; } catch (e) { - return { - isValid: false, - parsed: config.params?.default || "", - message: `${WIDGET_TYPE_VALIDATION_ERROR} ${getExpectedType(config)}`, - }; + return stringValidationError; } } // If the value is an empty string we skip