Files
familienarchiv/frontend/src/lib/errors.ts

113 lines
3.4 KiB
TypeScript

import * as m from '$lib/paraglide/messages.js';
/**
* Mirror of the backend ErrorCode enum.
* Keep in sync with backend/src/main/java/org/raddatz/familienarchiv/exception/ErrorCode.java
*/
export type ErrorCode =
| 'PERSON_NOT_FOUND'
| 'ALIAS_NOT_FOUND'
| 'DOCUMENT_NOT_FOUND'
| 'DOCUMENT_NO_FILE'
| 'FILE_NOT_FOUND'
| 'FILE_UPLOAD_FAILED'
| 'UNSUPPORTED_FILE_TYPE'
| 'USER_NOT_FOUND'
| 'EMAIL_ALREADY_IN_USE'
| 'WRONG_CURRENT_PASSWORD'
| 'IMPORT_ALREADY_RUNNING'
| 'INVALID_RESET_TOKEN'
| 'ANNOTATION_NOT_FOUND'
| 'ANNOTATION_UPDATE_FAILED'
| 'TRANSCRIPTION_BLOCK_NOT_FOUND'
| 'TRANSCRIPTION_BLOCK_CONFLICT'
| 'COMMENT_NOT_FOUND'
| 'OCR_SERVICE_UNAVAILABLE'
| 'OCR_JOB_NOT_FOUND'
| 'OCR_DOCUMENT_NOT_UPLOADED'
| 'OCR_PROCESSING_FAILED'
| 'TRAINING_ALREADY_RUNNING'
| 'UNAUTHORIZED'
| 'FORBIDDEN'
| 'VALIDATION_ERROR'
| 'INTERNAL_ERROR';
export interface BackendError {
code: ErrorCode;
message: string; // English developer message — not shown to users
}
/**
* Attempts to parse a backend ErrorResponse from a failed fetch response.
* Returns null if the body is not valid JSON or does not contain a code field.
*/
export async function parseBackendError(res: Response): Promise<BackendError | null> {
try {
const body = await res.json();
if (body && typeof body.code === 'string') {
return body as BackendError;
}
} catch {
// Body was not JSON
}
return null;
}
/** Returns a localised message for the given error code via Paraglide. Falls back to INTERNAL_ERROR. */
export function getErrorMessage(code: ErrorCode | string | undefined): string {
switch (code) {
case 'PERSON_NOT_FOUND':
return m.error_person_not_found();
case 'ALIAS_NOT_FOUND':
return m.error_alias_not_found();
case 'DOCUMENT_NOT_FOUND':
return m.error_document_not_found();
case 'DOCUMENT_NO_FILE':
return m.error_document_no_file();
case 'FILE_NOT_FOUND':
return m.error_file_not_found();
case 'FILE_UPLOAD_FAILED':
return m.error_file_upload_failed();
case 'UNSUPPORTED_FILE_TYPE':
return m.error_unsupported_file_type();
case 'USER_NOT_FOUND':
return m.error_user_not_found();
case 'EMAIL_ALREADY_IN_USE':
return m.error_email_already_in_use();
case 'WRONG_CURRENT_PASSWORD':
return m.error_wrong_current_password();
case 'IMPORT_ALREADY_RUNNING':
return m.error_import_already_running();
case 'INVALID_RESET_TOKEN':
return m.error_invalid_reset_token();
case 'ANNOTATION_NOT_FOUND':
return m.error_annotation_not_found();
case 'ANNOTATION_UPDATE_FAILED':
return m.error_annotation_update_failed();
case 'TRANSCRIPTION_BLOCK_NOT_FOUND':
return m.error_transcription_block_not_found();
case 'TRANSCRIPTION_BLOCK_CONFLICT':
return m.error_transcription_block_conflict();
case 'COMMENT_NOT_FOUND':
return m.error_comment_not_found();
case 'OCR_SERVICE_UNAVAILABLE':
return m.error_ocr_service_unavailable();
case 'OCR_JOB_NOT_FOUND':
return m.error_ocr_job_not_found();
case 'OCR_DOCUMENT_NOT_UPLOADED':
return m.error_ocr_document_not_uploaded();
case 'OCR_PROCESSING_FAILED':
return m.error_ocr_processing_failed();
case 'TRAINING_ALREADY_RUNNING':
return m.error_training_already_running();
case 'UNAUTHORIZED':
return m.error_unauthorized();
case 'FORBIDDEN':
return m.error_forbidden();
case 'VALIDATION_ERROR':
return m.error_validation_error();
default:
return m.error_internal_error();
}
}