hunk.code_window:         };
         }
         if (!moment(value).isValid()) return invalidResponse;
  
hunk.labels: add keep keep keep keep
hunk.after_edit:       } else if (value === "" && config.params?.required) {
           return invalidResponse;
  
hunk.file_path: app/client/src/workers/validations.ts
hunk.type: add
file: /* eslint-disable @typescript-eslint/no-unused-vars */
  
  import {
    ValidationTypes,
    ValidationResponse,
    Validator,
  } from "../constants/WidgetValidation";
  import _, {
    get,
    isArray,
    isObject,
    isPlainObject,
    isRegExp,
    isString,
    toString,
    uniq,
  } from "lodash";
  
  import moment from "moment";
  import { ValidationConfig } from "constants/PropertyControlConstants";
  import evaluate from "./evaluate";
  
  import getIsSafeURL from "utils/validation/getIsSafeURL";
  import * as log from "loglevel";
  export const UNDEFINED_VALIDATION = "UNDEFINED_VALIDATION";
  
  const flat = (array: Record<string, any>[], uniqueParam: string) => {
    let result: { value: string }[] = [];
    array.forEach((a) => {
      result.push({ value: a[uniqueParam] });
      if (Array.isArray(a.children)) {
        result = result.concat(flat(a.children, uniqueParam));
      }
    });
    return result;
  };
  
  function getPropertyEntry(
    obj: Record<string, unknown>,
    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<string, unknown>,
    props: Record<string, unknown>,
  ) {
    if (config.params?.allowedKeys) {
      let _valid = true;
      const _messages: string[] = [];
      config.params.allowedKeys.forEach((entry) => {
        const ignoreCase = !!entry.params?.ignoreCase;
        const entryName = getPropertyEntry(value, entry.name, ignoreCase);
  
        if (value.hasOwnProperty(entryName)) {
          const { isValid, messages, parsed } = validate(
            entry,
            value[entryName],
            props,
          );
          if (!isValid) {
            value[entryName] = parsed;
            _valid = isValid;
            messages &&
              messages.map((message) => {
                _messages.push(
                  `Value of key: ${entryName} is invalid: ${message}`,
                );
              });
          }
        } else if (entry.params?.required) {
          _valid = false;
          _messages.push(`Missing required key: ${entryName}`);
        }
      });
      if (_valid) {
        return {
          isValid: true,
          parsed: value,
        };
      }
      return {
        isValid: false,
        parsed: config.params?.default || value,
        messages: _messages,
      };
    }
    return {
      isValid: true,
      parsed: value,
    };
  }
  
  function validateArray(
    config: ValidationConfig,
    value: unknown[],
    props: Record<string, unknown>,
  ) {
    let _isValid = true;
    const _messages: string[] = [];
    const whiteList = config.params?.allowedValues;
    if (whiteList) {
      for (const entry of value) {
        if (!whiteList.includes(entry)) {
          return {
            isValid: false,
            parsed: config.params?.default || [],
            messages: [`Disallowed value: ${entry}`],
          };
        }
      }
    }
    // Check uniqueness of object elements in an array
    if (
      config.params?.children?.type === ValidationTypes.OBJECT &&
      (config.params.children.params?.allowedKeys || []).length > 0
    ) {
      const allowedKeysCofigArray =
        config.params.children.params?.allowedKeys || [];
  
      const allowedKeys = (config.params.children.params?.allowedKeys || []).map(
        (key) => key.name,
      );
      type ObjectKeys = typeof allowedKeys[number];
      type ItemType = {
        [key in ObjectKeys]: string;
      };
  
      const valueWithType = value as ItemType[];
      allowedKeysCofigArray.forEach((allowedKeyConfig) => {
        if (allowedKeyConfig.params?.unique) {
          const allowedKeyValues = valueWithType.map(
            (item) => item[allowedKeyConfig.name],
          );
          const normalizedValues = valueWithType.map((item) =>
            item[allowedKeyConfig.name].toLowerCase(),
          );
          // Check if value is duplicated
          _messages.push(
            ...allowedKeyValues.reduce(
              (acc: string[], currentValue, currentIndex) => {
                if (
                  normalizedValues.indexOf(currentValue.toLowerCase()) !==
                  currentIndex
                ) {
                  acc.push(
                    `Duplicated entry at index: ${currentIndex + 1}, key: ${
                      allowedKeyConfig.name
                    }, value: ${currentValue}`,
                  );
                }
                return acc;
              },
              [],
            ),
          );
          if (_messages.length > 0) {
            _isValid = false;
          }
        }
      });
    }
  
    const children = config.params?.children;
  
    if (children) {
      value.forEach((entry, index) => {
        const validation = validate(children, entry, props);
        if (!validation.isValid) {
          _isValid = false;
          validation.messages?.map((message) =>
            _messages.push(`Invalid entry at index: ${index}. ${message}`),
          );
        }
      });
    }
    if (config.params?.unique) {
      if (isArray(config.params?.unique)) {
        for (const param of config.params?.unique) {
          const shouldBeUnique = value.map((entry) =>
            get(entry, param as string, ""),
          );
          if (uniq(shouldBeUnique).length !== value.length) {
            _isValid = false;
            _messages.push(
              `path:${param} must be unique. Duplicate values found`,
            );
            break;
          }
        }
      } else if (
        uniq(value.map((entry) => JSON.stringify(entry))).length !== value.length
      ) {
        _isValid = false;
        _messages.push(`Array must be unique. Duplicate values found`);
      }
    }
  
    return {
      isValid: _isValid,
      parsed: _isValid ? value : config.params?.default || [],
      messages: _messages,
    };
  }
  
  export const validate = (
    config: ValidationConfig,
    value: unknown,
    props: Record<string, unknown>,
  ) => {
    const _result = VALIDATORS[config.type as ValidationTypes](
      config,
      value,
      props,
    );
    return _result;
  };
  
  export const WIDGET_TYPE_VALIDATION_ERROR =
    "This value does not evaluate to type"; // TODO: Lot's of changes in validations.ts file
  
  export function getExpectedType(config?: ValidationConfig): string | undefined {
    if (!config) return UNDEFINED_VALIDATION; // basic fallback
    switch (config.type) {
      case ValidationTypes.FUNCTION:
        return config.params?.expected?.type || "unknown";
      case ValidationTypes.TEXT:
        let result = "string";
        if (config.params?.allowedValues) {
          const allowed = config.params.allowedValues.join(" | ");
          result = result + ` ( ${allowed} )`;
        }
        if (config.params?.regex) {
          result = config.params?.regex.source;
        }
        if (config.params?.expected?.type) result = config.params?.expected.type;
        return result;
      case ValidationTypes.REGEX:
        return "regExp";
      case ValidationTypes.DATE_ISO_STRING:
        return "ISO 8601 date string";
      case ValidationTypes.BOOLEAN:
        return "boolean";
      case ValidationTypes.NUMBER:
        let type = "number";
        if (config.params?.min) {
          type = `${type} Min: ${config.params?.min}`;
        }
        if (config.params?.max) {
          type = `${type} Max: ${config.params?.max}`;
        }
        if (config.params?.required) {
          type = `${type} Required`;
        }
  
        return type;
      case ValidationTypes.OBJECT:
        type = "Object";
        if (config.params?.allowedKeys) {
          type = "{";
          config.params?.allowedKeys.forEach((allowedKeyConfig) => {
            const _expected = getExpectedType(allowedKeyConfig);
            type = `${type} "${allowedKeyConfig.name}": "${_expected}",`;
          });
          type = `${type.substring(0, type.length - 1)} }`;
          return type;
        }
        return type;
      case ValidationTypes.ARRAY:
      case ValidationTypes.NESTED_OBJECT_ARRAY:
        if (config.params?.allowedValues) {
          const allowed = config.params?.allowedValues.join("' | '");
          return `Array<'${allowed}'>`;
        }
        if (config.params?.children) {
          const children = getExpectedType(config.params.children);
          return `Array<${children}>`;
        }
        return "Array";
      case ValidationTypes.OBJECT_ARRAY:
        return `Array<Object>`;
      case ValidationTypes.IMAGE_URL:
        return `base64 encoded image | data uri | image url`;
      case ValidationTypes.SAFE_URL:
        return "URL";
    }
  }
  
  export const VALIDATORS: Record<ValidationTypes, Validator> = {
    [ValidationTypes.TEXT]: (
      config: ValidationConfig,
      value: unknown,
      props: Record<string, unknown>,
    ): ValidationResponse => {
      if (value === undefined || value === null || value === "") {
        if (config.params && config.params.required) {
          return {
            isValid: false,
            parsed: config.params?.default || "",
            messages: [
              `${WIDGET_TYPE_VALIDATION_ERROR} ${getExpectedType(config)}`,
            ],
          };
        }
  
        return {
          isValid: true,
          parsed: config.params?.default || "",
        };
      }
      let parsed = value;
  
      if (isObject(value)) {
        return {
          isValid: false,
          parsed: JSON.stringify(value, null, 2),
          messages: [
            `${WIDGET_TYPE_VALIDATION_ERROR} ${getExpectedType(config)}`,
          ],
        };
      }
  
      const isValid = isString(parsed);
      const stringValidationError = {
        isValid: false,
        parsed: config.params?.default || "",
        messages: [`${WIDGET_TYPE_VALIDATION_ERROR} ${getExpectedType(config)}`],
      };
      if (!isValid) {
        try {
          if (!config.params?.strict) parsed = toString(parsed);
          else return stringValidationError;
        } catch (e) {
          return stringValidationError;
        }
      }
      if (config.params?.allowedValues) {
        if (!config.params?.allowedValues.includes((parsed as string).trim())) {
          return {
            parsed: config.params?.default || "",
            messages: [`Disallowed value: ${parsed}`],
            isValid: false,
          };
        }
      }
  
      if (
        config.params?.regex &&
        isRegExp(config.params?.regex) &&
        !config.params?.regex.test(parsed as string)
      ) {
        return {
          parsed: config.params?.default || "",
          messages: [
            `${WIDGET_TYPE_VALIDATION_ERROR} ${getExpectedType(config)}`,
          ],
          isValid: false,
        };
      }
  
      return {
        isValid: true,
        parsed,
      };
    },
    // TODO(abhinav): The original validation does not make sense fix this.
    [ValidationTypes.REGEX]: (
      config: ValidationConfig,
      value: unknown,
      props: Record<string, unknown>,
    ): ValidationResponse => {
      const { isValid, messages, parsed } = VALIDATORS[ValidationTypes.TEXT](
        config,
        value,
        props,
      );
  
      if (!isValid) {
        return {
          isValid: false,
          parsed: new RegExp(parsed),
          messages: [
            `${WIDGET_TYPE_VALIDATION_ERROR} ${getExpectedType(config)}`,
          ],
        };
      }
  
      return { isValid, parsed, messages };
    },
    [ValidationTypes.NUMBER]: (
      config: ValidationConfig,
      value: unknown,
      props: Record<string, unknown>,
    ): ValidationResponse => {
      if (value === undefined || value === null || value === "") {
        if (config.params?.required) {
          return {
            isValid: false,
            parsed: config.params?.default || 0,
            messages: ["This value is required"],
          };
        }
  
        if (value === "") {
          return {
            isValid: true,
            parsed: config.params?.default || 0,
          };
        }
  
        return {
          isValid: true,
          parsed: value,
        };
      }
      if (!Number.isFinite(value) && !isString(value)) {
        return {
          isValid: false,
          parsed: config.params?.default || 0,
          messages: [
            `${WIDGET_TYPE_VALIDATION_ERROR} ${getExpectedType(config)}`,
          ],
        };
      }
  
      // check for min and max limits
      let parsed: number = value as number;
      if (isString(value)) {
        if (/^-?\d+\.?\d*$/.test(value)) {
          parsed = Number(value);
        } else {
          return {
            isValid: false,
            parsed: value || config.params?.default || 0,
            messages: [
              `${WIDGET_TYPE_VALIDATION_ERROR} ${getExpectedType(config)}`,
            ],
          };
        }
      }
  
      if (
        config.params?.min !== undefined &&
        Number.isFinite(config.params.min)
      ) {
        if (parsed < Number(config.params.min)) {
          return {
            isValid: false,
            parsed: parsed || config.params.min || 0,
            messages: [`Minimum allowed value: ${config.params.min}`],
          };
        }
      }
  
      if (
        config.params?.max !== undefined &&
        Number.isFinite(config.params.max)
      ) {
        if (parsed > Number(config.params.max)) {
          return {
            isValid: false,
            parsed: config.params.max || parsed || 0,
            messages: [`Maximum allowed value: ${config.params.max}`],
          };
        }
      }
      if (config.params?.natural && (parsed < 0 || !Number.isInteger(parsed))) {
        return {
          isValid: false,
          parsed: config.params.default || parsed || 0,
          messages: [`Value should be a positive integer`],
        };
      }
  
      return {
        isValid: true,
        parsed,
      };
    },
    [ValidationTypes.BOOLEAN]: (
      config: ValidationConfig,
      value: unknown,
      props: Record<string, unknown>,
    ): ValidationResponse => {
      if (value === undefined || value === null || value === "") {
        if (config.params && config.params.required) {
          return {
            isValid: false,
            parsed: !!config.params?.default,
            messages: [
              `${WIDGET_TYPE_VALIDATION_ERROR} ${getExpectedType(config)}`,
            ],
          };
        }
  
        if (value === "") {
          return {
            isValid: true,
            parsed: config.params?.default || false,
          };
        }
  
        return { isValid: true, parsed: config.params?.default || value };
      }
      const isABoolean = value === true || value === false;
      const isStringTrueFalse = value === "true" || value === "false";
      const isValid = isABoolean || isStringTrueFalse;
  
      let parsed = value;
      if (isStringTrueFalse) parsed = value !== "false";
  
      if (!isValid) {
        return {
          isValid: false,
          parsed: config.params?.default || false,
          messages: [
            `${WIDGET_TYPE_VALIDATION_ERROR} ${getExpectedType(config)}`,
          ],
        };
      }
  
      return { isValid, parsed };
    },
    [ValidationTypes.OBJECT]: (
      config: ValidationConfig,
      value: unknown,
      props: Record<string, unknown>,
    ): ValidationResponse => {
      if (
        value === undefined ||
        value === null ||
        (isString(value) && value.trim().length === 0)
      ) {
        if (config.params && config.params.required) {
          return {
            isValid: false,
            parsed: config.params?.default || {},
            messages: [
              `${WIDGET_TYPE_VALIDATION_ERROR}: ${getExpectedType(config)}`,
            ],
          };
        }
        return {
          isValid: true,
          parsed: config.params?.default || value,
        };
      }
  
      if (isPlainObject(value)) {
        return validatePlainObject(
          config,
          value as Record<string, unknown>,
          props,
        );
      }
  
      try {
        const result = { parsed: JSON.parse(value as string), isValid: true };
        if (isPlainObject(result.parsed)) {
          return validatePlainObject(config, result.parsed, props);
        }
        return {
          isValid: false,
          parsed: config.params?.default || {},
          messages: [
            `${WIDGET_TYPE_VALIDATION_ERROR}: ${getExpectedType(config)}`,
          ],
        };
      } catch (e) {
        return {
          isValid: false,
          parsed: config.params?.default || {},
          messages: [
            `${WIDGET_TYPE_VALIDATION_ERROR}: ${getExpectedType(config)}`,
          ],
        };
      }
    },
    [ValidationTypes.ARRAY]: (
      config: ValidationConfig,
      value: unknown,
      props: Record<string, unknown>,
    ): ValidationResponse => {
      const invalidResponse = {
        isValid: false,
        parsed: config.params?.default || [],
        messages: [`${WIDGET_TYPE_VALIDATION_ERROR} ${getExpectedType(config)}`],
      };
      if (value === undefined || value === null || value === "") {
        if (
          config.params &&
          config.params.required &&
          !isArray(config.params.default)
        ) {
          invalidResponse.messages = [
            "This property is required for the widget to function correctly",
          ];
          return invalidResponse;
        }
        if (value === "") {
          return {
            isValid: true,
            parsed: config.params?.default || [],
          };
        }
        if (config.params && isArray(config.params.default)) {
          return {
            isValid: true,
            parsed: config.params?.default,
          };
        }
  
        return {
          isValid: true,
          parsed: value,
        };
      }
  
      if (isString(value)) {
        try {
          const _value = JSON.parse(value);
          if (Array.isArray(_value)) {
            const result = validateArray(config, _value, props);
            return result;
          }
        } catch (e) {
          return invalidResponse;
        }
      }
  
      if (Array.isArray(value)) {
        return validateArray(config, value, props);
      }
  
      return invalidResponse;
    },
    [ValidationTypes.OBJECT_ARRAY]: (
      config: ValidationConfig,
      value: unknown,
      props: Record<string, unknown>,
    ): ValidationResponse => {
      const invalidResponse = {
        isValid: false,
        parsed: config.params?.default || [{}],
        messages: [`${WIDGET_TYPE_VALIDATION_ERROR} ${getExpectedType(config)}`],
      };
      if (value === undefined || value === null || value === "") {
        if (config.params?.required) return invalidResponse;
  
        if (value === "") {
          return {
            isValid: true,
            parsed: config.params?.default || [{}],
          };
        }
  
        return { isValid: true, parsed: value };
      }
      if (!isString(value) && !Array.isArray(value)) {
        return invalidResponse;
      }
  
      let parsed = value;
  
      if (isString(value)) {
        try {
          parsed = JSON.parse(value);
        } catch (e) {
          return invalidResponse;
        }
      }
  
      if (Array.isArray(parsed)) {
        if (parsed.length === 0) {
          if (config.params?.required) {
            return invalidResponse;
          } else {
            return {
              isValid: true,
              parsed: config.params?.default || [{}],
            };
          }
        }
  
        for (const [index, parsedEntry] of parsed.entries()) {
          if (!isPlainObject(parsedEntry)) {
            return {
              ...invalidResponse,
              messages: [`Invalid object at index ${index}`],
            };
          }
        }
        return { isValid: true, parsed };
      }
      return invalidResponse;
    },
  
    [ValidationTypes.NESTED_OBJECT_ARRAY]: (
      config: ValidationConfig,
      value: unknown,
      props: Record<string, unknown>,
    ): ValidationResponse => {
      let response: ValidationResponse = {
        isValid: false,
        parsed: config.params?.default || [],
        messages: [`${WIDGET_TYPE_VALIDATION_ERROR} ${getExpectedType(config)}`],
      };
      response = VALIDATORS.ARRAY(config, value, props);
  
      if (!response.isValid) {
        return response;
      }
      // Check if all values and children values are unique
      if (config.params?.unique && response.parsed.length) {
        if (isArray(config.params?.unique)) {
          for (const param of config.params?.unique) {
            const flattenedArray = flat(response.parsed, param);
            const shouldBeUnique = flattenedArray.map((entry) =>
              get(entry, param, ""),
            );
            if (uniq(shouldBeUnique).length !== flattenedArray.length) {
              response = {
                ...response,
                isValid: false,
                messages: [
                  `path:${param} must be unique. Duplicate values found`,
                ],
              };
            }
          }
        }
      }
      return response;
    },
    [ValidationTypes.DATE_ISO_STRING]: (
      config: ValidationConfig,
      value: unknown,
      props: Record<string, unknown>,
    ): ValidationResponse => {
      const invalidResponse = {
        isValid: false,
        parsed: config.params?.default || moment().toISOString(true),
        messages: [`Value does not match: ${getExpectedType(config)}`],
      };
      if (value === undefined || value === null || !isString(value)) {
        if (!config.params?.required) {
          return {
            isValid: true,
            parsed: value,
          };
        }
        return invalidResponse;
      }
      if (isString(value)) {
        if (value === "" && !config.params?.required) {
          return {
            isValid: true,
            parsed: config.params?.default || moment().toISOString(true),
          };
        }
  
        if (!moment(value).isValid()) return invalidResponse;
  
        if (
          value === moment(value).toISOString() ||
          value === moment(value).toISOString(true)
        ) {
          return {
            isValid: true,
            parsed: value,
          };
        }
        if (moment(value).isValid())
          return { isValid: true, parsed: moment(value).toISOString(true) };
      }
      return invalidResponse;
    },
    [ValidationTypes.FUNCTION]: (
      config: ValidationConfig,
      value: unknown,
      props: Record<string, unknown>,
    ): ValidationResponse => {
      const invalidResponse = {
        isValid: false,
        parsed: undefined,
        messages: ["Failed to validate"],
      };
      if (config.params?.fnString && isString(config.params?.fnString)) {
        try {
          const { result } = evaluate(config.params.fnString, {}, {}, undefined, [
            value,
            props,
            _,
            moment,
          ]);
          return result;
        } catch (e) {
          log.error("Validation function error: ", { e });
        }
      }
      return invalidResponse;
    },
    [ValidationTypes.IMAGE_URL]: (
      config: ValidationConfig,
      value: unknown,
      props: Record<string, unknown>,
    ): ValidationResponse => {
      const invalidResponse = {
        isValid: false,
        parsed: config.params?.default || "",
        messages: [`${WIDGET_TYPE_VALIDATION_ERROR}: ${getExpectedType(config)}`],
      };
      const base64Regex = /^(?:[A-Za-z\d+\/]{4})*?(?:[A-Za-z\d+\/]{2}(?:==)?|[A-Za-z\d+\/]{3}=?)?$/;
      const base64ImageRegex = /^data:image\/.*;base64/;
      const imageUrlRegex = /(http(s?):)([/|.|\w|\s|-])*\.(?:jpeg|jpg|gif|png)??(?:&?[^=&]*=[^=&]*)*/;
      if (
        value === undefined ||
        value === null ||
        (isString(value) && value.trim().length === 0)
      ) {
        if (config.params && config.params.required) return invalidResponse;
        return { isValid: true, parsed: value };
      }
      if (isString(value)) {
        if (imageUrlRegex.test(value.trim())) {
          return { isValid: true, parsed: value.trim() };
        }
        if (base64ImageRegex.test(value)) {
          return {
            isValid: true,
            parsed: value,
          };
        }
        if (base64Regex.test(value) && btoa(atob(value)) === value) {
          return { isValid: true, parsed: `data:image/png;base64,${value}` };
        }
      }
      return invalidResponse;
    },
    [ValidationTypes.SAFE_URL]: (
      config: ValidationConfig,
      value: unknown,
    ): ValidationResponse => {
      const invalidResponse = {
        isValid: false,
        parsed: config?.params?.default || "",
        messages: [`${WIDGET_TYPE_VALIDATION_ERROR}: ${getExpectedType(config)}`],
      };
  
      if (typeof value === "string" && getIsSafeURL(value)) {
        return {
          isValid: true,
          parsed: value,
        };
      } else {
        return invalidResponse;
      }
    },
  
    /**
     *
     * TABLE_PROPERTY can be used in scenarios where we wanted to validate
     * using ValidationTypes.ARRAY or ValidationTypes.* at the same time.
     * This is needed in case of properties inside Table widget where we use COMPUTE_VALUE
     * For more info: https://github.com/appsmithorg/appsmith/pull/9396
     *
     */
    [ValidationTypes.TABLE_PROPERTY]: (
      config: ValidationConfig,
      value: unknown,
      props: Record<string, unknown>,
    ): ValidationResponse => {
      if (!config.params?.type)
        return {
          isValid: false,
          parsed: undefined,
          messages: ["Invalid validation"],
        };
  
      // Validate when JS mode is disabled
      const result = VALIDATORS[config.params.type as ValidationTypes](
        config.params as ValidationConfig,
        value,
        props,
      );
      if (result.isValid) return result;
  
      // Validate when JS mode is enabled
      const resultValue = [];
      if (_.isArray(value)) {
        for (const item of value) {
          const result = VALIDATORS[config.params.type](
            config.params as ValidationConfig,
            item,
            props,
          );
          if (!result.isValid) return result;
          resultValue.push(result.parsed);
        }
      } else {
        return {
          isValid: false,
          parsed: config.params?.params?.default,
          messages: result.messages,
        };
      }
  
      return {
        isValid: true,
        parsed: resultValue,
      };
    },
  };
  