buckets/frontend/src/lib/fieldErrors.ts

46 lines
1.3 KiB
TypeScript
Raw Normal View History

import { ApiError } from './api'
/** Per-field validation messages: a 422 body's `{ errors: { field: [msg] } }`. */
export type FieldErrors = Record<string, string[]>
function isStringArray(value: unknown): value is string[] {
return Array.isArray(value) && value.every((item) => typeof item === 'string')
}
/**
* Extract the `{ errors: { field: [...] } }` map from a 422 ApiError body.
* Returns null for anything that isn't a well-formed 422 field-error response
* (wrong status, non-ApiError, non-JSON body, or a malformed errors shape) so
* the caller can fall back to a generic error message.
*/
export function parseFieldErrors(error: unknown): FieldErrors | null {
if (!(error instanceof ApiError) || error.status !== 422) {
return null
}
let parsed: unknown
try {
parsed = JSON.parse(error.body)
} catch {
return null
}
if (
typeof parsed !== 'object' ||
parsed === null ||
!('errors' in parsed) ||
typeof parsed.errors !== 'object' ||
parsed.errors === null
) {
return null
}
// Only accept the contract shape: every field maps to an array of strings.
const entries = Object.entries(parsed.errors as Record<string, unknown>)
if (!entries.every(([, messages]) => isStringArray(messages))) {
return null
}
return parsed.errors as FieldErrors
}