veza/apps/web/src/utils/formValidation.ts
senke 4cc73b0d12 api-contracts: replace ApiError interface with Zod-inferred type
- Replace manual ApiError interface with Zod-inferred type from apiSchemas
- Update all imports (15+ files) to use ApiError from @/schemas/apiSchemas
- Remove ApiError interface from types/api.ts
- Update ApiResponse to import ApiError from schemas
- All TypeScript checks pass for ApiError-related code
2026-01-15 17:03:35 +01:00

124 lines
3.4 KiB
TypeScript

/**
* Form Validation Utilities
* Utilities for applying API validation errors to form fields
*/
import type { ApiError } from '@/schemas/apiSchemas';
import { getValidationErrors, getErrorCategory } from './apiErrorHandler';
import type { UseFormSetError, Path } from 'react-hook-form';
/**
* Applies API validation errors to react-hook-form fields
* @param error - The API error containing validation details
* @param setError - react-hook-form's setError function
* @param fieldNameMap - Optional mapping from API field names to form field names
* @returns true if validation errors were applied, false otherwise
*/
export function applyApiValidationErrors<T extends Record<string, any>>(
error: ApiError | Error | unknown,
setError: UseFormSetError<T>,
fieldNameMap?: Record<string, keyof T>,
): boolean {
// Check if this is a validation error
const apiError =
error && typeof error === 'object' && 'message' in error
? (error as ApiError)
: null;
if (!apiError) {
return false;
}
const category = getErrorCategory(apiError);
if (category !== 'validation') {
return false;
}
// Extract validation errors from error.details
const validationErrors = getValidationErrors(apiError);
if (Object.keys(validationErrors).length === 0) {
return false;
}
// Apply errors to form fields
let appliedCount = 0;
for (const [apiFieldName, message] of Object.entries(validationErrors)) {
// Map API field name to form field name if mapping provided
const formFieldName = (fieldNameMap?.[apiFieldName] ||
apiFieldName) as Path<T>;
// Set error on the form field
setError(formFieldName, {
type: 'server',
message: message,
});
appliedCount++;
}
return appliedCount > 0;
}
/**
* Gets validation error for a specific field from an API error
* @param error - The API error
* @param fieldName - The field name to get error for
* @param fieldNameMap - Optional mapping from API field names to form field names
* @returns Error message for the field, or undefined if no error
*/
export function getFieldValidationError(
error: ApiError | Error | unknown,
fieldName: string,
fieldNameMap?: Record<string, string>,
): string | undefined {
const apiError =
error && typeof error === 'object' && 'message' in error
? (error as ApiError)
: null;
if (!apiError) {
return undefined;
}
const category = getErrorCategory(apiError);
if (category !== 'validation') {
return undefined;
}
const validationErrors = getValidationErrors(apiError);
// Check direct field name
if (validationErrors[fieldName]) {
return validationErrors[fieldName];
}
// Check mapped field name
if (fieldNameMap) {
for (const [apiFieldName, formFieldName] of Object.entries(fieldNameMap)) {
if (formFieldName === fieldName && validationErrors[apiFieldName]) {
return validationErrors[apiFieldName];
}
}
}
return undefined;
}
/**
* Checks if an error is a validation error
* @param error - The error to check
* @returns true if the error is a validation error
*/
export function isValidationError(error: ApiError | Error | unknown): boolean {
const apiError =
error && typeof error === 'object' && 'message' in error
? (error as ApiError)
: null;
if (!apiError) {
return false;
}
return getErrorCategory(apiError) === 'validation';
}