Skip to content

Commit

Permalink
feat(webform): doc validator
Browse files Browse the repository at this point in the history
  • Loading branch information
pkim-gswell committed Nov 3, 2023
1 parent fc76a61 commit 8553ad3
Show file tree
Hide file tree
Showing 5 changed files with 395 additions and 14 deletions.
2 changes: 2 additions & 0 deletions src/services/ui/src/components/RHF/utils/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export * from "./initializer";
export * from "./validator";
Original file line number Diff line number Diff line change
Expand Up @@ -2,27 +2,33 @@ import * as T from "@/components/RHF/types";

type GL = Record<string, any>;

export const formGroupReducer = (ACC: GL, FORM: T.FormGroup) => {
FORM.slots.reduce(slotReducer, ACC);
export const formGroupInitializer = (ACC: GL, FORM: T.FormGroup) => {
FORM.slots.reduce(slotInitializer, ACC);
return ACC;
};

export const slotReducer = (ACC: GL, SLOT: T.RHFSlotProps): GL => {
export const slotInitializer = (ACC: GL, SLOT: T.RHFSlotProps): GL => {
const optionReducer = (OPT: T.RHFOption) => {
if (OPT.form) OPT.form.reduce(formGroupReducer, ACC);
if (OPT.slots) OPT.slots.reduce(slotReducer, ACC);
if (OPT.form) OPT.form.reduce(formGroupInitializer, ACC);
if (OPT.slots) OPT.slots.reduce(slotInitializer, ACC);
return ACC;
};

const fieldReducer = (ACC1: GL, SLOT: T.RHFSlotProps): GL => {
const fieldInitializer = (ACC1: GL, SLOT: T.RHFSlotProps): GL => {
if (SLOT.rhf === "FieldArray") {
return { ...ACC1, [SLOT.name]: [SLOT.fields?.reduce(fieldReducer, {})] };
return {
...ACC1,
[SLOT.name]: [SLOT.fields?.reduce(fieldInitializer, {})],
};
}
if (SLOT.rhf === "FieldGroup") {
return { ...ACC1, [SLOT.name]: [SLOT.fields?.reduce(fieldReducer, {})] };
return {
...ACC1,
[SLOT.name]: [SLOT.fields?.reduce(fieldInitializer, {})],
};
}

return { ...ACC1, ...slotReducer(ACC1, SLOT) };
return { ...ACC1, ...slotInitializer(ACC1, SLOT) };
};

if (SLOT.rhf === "Input") ACC[SLOT.name] = "";
Expand Down Expand Up @@ -52,16 +58,16 @@ export const slotReducer = (ACC: GL, SLOT: T.RHFSlotProps): GL => {
}

if (SLOT.rhf === "FieldArray")
ACC[SLOT.name] = [SLOT.fields?.reduce(fieldReducer, {})];
ACC[SLOT.name] = [SLOT.fields?.reduce(fieldInitializer, {})];
if (SLOT.rhf === "FieldGroup")
ACC[SLOT.name] = [SLOT.fields?.reduce(fieldReducer, {})];
ACC[SLOT.name] = [SLOT.fields?.reduce(fieldInitializer, {})];

return ACC;
};

export const documentInitializer = (document: T.Document) => {
return document.sections.reduce((ACC, SEC) => {
SEC.form.reduce(formGroupReducer, ACC);
SEC.form.reduce(formGroupInitializer, ACC);
return ACC;
}, {} as any);
};
55 changes: 55 additions & 0 deletions src/services/ui/src/components/RHF/utils/is.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { ValidationRule } from "react-hook-form";

export const INPUT_VALIDATION_RULES = {
max: "max",
min: "min",
maxLength: "maxLength",
minLength: "minLength",
pattern: "pattern",
required: "required",
validate: "validate",
} as const;
export type InputValidationRules = typeof INPUT_VALIDATION_RULES;
export type ERROR = Record<string, string>;
export type MaxType =
| InputValidationRules["max"]
| InputValidationRules["maxLength"];

export type MinType =
| InputValidationRules["min"]
| InputValidationRules["minLength"];

// eslint-disable-next-line @typescript-eslint/ban-types
export const isFunction = (value: unknown): value is Function =>
typeof value === "function";

export const isNullOrUndefined = (value: unknown): value is null | undefined =>
value == null;

export const isUndefined = (val: unknown): val is undefined =>
val === undefined;

export const isDateObject = (value: unknown): value is Date =>
value instanceof Date;

export const isObjectType = (value: unknown) => typeof value === "object";

export const isString = (value: unknown): value is string =>
typeof value === "string";

export const isObject = <T extends object>(value: unknown): value is T =>
!isNullOrUndefined(value) &&
!Array.isArray(value) &&
isObjectType(value) &&
!isDateObject(value);

export const isRegex = (value: unknown): value is RegExp =>
value instanceof RegExp;

export const getValueAndMessage = (validationData?: ValidationRule) =>
isObject(validationData) && !isRegex(validationData)
? validationData
: {
value: validationData,
message: "",
};
255 changes: 255 additions & 0 deletions src/services/ui/src/components/RHF/utils/validator.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,255 @@
import * as T from "@/components/RHF/types";
import { RegisterOptions } from "react-hook-form";

import {
isNullOrUndefined,
isUndefined,
isRegex,
getValueAndMessage,
isString,
ERROR,
// INPUT_VALIDATION_RULES,
// isFunction,
// MaxType,
// MinType,
} from "./is";

export const validateInput = (inputValue: any, rules?: RegisterOptions) => {
const isEmpty =
isUndefined(inputValue) ||
inputValue === "" ||
(Array.isArray(inputValue) && !inputValue.length);

if (isEmpty && rules?.required) {
return isString(rules.required) ? rules.required : "*Required";
}

if (
!isEmpty &&
(!isNullOrUndefined(rules?.min) || !isNullOrUndefined(rules?.max))
) {
let exceedMax;
let exceedMin;
const maxOutput = getValueAndMessage(rules?.max);
const minOutput = getValueAndMessage(rules?.min);

if (!isNullOrUndefined(inputValue) && !isNaN(inputValue as number)) {
const valueNumber = inputValue ? +inputValue : inputValue;
if (!isNullOrUndefined(maxOutput.value)) {
exceedMax = valueNumber > maxOutput.value;
}
if (!isNullOrUndefined(minOutput.value)) {
exceedMin = valueNumber < minOutput.value;
}
} else {
const valueDate = new Date(inputValue as string);
// const convertTimeToDate = (time: unknown) =>
// new Date(new Date().toDateString() + " " + time);
// // const isTime = ref.type == "time";
// // const isWeek = ref.type == "week";

if (isString(maxOutput.value) && inputValue) {
exceedMax = valueDate > new Date(maxOutput.value);
}

if (isString(minOutput.value) && inputValue) {
exceedMin = valueDate < new Date(minOutput.value);
}
}

if (exceedMax) return maxOutput.message;
if (exceedMin) return minOutput.message;
}

if (
(rules?.maxLength || rules?.minLength) &&
!isEmpty &&
isString(inputValue)
) {
const maxLengthOutput = getValueAndMessage(rules?.maxLength);
const minLengthOutput = getValueAndMessage(rules?.minLength);
const exceedMax =
!isNullOrUndefined(maxLengthOutput.value) &&
inputValue.length > +maxLengthOutput.value;
const exceedMin =
!isNullOrUndefined(minLengthOutput.value) &&
inputValue.length < +minLengthOutput.value;

if (exceedMax) return maxLengthOutput.message;
if (exceedMin) return minLengthOutput.message;
}

if (rules?.pattern && !isEmpty && isString(inputValue)) {
const { value: patternValue, message } = getValueAndMessage(rules?.pattern);

if (isRegex(patternValue) && !inputValue.match(patternValue)) {
return message;
}
}
// TODO: Add validate condition
// if (rules?.validate) {
// if (isFunction(rules?.validate)) {
// const result = await rules?.validate(inputValue, formValues);
// const validateError = getValidateError(result, inputRef);

// if (validateError) {
// error[name] = {
// ...validateError,
// ...appendErrorsCurry(
// INPUT_VALIDATION_RULES.validate,
// validateError.message
// ),
// };
// if (!validateAllFieldCriteria) {
// setCustomValidity(validateError.message);
// return error;
// }
// }
// } else if (isObject(validate)) {
// let validationResult = {} as FieldError;

// for (const key in validate) {
// if (!isEmptyObject(validationResult) && !validateAllFieldCriteria) {
// break;
// }

// const validateError = getValidateError(
// await validate[key](inputValue, formValues),
// inputRef,
// key
// );

// if (validateError) {
// validationResult = {
// ...validateError,
// ...appendErrorsCurry(key, validateError.message),
// };

// setCustomValidity(validateError.message);

// if (validateAllFieldCriteria) {
// error[name] = validationResult;
// }
// }
// }

// if (!isEmptyObject(validationResult)) {
// error[name] = {
// ref: inputRef,
// ...validationResult,
// };
// if (!validateAllFieldCriteria) {
// return error;
// }
// }
// }
// }

// If all checks pass, the input value is valid
return "";
};

export const validateOption = (optionValue: string, options: any[]) => {
return options.find((OPT: any) => OPT.value === optionValue);
};

export const formGroupValidator =
(data: any) => (ACC: ERROR, FORM: T.FormGroup) => {
FORM.slots.reduce(slotValidator(data), ACC);
return ACC;
};

export const slotValidator =
(data: any) =>
(ACC: ERROR, SLOT: T.RHFSlotProps): ERROR => {
const optionValidator = (OPT: T.RHFOption) => {
if (OPT.form) OPT.form.reduce(formGroupValidator(data), ACC);
if (OPT.slots) {
OPT.slots.reduce(slotValidator(data), ACC);
}
return ACC;
};

const fieldValidator = (FLD: any) => (SLOT1: T.RHFSlotProps) => {
if (SLOT1.rhf === "FieldArray") {
FLD[SLOT1.name].forEach((DAT: any) => {
SLOT1.fields?.forEach(fieldValidator(DAT));
});
} else if (SLOT1.rhf === "FieldGroup") {
FLD[SLOT1.name].forEach((DAT: any) => {
SLOT1.fields?.forEach(fieldValidator(DAT));
});
} else {
slotValidator(FLD)(ACC, SLOT1);
}
};

if (SLOT.rhf === "Input") {
ACC[SLOT.name] = validateInput(data[SLOT.name], SLOT.rules);
}
if (SLOT.rhf === "Textarea") {
ACC[SLOT.name] = validateInput(data[SLOT.name], SLOT.rules);
}

if (SLOT.rhf === "Switch") {
ACC[SLOT.name] = validateInput(data[SLOT.name], SLOT.rules);
}

if (SLOT.rhf === "Radio") {
const validOption = SLOT.props?.options.find(
(OPT) => OPT.value === data[SLOT.name]
);
if (!validOption) {
ACC[SLOT.name] = `invalid option - '${data[SLOT.name]}'`;
} else {
optionValidator(validOption);
}
}

if (SLOT.rhf === "Select") {
const validOption = SLOT.props?.options.find(
(OPT) => OPT.value === data[SLOT.name]
);
if (!validOption) {
ACC[SLOT.name] = `invalid option - '${data[SLOT.name]}'`;
} else {
optionValidator(validOption);
}
}

if (SLOT.rhf === "Checkbox") {
if (data[SLOT.name]?.length) {
const validList = data[SLOT.name].every((VAL: any) =>
SLOT.props?.options.some((OPT) => OPT.value === VAL)
);
if (!validList) {
ACC[SLOT.name] = `invalid option - '${data[SLOT.name]}'`;
}

const selectedOptions = SLOT.props?.options.filter((OPT) =>
data[SLOT.name].includes(OPT.value)
);
selectedOptions?.forEach(optionValidator);
}
}

if (SLOT.rhf === "FieldArray") {
data[SLOT.name].forEach((DAT: any) => {
SLOT.fields?.forEach(fieldValidator(DAT));
});
}
if (SLOT.rhf === "FieldGroup") {
data[SLOT.name].forEach((DAT: any) => {
SLOT.fields?.forEach(fieldValidator(DAT));
});
}

return ACC;
};

export const documentValidator = (document: T.Document) => (data: any) => {
return document.sections.reduce((ACC, SEC) => {
SEC.form.reduce(formGroupValidator(data), ACC);
return ACC;
}, {} as ERROR);
};
Loading

0 comments on commit 8553ad3

Please sign in to comment.