Refactor useFormValidation
This commit is contained in:
parent
b42bf24935
commit
82ffa801d6
@ -1,5 +1,5 @@
|
|||||||
import "keycloakify/tools/Array.prototype.every";
|
import "keycloakify/tools/Array.prototype.every";
|
||||||
import { useMemo, useReducer, Fragment } from "react";
|
import { useMemo, useReducer, Fragment, type Dispatch } from "react";
|
||||||
import { id } from "tsafe/id";
|
import { id } from "tsafe/id";
|
||||||
import type { MessageKey } from "keycloakify/login/i18n/i18n";
|
import type { MessageKey } from "keycloakify/login/i18n/i18n";
|
||||||
import type { Attribute, Validators } from "keycloakify/login/kcContext/KcContext";
|
import type { Attribute, Validators } from "keycloakify/login/kcContext/KcContext";
|
||||||
@ -7,12 +7,46 @@ import { useConstCallback } from "keycloakify/tools/useConstCallback";
|
|||||||
import { emailRegexp } from "keycloakify/tools/emailRegExp";
|
import { emailRegexp } from "keycloakify/tools/emailRegExp";
|
||||||
import type { KcContext } from "../kcContext";
|
import type { KcContext } from "../kcContext";
|
||||||
import type { I18n } from "../i18n";
|
import type { I18n } from "../i18n";
|
||||||
|
import type { Param0 } from "tsafe";
|
||||||
|
import { assert, type Equals } from "tsafe/assert";
|
||||||
|
|
||||||
/**
|
export type FormFieldError = {
|
||||||
* NOTE: The attributesWithPassword returned is actually augmented with
|
errorMessage: JSX.Element;
|
||||||
* artificial password related attributes only if kcContext.passwordRequired === true
|
errorMessageStr: string;
|
||||||
*/
|
validatorName: keyof Validators | undefined;
|
||||||
export function useFormValidation(params: {
|
};
|
||||||
|
|
||||||
|
export type FormFieldState = {
|
||||||
|
name: string;
|
||||||
|
/** The index is always 0 for non multi-valued fields */
|
||||||
|
index: number;
|
||||||
|
value: string;
|
||||||
|
displayableError: FormFieldError[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FormState = {
|
||||||
|
isFormSubmittable: boolean;
|
||||||
|
formFieldStates: FormFieldState[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type FormAction =
|
||||||
|
| {
|
||||||
|
action: "update value";
|
||||||
|
name: string;
|
||||||
|
index: number;
|
||||||
|
newValue: string;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
action: "focus lost";
|
||||||
|
name: string;
|
||||||
|
index: number;
|
||||||
|
}
|
||||||
|
| {
|
||||||
|
action: "add value to multi-valued attribute";
|
||||||
|
name: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type ParamsOfUseFromValidation = {
|
||||||
kcContext: {
|
kcContext: {
|
||||||
messagesPerField: Pick<KcContext.Common["messagesPerField"], "existsError" | "get">;
|
messagesPerField: Pick<KcContext.Common["messagesPerField"], "existsError" | "get">;
|
||||||
profile: {
|
profile: {
|
||||||
@ -22,159 +56,194 @@ export function useFormValidation(params: {
|
|||||||
realm: { registrationEmailAsUsername: boolean };
|
realm: { registrationEmailAsUsername: boolean };
|
||||||
};
|
};
|
||||||
passwordValidators?: Validators;
|
passwordValidators?: Validators;
|
||||||
//TODO: Add a param that enable not to use password confirmation
|
requirePasswordConfirmation?: boolean;
|
||||||
i18n: I18n;
|
i18n: I18n;
|
||||||
}) {
|
};
|
||||||
const { kcContext, passwordValidators = {}, i18n } = params;
|
|
||||||
|
|
||||||
const attributesWithPassword = useMemo(
|
export type ReturnTypeOfUseFormValidation = {
|
||||||
() =>
|
formState: FormState;
|
||||||
!kcContext.passwordRequired
|
dispatchFormAction: Dispatch<FormAction>;
|
||||||
? kcContext.profile.attributes
|
attributesWithPassword: Attribute[];
|
||||||
: (() => {
|
};
|
||||||
const name = kcContext.realm.registrationEmailAsUsername ? "email" : "username";
|
|
||||||
|
|
||||||
return kcContext.profile.attributes.reduce<Attribute[]>(
|
/**
|
||||||
(prev, curr) => [
|
* NOTE: The attributesWithPassword returned is actually augmented with
|
||||||
...prev,
|
* artificial password related attributes only if kcContext.passwordRequired === true
|
||||||
...(curr.name !== name
|
*/
|
||||||
? [curr]
|
export function useFormValidation(params: ParamsOfUseFromValidation): ReturnTypeOfUseFormValidation {
|
||||||
: [
|
const { kcContext, passwordValidators = {}, requirePasswordConfirmation = true, i18n } = params;
|
||||||
curr,
|
|
||||||
id<Attribute>({
|
const attributesWithPassword = useMemo(() => {
|
||||||
"name": "password",
|
const attributesWithPassword: Attribute[] = [];
|
||||||
"displayName": id<`\${${MessageKey}}`>("${password}"),
|
|
||||||
"required": true,
|
for (const attribute of kcContext.profile.attributes) {
|
||||||
"readOnly": false,
|
attributesWithPassword.push(attribute);
|
||||||
"validators": passwordValidators,
|
|
||||||
"annotations": {},
|
add_password_and_password_confirm: {
|
||||||
"autocomplete": "new-password",
|
if (attribute.name !== (kcContext.realm.registrationEmailAsUsername ? "email" : "username")) {
|
||||||
"html5DataAnnotations": {},
|
// NOTE: We want to add password and password-confirm after the field that identifies the user.
|
||||||
// NOTE: Compat with Keycloak version prior to 24
|
// It's either email or username.
|
||||||
...({ "groupAnnotations": {} } as {})
|
break add_password_and_password_confirm;
|
||||||
}),
|
}
|
||||||
id<Attribute>({
|
|
||||||
"name": "password-confirm",
|
attributesWithPassword.push(
|
||||||
"displayName": id<`\${${MessageKey}}`>("${passwordConfirm}"),
|
{
|
||||||
"required": true,
|
"name": "password",
|
||||||
"readOnly": false,
|
"displayName": id<`\${${MessageKey}}`>("${password}"),
|
||||||
"validators": {
|
"required": true,
|
||||||
"_compareToOther": {
|
"readOnly": false,
|
||||||
"name": "password",
|
"validators": passwordValidators,
|
||||||
"ignore.empty.value": true,
|
"annotations": {},
|
||||||
"shouldBe": "equal",
|
"autocomplete": "new-password",
|
||||||
"error-message": id<`\${${MessageKey}}`>("${invalidPasswordConfirmMessage}")
|
"html5DataAnnotations": {},
|
||||||
}
|
// NOTE: Compat with Keycloak version prior to 24
|
||||||
},
|
...({ "groupAnnotations": {} } as {})
|
||||||
"annotations": {},
|
},
|
||||||
"html5DataAnnotations": {},
|
{
|
||||||
"autocomplete": "new-password",
|
"name": "password-confirm",
|
||||||
// NOTE: Compat with Keycloak version prior to 24
|
"displayName": id<`\${${MessageKey}}`>("${passwordConfirm}"),
|
||||||
...({ "groupAnnotations": {} } as {})
|
"required": true,
|
||||||
})
|
"readOnly": false,
|
||||||
])
|
"validators": {
|
||||||
],
|
"_compareToOther": {
|
||||||
[]
|
"name": "password",
|
||||||
);
|
"ignore.empty.value": true,
|
||||||
})(),
|
"shouldBe": "equal",
|
||||||
[kcContext, JSON.stringify(passwordValidators)]
|
"error-message": id<`\${${MessageKey}}`>("${invalidPasswordConfirmMessage}")
|
||||||
);
|
}
|
||||||
|
},
|
||||||
|
"annotations": {},
|
||||||
|
"html5DataAnnotations": {},
|
||||||
|
"autocomplete": "new-password",
|
||||||
|
"hidden": !requirePasswordConfirmation,
|
||||||
|
// NOTE: Compat with Keycloak version prior to 24
|
||||||
|
...({ "groupAnnotations": {} } as {})
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return attributesWithPassword;
|
||||||
|
}, []);
|
||||||
|
|
||||||
const { getErrors } = useGetErrors({
|
const { getErrors } = useGetErrors({
|
||||||
"kcContext": {
|
kcContext,
|
||||||
"messagesPerField": kcContext.messagesPerField,
|
"attributes": attributesWithPassword,
|
||||||
"profile": {
|
|
||||||
"attributes": attributesWithPassword
|
|
||||||
}
|
|
||||||
},
|
|
||||||
i18n
|
i18n
|
||||||
});
|
});
|
||||||
|
|
||||||
const initialInternalState = useMemo(
|
type FormFieldState_internal = Omit<FormFieldState, "displayableError"> & {
|
||||||
() =>
|
errors: FormFieldError[];
|
||||||
Object.fromEntries(
|
hasLostFocusAtLeastOnce: boolean;
|
||||||
attributesWithPassword
|
};
|
||||||
.map(attribute => ({
|
|
||||||
attribute,
|
|
||||||
"errors": getErrors({
|
|
||||||
"name": attribute.name,
|
|
||||||
"fieldValueByAttributeName": Object.fromEntries(
|
|
||||||
attributesWithPassword.map(({ name, value }) => [name, { "value": value ?? "" }])
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}))
|
|
||||||
.map(({ attribute, errors }) => [
|
|
||||||
attribute.name,
|
|
||||||
{
|
|
||||||
"value": attribute.value ?? "",
|
|
||||||
errors,
|
|
||||||
"doDisplayPotentialErrorMessages": errors.length !== 0
|
|
||||||
}
|
|
||||||
])
|
|
||||||
),
|
|
||||||
[attributesWithPassword]
|
|
||||||
);
|
|
||||||
|
|
||||||
type InternalState = typeof initialInternalState;
|
type State = FormFieldState_internal[];
|
||||||
|
|
||||||
const [formValidationInternalState, formValidationDispatch] = useReducer(
|
const [state, dispatchFormAction] = useReducer(
|
||||||
(
|
(state: State, params: FormAction): State => {
|
||||||
state: InternalState,
|
if (params.action === "add value to multi-valued attribute") {
|
||||||
params:
|
const formFieldStates = state.filter(({ name }) => name === params.name);
|
||||||
| {
|
|
||||||
action: "update value";
|
state.splice(state.indexOf(formFieldStates[formFieldStates.length - 1]) + 1, 0, {
|
||||||
name: string;
|
"index": formFieldStates.length,
|
||||||
newValue: string;
|
"name": params.name,
|
||||||
}
|
"value": "",
|
||||||
| {
|
"errors": getErrors({
|
||||||
action: "focus lost";
|
"name": params.name,
|
||||||
name: string;
|
"index": formFieldStates.length,
|
||||||
}
|
"fieldValues": state
|
||||||
): InternalState => ({
|
}),
|
||||||
...state,
|
"hasLostFocusAtLeastOnce": false
|
||||||
[params.name]: {
|
});
|
||||||
...state[params.name],
|
|
||||||
...(() => {
|
return state;
|
||||||
switch (params.action) {
|
|
||||||
case "focus lost":
|
|
||||||
return { "doDisplayPotentialErrorMessages": true };
|
|
||||||
case "update value":
|
|
||||||
return {
|
|
||||||
"value": params.newValue,
|
|
||||||
"errors": getErrors({
|
|
||||||
"name": params.name,
|
|
||||||
"fieldValueByAttributeName": {
|
|
||||||
...state,
|
|
||||||
[params.name]: { "value": params.newValue }
|
|
||||||
}
|
|
||||||
})
|
|
||||||
};
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
}
|
}
|
||||||
}),
|
|
||||||
initialInternalState
|
const formFieldState = state.find(({ name, index }) => name === params.name && index === params.index);
|
||||||
|
|
||||||
|
assert(formFieldState !== undefined);
|
||||||
|
|
||||||
|
switch (params.action) {
|
||||||
|
case "focus lost":
|
||||||
|
formFieldState.hasLostFocusAtLeastOnce = true;
|
||||||
|
return state;
|
||||||
|
case "update value":
|
||||||
|
formFieldState.value = params.newValue;
|
||||||
|
formFieldState.errors = getErrors({
|
||||||
|
"name": params.name,
|
||||||
|
"index": params.index,
|
||||||
|
"fieldValues": state
|
||||||
|
});
|
||||||
|
return state;
|
||||||
|
}
|
||||||
|
|
||||||
|
assert<Equals<typeof params, never>>(false);
|
||||||
|
},
|
||||||
|
useMemo(function getInitialState(): State {
|
||||||
|
const initialFormFieldValues = (() => {
|
||||||
|
const initialFormFieldValues: Param0<typeof getErrors>["fieldValues"] = [];
|
||||||
|
|
||||||
|
for (const attribute of attributesWithPassword) {
|
||||||
|
handle_multi_valued_attribute: {
|
||||||
|
if (!attribute.multivalued) {
|
||||||
|
break handle_multi_valued_attribute;
|
||||||
|
}
|
||||||
|
|
||||||
|
const values = attribute.values ?? [attribute.value ?? ""];
|
||||||
|
|
||||||
|
for (let index = 0; index < values.length; index++) {
|
||||||
|
initialFormFieldValues.push({
|
||||||
|
"name": attribute.name,
|
||||||
|
index,
|
||||||
|
"value": values[index]
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
initialFormFieldValues.push({
|
||||||
|
"name": attribute.name,
|
||||||
|
"index": 0,
|
||||||
|
"value": attribute.value ?? ""
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return initialFormFieldValues;
|
||||||
|
})();
|
||||||
|
|
||||||
|
const initialState: State = initialFormFieldValues.map(({ name, index, value }) => ({
|
||||||
|
name,
|
||||||
|
index,
|
||||||
|
value,
|
||||||
|
"errors": getErrors({
|
||||||
|
"name": name,
|
||||||
|
index,
|
||||||
|
"fieldValues": initialFormFieldValues
|
||||||
|
}),
|
||||||
|
"hasLostFocusAtLeastOnce": false
|
||||||
|
}));
|
||||||
|
|
||||||
|
return initialState;
|
||||||
|
}, [])
|
||||||
);
|
);
|
||||||
|
|
||||||
const formValidationState = useMemo(
|
const formState: FormState = useMemo(
|
||||||
() => ({
|
() => ({
|
||||||
"fieldStateByAttributeName": Object.fromEntries(
|
"formFieldStates": state.map(({ name, index, value, errors, hasLostFocusAtLeastOnce }) => ({
|
||||||
Object.entries(formValidationInternalState).map(([name, { value, errors, doDisplayPotentialErrorMessages }]) => [
|
name,
|
||||||
name,
|
index,
|
||||||
{ value, "displayableErrors": doDisplayPotentialErrorMessages ? errors : [] }
|
value,
|
||||||
])
|
"displayableError": hasLostFocusAtLeastOnce ? errors : []
|
||||||
),
|
})),
|
||||||
"isFormSubmittable": Object.entries(formValidationInternalState).every(
|
"isFormSubmittable": state.every(({ errors }) => errors.length === 0)
|
||||||
([name, { value, errors }]) =>
|
|
||||||
errors.length === 0 && (value !== "" || !attributesWithPassword.find(attribute => attribute.name === name)!.required)
|
|
||||||
)
|
|
||||||
}),
|
}),
|
||||||
[formValidationInternalState, attributesWithPassword]
|
[state]
|
||||||
);
|
);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
formValidationState,
|
formState,
|
||||||
formValidationDispatch,
|
dispatchFormAction,
|
||||||
attributesWithPassword
|
attributesWithPassword
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -183,296 +252,327 @@ export function useFormValidation(params: {
|
|||||||
function useGetErrors(params: {
|
function useGetErrors(params: {
|
||||||
kcContext: {
|
kcContext: {
|
||||||
messagesPerField: Pick<KcContext.Common["messagesPerField"], "existsError" | "get">;
|
messagesPerField: Pick<KcContext.Common["messagesPerField"], "existsError" | "get">;
|
||||||
profile: {
|
|
||||||
attributes: { name: string; value?: string; validators: Validators }[];
|
|
||||||
};
|
|
||||||
};
|
};
|
||||||
|
attributes: {
|
||||||
|
name: string;
|
||||||
|
validators: Validators;
|
||||||
|
value?: string;
|
||||||
|
values?: string[];
|
||||||
|
required?: boolean;
|
||||||
|
}[];
|
||||||
i18n: I18n;
|
i18n: I18n;
|
||||||
}) {
|
}) {
|
||||||
const { kcContext, i18n } = params;
|
const { kcContext, attributes, i18n } = params;
|
||||||
|
|
||||||
const {
|
const { messagesPerField } = kcContext;
|
||||||
messagesPerField,
|
|
||||||
profile: { attributes }
|
|
||||||
} = kcContext;
|
|
||||||
|
|
||||||
const { msg, msgStr, advancedMsg, advancedMsgStr } = i18n;
|
const { msg, msgStr, advancedMsg, advancedMsgStr } = i18n;
|
||||||
|
|
||||||
const getErrors = useConstCallback((params: { name: string; fieldValueByAttributeName: Record<string, { value: string }> }) => {
|
const getErrors = useConstCallback(
|
||||||
const { name, fieldValueByAttributeName } = params;
|
(params: { name: string; index: number; fieldValues: { name: string; index: number; value: string }[] }): FormFieldError[] => {
|
||||||
|
const { name, index, fieldValues } = params;
|
||||||
|
|
||||||
const { value } = fieldValueByAttributeName[name];
|
const value = (() => {
|
||||||
|
const fieldValue = fieldValues.find(fieldValue => fieldValue.name === name && fieldValue.index === index);
|
||||||
|
|
||||||
const { value: defaultValue, validators } = attributes.find(attribute => attribute.name === name)!;
|
assert(fieldValue !== undefined);
|
||||||
|
|
||||||
block: {
|
return fieldValue.value;
|
||||||
if ((defaultValue ?? "") !== value) {
|
|
||||||
break block;
|
|
||||||
}
|
|
||||||
|
|
||||||
let doesErrorExist: boolean;
|
|
||||||
|
|
||||||
try {
|
|
||||||
doesErrorExist = messagesPerField.existsError(name);
|
|
||||||
} catch {
|
|
||||||
break block;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!doesErrorExist) {
|
|
||||||
break block;
|
|
||||||
}
|
|
||||||
|
|
||||||
const errorMessageStr = messagesPerField.get(name);
|
|
||||||
|
|
||||||
return [
|
|
||||||
{
|
|
||||||
"validatorName": undefined,
|
|
||||||
errorMessageStr,
|
|
||||||
"errorMessage": <span key={0}>{errorMessageStr}</span>
|
|
||||||
}
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
const errors: {
|
|
||||||
errorMessage: JSX.Element;
|
|
||||||
errorMessageStr: string;
|
|
||||||
validatorName: keyof Validators | undefined;
|
|
||||||
}[] = [];
|
|
||||||
|
|
||||||
scope: {
|
|
||||||
const validatorName = "length";
|
|
||||||
|
|
||||||
const validator = validators[validatorName];
|
|
||||||
|
|
||||||
if (validator === undefined) {
|
|
||||||
break scope;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { "ignore.empty.value": ignoreEmptyValue = false, max, min } = validator;
|
|
||||||
|
|
||||||
if (ignoreEmptyValue && value === "") {
|
|
||||||
break scope;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (max !== undefined && value.length > parseInt(max)) {
|
|
||||||
const msgArgs = ["error-invalid-length-too-long", max] as const;
|
|
||||||
|
|
||||||
errors.push({
|
|
||||||
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
|
|
||||||
"errorMessageStr": msgStr(...msgArgs),
|
|
||||||
validatorName
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (min !== undefined && value.length < parseInt(min)) {
|
|
||||||
const msgArgs = ["error-invalid-length-too-short", min] as const;
|
|
||||||
|
|
||||||
errors.push({
|
|
||||||
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
|
|
||||||
"errorMessageStr": msgStr(...msgArgs),
|
|
||||||
validatorName
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
scope: {
|
|
||||||
const validatorName = "_compareToOther";
|
|
||||||
|
|
||||||
const validator = validators[validatorName];
|
|
||||||
|
|
||||||
if (validator === undefined) {
|
|
||||||
break scope;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { "ignore.empty.value": ignoreEmptyValue = false, name: otherName, shouldBe, "error-message": errorMessageKey } = validator;
|
|
||||||
|
|
||||||
if (ignoreEmptyValue && value === "") {
|
|
||||||
break scope;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { value: otherValue } = fieldValueByAttributeName[otherName];
|
|
||||||
|
|
||||||
const isValid = (() => {
|
|
||||||
switch (shouldBe) {
|
|
||||||
case "different":
|
|
||||||
return otherValue !== value;
|
|
||||||
case "equal":
|
|
||||||
return otherValue === value;
|
|
||||||
}
|
|
||||||
})();
|
})();
|
||||||
|
|
||||||
if (isValid) {
|
const attribute = attributes.find(attribute => attribute.name === name);
|
||||||
break scope;
|
|
||||||
|
assert(attribute !== undefined);
|
||||||
|
|
||||||
|
server_side_error: {
|
||||||
|
const defaultValue = (attribute.values !== undefined ? attribute.values[index] : attribute.value) ?? "";
|
||||||
|
|
||||||
|
if (defaultValue !== value) {
|
||||||
|
break server_side_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
let doesErrorExist: boolean;
|
||||||
|
|
||||||
|
try {
|
||||||
|
doesErrorExist = messagesPerField.existsError(name);
|
||||||
|
} catch {
|
||||||
|
break server_side_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!doesErrorExist) {
|
||||||
|
break server_side_error;
|
||||||
|
}
|
||||||
|
|
||||||
|
const errorMessageStr = messagesPerField.get(name);
|
||||||
|
|
||||||
|
return [
|
||||||
|
{
|
||||||
|
"validatorName": undefined,
|
||||||
|
errorMessageStr,
|
||||||
|
"errorMessage": <span key={0}>{errorMessageStr}</span>
|
||||||
|
}
|
||||||
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
const msgArg = [
|
const errors: FormFieldError[] = [];
|
||||||
errorMessageKey ??
|
|
||||||
id<MessageKey>(
|
|
||||||
(() => {
|
|
||||||
switch (shouldBe) {
|
|
||||||
case "equal":
|
|
||||||
return "shouldBeEqual";
|
|
||||||
case "different":
|
|
||||||
return "shouldBeDifferent";
|
|
||||||
}
|
|
||||||
})()
|
|
||||||
),
|
|
||||||
otherName,
|
|
||||||
name,
|
|
||||||
shouldBe
|
|
||||||
] as const;
|
|
||||||
|
|
||||||
errors.push({
|
const { validators } = attribute;
|
||||||
validatorName,
|
|
||||||
"errorMessage": <Fragment key={errors.length}>{advancedMsg(...msgArg)}</Fragment>,
|
|
||||||
"errorMessageStr": advancedMsgStr(...msgArg)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
scope: {
|
required_field: {
|
||||||
const validatorName = "pattern";
|
if (!attribute.required) {
|
||||||
|
break required_field;
|
||||||
|
}
|
||||||
|
|
||||||
const validator = validators[validatorName];
|
if (value !== "") {
|
||||||
|
break required_field;
|
||||||
|
}
|
||||||
|
|
||||||
if (validator === undefined) {
|
const msgArgs = ["error-user-attribute-required"] as const;
|
||||||
break scope;
|
|
||||||
|
errors.push({
|
||||||
|
"validatorName": undefined,
|
||||||
|
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
|
||||||
|
"errorMessageStr": msgStr(...msgArgs)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const { "ignore.empty.value": ignoreEmptyValue = false, pattern, "error-message": errorMessageKey } = validator;
|
validator_x: {
|
||||||
|
const validatorName = "length";
|
||||||
|
|
||||||
if (ignoreEmptyValue && value === "") {
|
const validator = validators[validatorName];
|
||||||
break scope;
|
|
||||||
|
if (validator === undefined) {
|
||||||
|
break validator_x;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { "ignore.empty.value": ignoreEmptyValue = false, max, min } = validator;
|
||||||
|
|
||||||
|
if (ignoreEmptyValue && value === "") {
|
||||||
|
break validator_x;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (max !== undefined && value.length > parseInt(max)) {
|
||||||
|
const msgArgs = ["error-invalid-length-too-long", max] as const;
|
||||||
|
|
||||||
|
errors.push({
|
||||||
|
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
|
||||||
|
"errorMessageStr": msgStr(...msgArgs),
|
||||||
|
validatorName
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (min !== undefined && value.length < parseInt(min)) {
|
||||||
|
const msgArgs = ["error-invalid-length-too-short", min] as const;
|
||||||
|
|
||||||
|
errors.push({
|
||||||
|
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
|
||||||
|
"errorMessageStr": msgStr(...msgArgs),
|
||||||
|
validatorName
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (new RegExp(pattern).test(value)) {
|
validator_x: {
|
||||||
break scope;
|
const validatorName = "_compareToOther";
|
||||||
|
|
||||||
|
const validator = validators[validatorName];
|
||||||
|
|
||||||
|
if (validator === undefined) {
|
||||||
|
break validator_x;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { "ignore.empty.value": ignoreEmptyValue = false, name: otherName, shouldBe, "error-message": errorMessageKey } = validator;
|
||||||
|
|
||||||
|
if (ignoreEmptyValue && value === "") {
|
||||||
|
break validator_x;
|
||||||
|
}
|
||||||
|
|
||||||
|
const otherFieldValue = fieldValues.find(fieldValue => fieldValue.name === otherName);
|
||||||
|
|
||||||
|
assert(otherFieldValue !== undefined);
|
||||||
|
|
||||||
|
const isValid = (() => {
|
||||||
|
switch (shouldBe) {
|
||||||
|
case "different":
|
||||||
|
return otherFieldValue.value !== value;
|
||||||
|
case "equal":
|
||||||
|
return otherFieldValue.value === value;
|
||||||
|
}
|
||||||
|
})();
|
||||||
|
|
||||||
|
if (isValid) {
|
||||||
|
break validator_x;
|
||||||
|
}
|
||||||
|
|
||||||
|
const msgArg = [
|
||||||
|
errorMessageKey ??
|
||||||
|
id<MessageKey>(
|
||||||
|
(() => {
|
||||||
|
switch (shouldBe) {
|
||||||
|
case "equal":
|
||||||
|
return "shouldBeEqual";
|
||||||
|
case "different":
|
||||||
|
return "shouldBeDifferent";
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
),
|
||||||
|
otherName,
|
||||||
|
name,
|
||||||
|
shouldBe
|
||||||
|
] as const;
|
||||||
|
|
||||||
|
errors.push({
|
||||||
|
validatorName,
|
||||||
|
"errorMessage": <Fragment key={errors.length}>{advancedMsg(...msgArg)}</Fragment>,
|
||||||
|
"errorMessageStr": advancedMsgStr(...msgArg)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const msgArgs = [errorMessageKey ?? id<MessageKey>("shouldMatchPattern"), pattern] as const;
|
validator_x: {
|
||||||
|
const validatorName = "pattern";
|
||||||
|
|
||||||
errors.push({
|
const validator = validators[validatorName];
|
||||||
validatorName,
|
|
||||||
"errorMessage": <Fragment key={errors.length}>{advancedMsg(...msgArgs)}</Fragment>,
|
|
||||||
"errorMessageStr": advancedMsgStr(...msgArgs)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
scope: {
|
if (validator === undefined) {
|
||||||
if ([...errors].reverse()[0]?.validatorName === "pattern") {
|
break validator_x;
|
||||||
break scope;
|
}
|
||||||
|
|
||||||
|
const { "ignore.empty.value": ignoreEmptyValue = false, pattern, "error-message": errorMessageKey } = validator;
|
||||||
|
|
||||||
|
if (ignoreEmptyValue && value === "") {
|
||||||
|
break validator_x;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (new RegExp(pattern).test(value)) {
|
||||||
|
break validator_x;
|
||||||
|
}
|
||||||
|
|
||||||
|
const msgArgs = [errorMessageKey ?? id<MessageKey>("shouldMatchPattern"), pattern] as const;
|
||||||
|
|
||||||
|
errors.push({
|
||||||
|
validatorName,
|
||||||
|
"errorMessage": <Fragment key={errors.length}>{advancedMsg(...msgArgs)}</Fragment>,
|
||||||
|
"errorMessageStr": advancedMsgStr(...msgArgs)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const validatorName = "email";
|
validator_x: {
|
||||||
|
if ([...errors].reverse()[0]?.validatorName === "pattern") {
|
||||||
|
break validator_x;
|
||||||
|
}
|
||||||
|
|
||||||
const validator = validators[validatorName];
|
const validatorName = "email";
|
||||||
|
|
||||||
if (validator === undefined) {
|
const validator = validators[validatorName];
|
||||||
break scope;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { "ignore.empty.value": ignoreEmptyValue = false } = validator;
|
if (validator === undefined) {
|
||||||
|
break validator_x;
|
||||||
|
}
|
||||||
|
|
||||||
if (ignoreEmptyValue && value === "") {
|
const { "ignore.empty.value": ignoreEmptyValue = false } = validator;
|
||||||
break scope;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (emailRegexp.test(value)) {
|
if (ignoreEmptyValue && value === "") {
|
||||||
break scope;
|
break validator_x;
|
||||||
}
|
}
|
||||||
|
|
||||||
const msgArgs = [id<MessageKey>("invalidEmailMessage")] as const;
|
if (emailRegexp.test(value)) {
|
||||||
|
break validator_x;
|
||||||
|
}
|
||||||
|
|
||||||
errors.push({
|
const msgArgs = [id<MessageKey>("invalidEmailMessage")] as const;
|
||||||
validatorName,
|
|
||||||
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
|
|
||||||
"errorMessageStr": msgStr(...msgArgs)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
scope: {
|
|
||||||
const validatorName = "integer";
|
|
||||||
|
|
||||||
const validator = validators[validatorName];
|
|
||||||
|
|
||||||
if (validator === undefined) {
|
|
||||||
break scope;
|
|
||||||
}
|
|
||||||
|
|
||||||
const { "ignore.empty.value": ignoreEmptyValue = false, max, min } = validator;
|
|
||||||
|
|
||||||
if (ignoreEmptyValue && value === "") {
|
|
||||||
break scope;
|
|
||||||
}
|
|
||||||
|
|
||||||
const intValue = parseInt(value);
|
|
||||||
|
|
||||||
if (isNaN(intValue)) {
|
|
||||||
const msgArgs = ["mustBeAnInteger"] as const;
|
|
||||||
|
|
||||||
errors.push({
|
errors.push({
|
||||||
validatorName,
|
validatorName,
|
||||||
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
|
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
|
||||||
"errorMessageStr": msgStr(...msgArgs)
|
"errorMessageStr": msgStr(...msgArgs)
|
||||||
});
|
});
|
||||||
|
|
||||||
break scope;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (max !== undefined && intValue > parseInt(max)) {
|
validator_x: {
|
||||||
const msgArgs = ["error-number-out-of-range-too-big", max] as const;
|
const validatorName = "integer";
|
||||||
|
|
||||||
|
const validator = validators[validatorName];
|
||||||
|
|
||||||
|
if (validator === undefined) {
|
||||||
|
break validator_x;
|
||||||
|
}
|
||||||
|
|
||||||
|
const { "ignore.empty.value": ignoreEmptyValue = false, max, min } = validator;
|
||||||
|
|
||||||
|
if (ignoreEmptyValue && value === "") {
|
||||||
|
break validator_x;
|
||||||
|
}
|
||||||
|
|
||||||
|
const intValue = parseInt(value);
|
||||||
|
|
||||||
|
if (isNaN(intValue)) {
|
||||||
|
const msgArgs = ["mustBeAnInteger"] as const;
|
||||||
|
|
||||||
|
errors.push({
|
||||||
|
validatorName,
|
||||||
|
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
|
||||||
|
"errorMessageStr": msgStr(...msgArgs)
|
||||||
|
});
|
||||||
|
|
||||||
|
break validator_x;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (max !== undefined && intValue > parseInt(max)) {
|
||||||
|
const msgArgs = ["error-number-out-of-range-too-big", max] as const;
|
||||||
|
|
||||||
|
errors.push({
|
||||||
|
validatorName,
|
||||||
|
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
|
||||||
|
"errorMessageStr": msgStr(...msgArgs)
|
||||||
|
});
|
||||||
|
|
||||||
|
break validator_x;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (min !== undefined && intValue < parseInt(min)) {
|
||||||
|
const msgArgs = ["error-number-out-of-range-too-small", min] as const;
|
||||||
|
|
||||||
|
errors.push({
|
||||||
|
validatorName,
|
||||||
|
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
|
||||||
|
"errorMessageStr": msgStr(...msgArgs)
|
||||||
|
});
|
||||||
|
|
||||||
|
break validator_x;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
validator_x: {
|
||||||
|
const validatorName = "options";
|
||||||
|
|
||||||
|
const validator = validators[validatorName];
|
||||||
|
|
||||||
|
if (validator === undefined) {
|
||||||
|
break validator_x;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (value === "") {
|
||||||
|
break validator_x;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (validator.options.indexOf(value) >= 0) {
|
||||||
|
break validator_x;
|
||||||
|
}
|
||||||
|
|
||||||
|
const msgArgs = [id<MessageKey>("notAValidOption")] as const;
|
||||||
|
|
||||||
errors.push({
|
errors.push({
|
||||||
validatorName,
|
validatorName,
|
||||||
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
|
"errorMessage": <Fragment key={errors.length}>{advancedMsg(...msgArgs)}</Fragment>,
|
||||||
"errorMessageStr": msgStr(...msgArgs)
|
"errorMessageStr": advancedMsgStr(...msgArgs)
|
||||||
});
|
});
|
||||||
|
|
||||||
break scope;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (min !== undefined && intValue < parseInt(min)) {
|
//TODO: Implement missing validators.
|
||||||
const msgArgs = ["error-number-out-of-range-too-small", min] as const;
|
|
||||||
|
|
||||||
errors.push({
|
return errors;
|
||||||
validatorName,
|
|
||||||
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
|
|
||||||
"errorMessageStr": msgStr(...msgArgs)
|
|
||||||
});
|
|
||||||
|
|
||||||
break scope;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
);
|
||||||
scope: {
|
|
||||||
const validatorName = "options";
|
|
||||||
|
|
||||||
const validator = validators[validatorName];
|
|
||||||
|
|
||||||
if (validator === undefined) {
|
|
||||||
break scope;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (value === "") {
|
|
||||||
break scope;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (validator.options.indexOf(value) >= 0) {
|
|
||||||
break scope;
|
|
||||||
}
|
|
||||||
|
|
||||||
const msgArgs = [id<MessageKey>("notAValidOption")] as const;
|
|
||||||
|
|
||||||
errors.push({
|
|
||||||
validatorName,
|
|
||||||
"errorMessage": <Fragment key={errors.length}>{advancedMsg(...msgArgs)}</Fragment>,
|
|
||||||
"errorMessageStr": advancedMsgStr(...msgArgs)
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
//TODO: Implement missing validators.
|
|
||||||
|
|
||||||
return errors;
|
|
||||||
});
|
|
||||||
|
|
||||||
return { getErrors };
|
return { getErrors };
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user