keycloak_theme/src/login/lib/useUserProfileForm.tsx

774 lines
27 KiB
TypeScript
Raw Normal View History

2023-03-18 06:14:05 +01:00
import "keycloakify/tools/Array.prototype.every";
2024-04-21 08:12:25 +02:00
import { useMemo, useReducer, Fragment, type Dispatch } from "react";
2023-03-18 06:14:05 +01:00
import { id } from "tsafe/id";
2023-03-19 23:12:45 +01:00
import type { MessageKey } from "keycloakify/login/i18n/i18n";
import type { Attribute, Validators } from "keycloakify/login/kcContext/KcContext";
2023-03-18 06:14:05 +01:00
import { useConstCallback } from "keycloakify/tools/useConstCallback";
import { emailRegexp } from "keycloakify/tools/emailRegExp";
import type { KcContext, PasswordPolicies } from "keycloakify/login/kcContext/KcContext";
2024-04-21 08:12:25 +02:00
import type { Param0 } from "tsafe";
import { assert, type Equals } from "tsafe/assert";
import type { I18n } from "../i18n";
2024-04-21 08:12:25 +02:00
export type FormFieldError = {
errorMessage: JSX.Element;
errorMessageStr: string;
validatorName: keyof Validators | undefined;
};
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 KcContextLike = {
messagesPerField: Pick<KcContext.Common["messagesPerField"], "existsError" | "get">;
profile: {
attributes: Attribute[];
2023-03-18 06:14:05 +01:00
};
passwordRequired?: boolean;
realm: { registrationEmailAsUsername: boolean };
passwordPolicies?: PasswordPolicies;
};
export type ParamsOfUseUserProfileForm = {
kcContext: KcContextLike;
2023-03-18 06:14:05 +01:00
i18n: I18n;
passwordConfirmationDisabled?: boolean;
2024-04-21 08:12:25 +02:00
};
2023-03-18 06:14:05 +01:00
export type ReturnTypeOfUseUserProfileForm = {
2024-04-21 08:12:25 +02:00
formState: FormState;
dispatchFormAction: Dispatch<FormAction>;
attributesWithPassword: Attribute[];
};
/**
* NOTE: The attributesWithPassword returned is actually augmented with
* artificial password related attributes only if kcContext.passwordRequired === true
*/
export function useUserProfileForm(params: ParamsOfUseUserProfileForm): ReturnTypeOfUseUserProfileForm {
2024-04-22 06:34:50 +02:00
const { kcContext, i18n, passwordConfirmationDisabled = false } = params;
2024-04-21 08:12:25 +02:00
const attributesWithPassword = useMemo(() => {
const attributesWithPassword: Attribute[] = [];
for (const attribute of kcContext.profile.attributes) {
attributesWithPassword.push(attribute);
add_password_and_password_confirm: {
if (attribute.name !== (kcContext.realm.registrationEmailAsUsername ? "email" : "username")) {
// NOTE: We want to add password and password-confirm after the field that identifies the user.
// It's either email or username.
break add_password_and_password_confirm;
}
attributesWithPassword.push(
{
"name": "password",
"displayName": id<`\${${MessageKey}}`>("${password}"),
"required": true,
"readOnly": false,
2024-04-22 06:34:50 +02:00
"validators": {},
2024-04-21 08:12:25 +02:00
"annotations": {},
"autocomplete": "new-password",
"html5DataAnnotations": {},
// NOTE: Compat with Keycloak version prior to 24
...({ "groupAnnotations": {} } as {})
},
{
"name": "password-confirm",
"displayName": id<`\${${MessageKey}}`>("${passwordConfirm}"),
"required": true,
"readOnly": false,
"validators": {},
2024-04-21 08:12:25 +02:00
"annotations": {},
"html5DataAnnotations": {},
"autocomplete": "new-password",
// NOTE: Compat with Keycloak version prior to 24
...({ "groupAnnotations": {} } as {})
}
);
2023-03-18 06:14:05 +01:00
}
2024-04-21 08:12:25 +02:00
}
return attributesWithPassword;
}, []);
const { getErrors } = useGetErrors({
kcContext,
"attributes": attributesWithPassword,
2023-03-18 06:14:05 +01:00
i18n
});
2024-04-21 08:12:25 +02:00
type FormFieldState_internal = Omit<FormFieldState, "displayableError"> & {
errors: FormFieldError[];
hasLostFocusAtLeastOnce: boolean;
};
type State = FormFieldState_internal[];
const [state, dispatchFormAction] = useReducer(
function reducer(state: State, params: FormAction): State {
2024-04-21 08:12:25 +02:00
if (params.action === "add value to multi-valued attribute") {
const formFieldStates = state.filter(({ name }) => name === params.name);
state.splice(state.indexOf(formFieldStates[formFieldStates.length - 1]) + 1, 0, {
"index": formFieldStates.length,
"name": params.name,
"value": "",
"errors": getErrors({
"name": params.name,
"index": formFieldStates.length,
"fieldValues": state
}),
"hasLostFocusAtLeastOnce": false
});
return state;
}
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":
update_password_confirm: {
if (params.name !== "password") {
break update_password_confirm;
}
if (!passwordConfirmationDisabled) {
break update_password_confirm;
}
state = reducer(state, {
"action": "update value",
"name": "password-confirm",
"index": 0,
"newValue": params.newValue
});
}
2024-04-21 08:12:25 +02:00
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;
2023-03-18 06:14:05 +01:00
}
const values = attribute.values ?? [""];
apply_validator_min_range: {
const validator = attribute.validators.multivalued;
if (validator === undefined) {
break apply_validator_min_range;
}
const { min: minStr } = validator;
if (minStr === undefined) {
break apply_validator_min_range;
}
const min = parseInt(minStr);
for (let index = values.length; index < min; index++) {
values.push("");
}
}
2024-04-21 08:12:25 +02:00
for (let index = 0; index < values.length; index++) {
initialFormFieldValues.push({
"name": attribute.name,
index,
"value": values[index]
});
}
continue;
2023-03-18 06:14:05 +01:00
}
2024-04-21 08:12:25 +02:00
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;
}, [])
2023-03-18 06:14:05 +01:00
);
2024-04-21 08:12:25 +02:00
const formState: FormState = useMemo(
2023-03-18 06:14:05 +01:00
() => ({
2024-04-21 08:12:25 +02:00
"formFieldStates": state.map(({ name, index, value, errors, hasLostFocusAtLeastOnce }) => ({
name,
index,
value,
"displayableError": hasLostFocusAtLeastOnce ? errors : []
})),
"isFormSubmittable": state.every(({ errors }) => errors.length === 0)
2023-03-18 06:14:05 +01:00
}),
2024-04-21 08:12:25 +02:00
[state]
2023-03-18 06:14:05 +01:00
);
return {
2024-04-21 08:12:25 +02:00
formState,
dispatchFormAction,
2023-03-18 06:14:05 +01:00
attributesWithPassword
};
}
/** Expect to be used in a component wrapped within a <I18nProvider> */
function useGetErrors(params: {
2024-04-22 06:34:50 +02:00
kcContext: Pick<KcContextLike, "messagesPerField" | "passwordPolicies">;
2024-04-21 08:12:25 +02:00
attributes: {
name: string;
validators: Validators;
value?: string;
values?: string[];
required?: boolean;
}[];
2023-03-18 06:14:05 +01:00
i18n: I18n;
}) {
2024-04-21 08:12:25 +02:00
const { kcContext, attributes, i18n } = params;
2023-03-18 06:14:05 +01:00
2024-04-22 06:34:50 +02:00
const { messagesPerField, passwordPolicies } = kcContext;
2023-03-18 06:14:05 +01:00
const { msg, msgStr, advancedMsg, advancedMsgStr } = i18n;
2024-04-21 08:12:25 +02:00
const getErrors = useConstCallback(
(params: { name: string; index: number; fieldValues: { name: string; index: number; value: string }[] }): FormFieldError[] => {
const { name, index, fieldValues } = params;
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
const value = (() => {
const fieldValue = fieldValues.find(fieldValue => fieldValue.name === name && fieldValue.index === index);
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
assert(fieldValue !== undefined);
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
return fieldValue.value;
})();
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
const attribute = attributes.find(attribute => attribute.name === name);
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
assert(attribute !== undefined);
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
server_side_error: {
const defaultValue = (attribute.values !== undefined ? attribute.values[index] : attribute.value) ?? "";
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
if (defaultValue !== value) {
break server_side_error;
2023-03-18 06:14:05 +01:00
}
2024-04-21 08:12:25 +02:00
let doesErrorExist: boolean;
try {
doesErrorExist = messagesPerField.existsError(name);
} catch {
break server_side_error;
}
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
if (!doesErrorExist) {
break server_side_error;
}
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
const errorMessageStr = messagesPerField.get(name);
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
return [
{
"validatorName": undefined,
errorMessageStr,
"errorMessage": <span key={0}>{errorMessageStr}</span>
}
];
2023-03-18 06:14:05 +01:00
}
2024-04-21 08:12:25 +02:00
const errors: FormFieldError[] = [];
2023-03-18 06:14:05 +01:00
2024-04-22 06:34:50 +02:00
check_password_policies: {
if (name !== "password") {
break check_password_policies;
}
if (passwordPolicies === undefined) {
break check_password_policies;
}
check_password_policy_x: {
const policyName = "length";
const policy = passwordPolicies[policyName];
if (policy === undefined) {
break check_password_policy_x;
}
const minLength = parseInt(policy);
assert(!isNaN(minLength));
if (value.length >= minLength) {
break check_password_policy_x;
}
const msgArgs = ["invalidPasswordMinLengthMessage", `${minLength}`] as const;
errors.push({
"validatorName": undefined,
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs)
});
}
check_password_policy_x: {
const policyName = "digits";
const policy = passwordPolicies[policyName];
if (policy === undefined) {
break check_password_policy_x;
}
const minNumberOfDigits = parseInt(policy);
assert(!isNaN(minNumberOfDigits));
if (value.split("").filter(char => !isNaN(parseInt(char))).length >= minNumberOfDigits) {
break check_password_policy_x;
}
const msgArgs = ["invalidPasswordMinDigitsMessage", `${minNumberOfDigits}`] as const;
errors.push({
"validatorName": undefined,
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs)
});
}
check_password_policy_x: {
const policyName = "lowerCase";
const policy = passwordPolicies[policyName];
if (policy === undefined) {
break check_password_policy_x;
}
const minNumberOfLowerCaseChar = parseInt(policy);
assert(!isNaN(minNumberOfLowerCaseChar));
if (
value.split("").filter(char => char === char.toLowerCase() && char !== char.toUpperCase()).length >= minNumberOfLowerCaseChar
) {
break check_password_policy_x;
}
const msgArgs = ["invalidPasswordMinLowerCaseCharsMessage", `${minNumberOfLowerCaseChar}`] as const;
errors.push({
"validatorName": undefined,
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs)
});
}
check_password_policy_x: {
const policyName = "upperCase";
const policy = passwordPolicies[policyName];
if (policy === undefined) {
break check_password_policy_x;
}
const minNumberOfUpperCaseChar = parseInt(policy);
assert(!isNaN(minNumberOfUpperCaseChar));
if (
value.split("").filter(char => char === char.toUpperCase() && char !== char.toLowerCase()).length >= minNumberOfUpperCaseChar
) {
break check_password_policy_x;
}
const msgArgs = ["invalidPasswordMinUpperCaseCharsMessage", `${minNumberOfUpperCaseChar}`] as const;
errors.push({
"validatorName": undefined,
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs)
});
}
check_password_policy_x: {
const policyName = "specialChars";
const policy = passwordPolicies[policyName];
if (policy === undefined) {
break check_password_policy_x;
}
const minNumberOfSpecialChar = parseInt(policy);
assert(!isNaN(minNumberOfSpecialChar));
if (value.split("").filter(char => !char.match(/[a-zA-Z0-9]/)).length >= minNumberOfSpecialChar) {
break check_password_policy_x;
}
const msgArgs = ["invalidPasswordMinSpecialCharsMessage", `${minNumberOfSpecialChar}`] as const;
errors.push({
"validatorName": undefined,
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs)
});
}
check_password_policy_x: {
const policyName = "notUsername";
const notUsername = passwordPolicies[policyName];
if (!notUsername) {
break check_password_policy_x;
}
const usernameFieldValue = fieldValues.find(fieldValue => fieldValue.name === "username");
if (usernameFieldValue === undefined) {
break check_password_policy_x;
}
if (value !== usernameFieldValue.value) {
break check_password_policy_x;
}
const msgArgs = ["invalidPasswordNotUsernameMessage"] as const;
errors.push({
"validatorName": undefined,
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs)
});
}
check_password_policy_x: {
const policyName = "notEmail";
const notEmail = passwordPolicies[policyName];
if (!notEmail) {
break check_password_policy_x;
}
const emailFieldValue = fieldValues.find(fieldValue => fieldValue.name === "email");
if (emailFieldValue === undefined) {
break check_password_policy_x;
}
if (value !== emailFieldValue.value) {
break check_password_policy_x;
}
const msgArgs = ["invalidPasswordNotEmailMessage"] as const;
errors.push({
"validatorName": undefined,
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs)
});
}
}
password_confirm_matches_password: {
if (name !== "password-confirm") {
break password_confirm_matches_password;
}
const passwordFieldValue = fieldValues.find(fieldValue => fieldValue.name === "password");
assert(passwordFieldValue !== undefined);
if (passwordFieldValue.value === value) {
break password_confirm_matches_password;
}
const msgArgs = ["invalidPasswordConfirmMessage"] as const;
errors.push({
"validatorName": undefined,
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs)
});
}
2024-04-21 08:12:25 +02:00
const { validators } = attribute;
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
required_field: {
if (!attribute.required) {
break required_field;
}
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
if (value !== "") {
break required_field;
}
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
const msgArgs = ["error-user-attribute-required"] as const;
2023-03-18 06:14:05 +01:00
errors.push({
2024-04-21 08:12:25 +02:00
"validatorName": undefined,
2023-03-18 06:14:05 +01:00
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
2024-04-21 08:12:25 +02:00
"errorMessageStr": msgStr(...msgArgs)
2023-03-18 06:14:05 +01:00
});
}
2024-04-21 08:12:25 +02:00
validator_x: {
const validatorName = "length";
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
const validator = validators[validatorName];
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
if (validator === undefined) {
break validator_x;
}
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
const { "ignore.empty.value": ignoreEmptyValue = false, max, min } = validator;
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
if (ignoreEmptyValue && value === "") {
break validator_x;
}
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
if (max !== undefined && value.length > parseInt(max)) {
const msgArgs = ["error-invalid-length-too-long", max] as const;
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
errors.push({
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs),
validatorName
});
2023-03-18 06:14:05 +01:00
}
2024-04-21 08:12:25 +02:00
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
});
}
2023-03-18 06:14:05 +01:00
}
2024-04-21 08:12:25 +02:00
validator_x: {
const validatorName = "pattern";
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
const validator = validators[validatorName];
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
if (validator === undefined) {
break validator_x;
}
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
const { "ignore.empty.value": ignoreEmptyValue = false, pattern, "error-message": errorMessageKey } = validator;
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
if (ignoreEmptyValue && value === "") {
break validator_x;
}
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
if (new RegExp(pattern).test(value)) {
break validator_x;
}
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
const msgArgs = [errorMessageKey ?? id<MessageKey>("shouldMatchPattern"), pattern] as const;
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
errors.push({
validatorName,
"errorMessage": <Fragment key={errors.length}>{advancedMsg(...msgArgs)}</Fragment>,
"errorMessageStr": advancedMsgStr(...msgArgs)
});
}
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
validator_x: {
if ([...errors].reverse()[0]?.validatorName === "pattern") {
break validator_x;
}
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
const validatorName = "email";
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
const validator = validators[validatorName];
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
if (validator === undefined) {
break validator_x;
}
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
const { "ignore.empty.value": ignoreEmptyValue = false } = validator;
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
if (ignoreEmptyValue && value === "") {
break validator_x;
}
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
if (emailRegexp.test(value)) {
break validator_x;
}
const msgArgs = [id<MessageKey>("invalidEmailMessage")] as const;
2023-03-18 06:14:05 +01:00
errors.push({
validatorName,
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs)
});
}
2024-04-21 08:12:25 +02:00
validator_x: {
const validatorName = "integer";
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
const validator = validators[validatorName];
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
if (validator === undefined) {
break validator_x;
}
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
const { "ignore.empty.value": ignoreEmptyValue = false, max, min } = validator;
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
if (ignoreEmptyValue && value === "") {
break validator_x;
}
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
const intValue = parseInt(value);
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
if (isNaN(intValue)) {
const msgArgs = ["mustBeAnInteger"] as const;
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
errors.push({
validatorName,
"errorMessage": <Fragment key={errors.length}>{msg(...msgArgs)}</Fragment>,
"errorMessageStr": msgStr(...msgArgs)
});
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
break validator_x;
}
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
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;
}
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
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;
}
2023-03-18 06:14:05 +01:00
}
2024-04-21 08:12:25 +02:00
validator_x: {
const validatorName = "options";
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
const validator = validators[validatorName];
if (validator === undefined) {
break validator_x;
}
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
if (value === "") {
break validator_x;
}
2023-03-18 06:14:05 +01:00
2024-04-21 08:12:25 +02:00
if (validator.options.indexOf(value) >= 0) {
break validator_x;
}
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;
}
);
2023-03-18 06:14:05 +01:00
return { getErrors };
}