mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-03-18 14:18:21 +03:00
generic and custom per-field validation
This commit is contained in:
parent
f7cc87e8ce
commit
dd0c497fd3
20
web/public/locales/en/validation.json
Normal file
20
web/public/locales/en/validation.json
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
{
|
||||||
|
"minimum": "Must be at least {{limit}}",
|
||||||
|
"maximum": "Must be at most {{limit}}",
|
||||||
|
"exclusiveMinimum": "Must be greater than {{limit}}",
|
||||||
|
"exclusiveMaximum": "Must be less than {{limit}}",
|
||||||
|
"minLength": "Must be at least {{limit}} character(s)",
|
||||||
|
"maxLength": "Must be at most {{limit}} character(s)",
|
||||||
|
"minItems": "Must have at least {{limit}} items",
|
||||||
|
"maxItems": "Must have at most {{limit}} items",
|
||||||
|
"pattern": "Invalid format",
|
||||||
|
"required": "This field is required",
|
||||||
|
"type": "Invalid value type",
|
||||||
|
"enum": "Must be one of the allowed values",
|
||||||
|
"const": "Value does not match expected constant",
|
||||||
|
"uniqueItems": "All items must be unique",
|
||||||
|
"format": "Invalid format",
|
||||||
|
"additionalProperties": "Unknown property is not allowed",
|
||||||
|
"oneOf": "Must match exactly one of the allowed schemas",
|
||||||
|
"anyOf": "Must match at least one of the allowed schemas"
|
||||||
|
}
|
||||||
@ -68,7 +68,11 @@ export function ConfigForm({
|
|||||||
formContext,
|
formContext,
|
||||||
i18nNamespace,
|
i18nNamespace,
|
||||||
}: ConfigFormProps) {
|
}: ConfigFormProps) {
|
||||||
const { t } = useTranslation([i18nNamespace || "common", "views/settings"]);
|
const { t } = useTranslation([
|
||||||
|
i18nNamespace || "common",
|
||||||
|
"views/settings",
|
||||||
|
"validation",
|
||||||
|
]);
|
||||||
const [showAdvanced, setShowAdvanced] = useState(false);
|
const [showAdvanced, setShowAdvanced] = useState(false);
|
||||||
|
|
||||||
// Determine which fields to hide based on advanced toggle
|
// Determine which fields to hide based on advanced toggle
|
||||||
@ -113,7 +117,7 @@ export function ConfigForm({
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Create error transformer for user-friendly error messages
|
// Create error transformer for user-friendly error messages
|
||||||
const errorTransformer = useMemo(() => createErrorTransformer(), []);
|
const errorTransformer = useMemo(() => createErrorTransformer(t), [t]);
|
||||||
|
|
||||||
const handleChange = useCallback(
|
const handleChange = useCallback(
|
||||||
(e: IChangeEvent) => {
|
(e: IChangeEvent) => {
|
||||||
|
|||||||
@ -2,6 +2,7 @@
|
|||||||
// Maps JSON Schema validation keywords to user-friendly messages
|
// Maps JSON Schema validation keywords to user-friendly messages
|
||||||
|
|
||||||
import type { ErrorTransformer } from "@rjsf/utils";
|
import type { ErrorTransformer } from "@rjsf/utils";
|
||||||
|
import type { TFunction } from "i18next";
|
||||||
|
|
||||||
export interface ErrorMessageMap {
|
export interface ErrorMessageMap {
|
||||||
[keyword: string]: string | ((params: Record<string, unknown>) => string);
|
[keyword: string]: string | ((params: Record<string, unknown>) => string);
|
||||||
@ -30,7 +31,13 @@ export const defaultErrorMessages: ErrorMessageMap = {
|
|||||||
};
|
};
|
||||||
return formatLabels[format] || `Invalid ${format} format`;
|
return formatLabels[format] || `Invalid ${format} format`;
|
||||||
},
|
},
|
||||||
enum: "Must be one of the allowed values",
|
enum: (params) => {
|
||||||
|
const allowedValues = params.allowedValues as unknown;
|
||||||
|
if (Array.isArray(allowedValues)) {
|
||||||
|
return `Must be one of: ${allowedValues.join(", ")}`;
|
||||||
|
}
|
||||||
|
return "Must be one of the allowed values";
|
||||||
|
},
|
||||||
const: "Value does not match expected constant",
|
const: "Value does not match expected constant",
|
||||||
uniqueItems: "All items must be unique",
|
uniqueItems: "All items must be unique",
|
||||||
minItems: (params) => `Must have at least ${params.limit} items`,
|
minItems: (params) => `Must have at least ${params.limit} items`,
|
||||||
@ -44,25 +51,107 @@ export const defaultErrorMessages: ErrorMessageMap = {
|
|||||||
* Creates an error transformer function for RJSF
|
* Creates an error transformer function for RJSF
|
||||||
* Transforms technical JSON Schema errors into user-friendly messages
|
* Transforms technical JSON Schema errors into user-friendly messages
|
||||||
*/
|
*/
|
||||||
export function createErrorTransformer(
|
export function createErrorTransformer(t: TFunction): ErrorTransformer {
|
||||||
customMessages: ErrorMessageMap = {},
|
const getDefaultMessage = (
|
||||||
): ErrorTransformer {
|
errorType: string,
|
||||||
const messages = { ...defaultErrorMessages, ...customMessages };
|
params: Record<string, unknown>,
|
||||||
|
): string | undefined => {
|
||||||
|
const template = defaultErrorMessages[errorType];
|
||||||
|
|
||||||
return (errors) => {
|
if (!template) {
|
||||||
return errors.map((error) => {
|
return undefined;
|
||||||
const keyword = error.name || "";
|
}
|
||||||
const messageTemplate = messages[keyword];
|
|
||||||
|
|
||||||
if (!messageTemplate) {
|
if (typeof template === "function") {
|
||||||
|
return template(params);
|
||||||
|
}
|
||||||
|
|
||||||
|
return template;
|
||||||
|
};
|
||||||
|
|
||||||
|
const normalizeParams = (
|
||||||
|
params: Record<string, unknown> | undefined,
|
||||||
|
): Record<string, unknown> => {
|
||||||
|
if (!params) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const allowedValues = params.allowedValues as unknown;
|
||||||
|
|
||||||
|
return {
|
||||||
|
...params,
|
||||||
|
allowedValues: Array.isArray(allowedValues)
|
||||||
|
? allowedValues.join(", ")
|
||||||
|
: allowedValues,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const getFieldPathFromProperty = (
|
||||||
|
property: string | undefined,
|
||||||
|
params: Record<string, unknown>,
|
||||||
|
errorType: string,
|
||||||
|
): string => {
|
||||||
|
const basePath = (property || "").replace(/^\./, "").trim();
|
||||||
|
const missingProperty = params.missingProperty as string | undefined;
|
||||||
|
|
||||||
|
if (errorType === "required" && missingProperty) {
|
||||||
|
return basePath ? `${basePath}.${missingProperty}` : missingProperty;
|
||||||
|
}
|
||||||
|
|
||||||
|
return basePath;
|
||||||
|
};
|
||||||
|
|
||||||
|
return (errors) =>
|
||||||
|
errors.map((error) => {
|
||||||
|
const errorType = error.name || "";
|
||||||
|
if (!errorType) {
|
||||||
return error;
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
let message: string;
|
const normalizedParams = normalizeParams(error.params);
|
||||||
if (typeof messageTemplate === "function") {
|
const fieldPath = getFieldPathFromProperty(
|
||||||
message = messageTemplate(error.params || {});
|
error.property,
|
||||||
} else {
|
normalizedParams,
|
||||||
message = messageTemplate;
|
errorType,
|
||||||
|
);
|
||||||
|
|
||||||
|
let message: string | undefined;
|
||||||
|
|
||||||
|
const missingTranslation = "__missing_translation__";
|
||||||
|
|
||||||
|
// Try field-specific validation message first
|
||||||
|
if (fieldPath) {
|
||||||
|
const fieldKey = `${fieldPath}.validation.${errorType}`;
|
||||||
|
const translated = t(fieldKey, {
|
||||||
|
...normalizedParams,
|
||||||
|
ns: ["config"],
|
||||||
|
defaultValue: missingTranslation,
|
||||||
|
});
|
||||||
|
if (translated !== fieldKey && translated !== missingTranslation) {
|
||||||
|
message = translated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to generic validation message
|
||||||
|
if (!message) {
|
||||||
|
const genericKey = errorType;
|
||||||
|
const translated = t(genericKey, {
|
||||||
|
...normalizedParams,
|
||||||
|
ns: ["validation"],
|
||||||
|
defaultValue: missingTranslation,
|
||||||
|
});
|
||||||
|
if (translated !== genericKey && translated !== missingTranslation) {
|
||||||
|
message = translated;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fall back to English defaults
|
||||||
|
if (!message) {
|
||||||
|
message = getDefaultMessage(errorType, normalizedParams);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!message) {
|
||||||
|
return error;
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -70,7 +159,6 @@ export function createErrorTransformer(
|
|||||||
message,
|
message,
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user