feat(frontend): add typed API contract layer
Made-with: Cursor
This commit is contained in:
75
frontend/src/api/errors.ts
Normal file
75
frontend/src/api/errors.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
export type ApiErrorCode =
|
||||
| "validation_error"
|
||||
| "not_found"
|
||||
| "database_unavailable"
|
||||
| "conflict"
|
||||
| "network_error"
|
||||
| "unknown_error";
|
||||
|
||||
export interface ApiErrorPayload {
|
||||
error?: string;
|
||||
details?: string[];
|
||||
}
|
||||
|
||||
export class ApiError extends Error {
|
||||
public readonly code: ApiErrorCode;
|
||||
public readonly status: number | null;
|
||||
public readonly details: string[];
|
||||
|
||||
constructor(params: {
|
||||
code: ApiErrorCode;
|
||||
message: string;
|
||||
status?: number | null;
|
||||
details?: string[];
|
||||
}) {
|
||||
super(params.message);
|
||||
this.name = "ApiError";
|
||||
this.code = params.code;
|
||||
this.status = params.status ?? null;
|
||||
this.details = params.details ?? [];
|
||||
}
|
||||
}
|
||||
|
||||
function normalizeApiCode(value: string | undefined): ApiErrorCode {
|
||||
if (
|
||||
value === "validation_error" ||
|
||||
value === "not_found" ||
|
||||
value === "database_unavailable" ||
|
||||
value === "conflict"
|
||||
) {
|
||||
return value;
|
||||
}
|
||||
return "unknown_error";
|
||||
}
|
||||
|
||||
export function toApiError(status: number, payload: unknown): ApiError {
|
||||
const maybePayload = payload as ApiErrorPayload;
|
||||
const code = normalizeApiCode(maybePayload?.error);
|
||||
const details = Array.isArray(maybePayload?.details)
|
||||
? maybePayload.details.filter((item): item is string => typeof item === "string")
|
||||
: [];
|
||||
|
||||
return new ApiError({
|
||||
code,
|
||||
status,
|
||||
message: getApiErrorMessage(code),
|
||||
details,
|
||||
});
|
||||
}
|
||||
|
||||
export function getApiErrorMessage(code: ApiErrorCode): string {
|
||||
switch (code) {
|
||||
case "validation_error":
|
||||
return "Проверьте введённые данные и попробуйте снова.";
|
||||
case "not_found":
|
||||
return "Запись не найдена.";
|
||||
case "database_unavailable":
|
||||
return "Сервис временно недоступен. Попробуйте позже.";
|
||||
case "conflict":
|
||||
return "Запись с таким идентификатором уже существует.";
|
||||
case "network_error":
|
||||
return "Не удалось связаться с сервером.";
|
||||
default:
|
||||
return "Произошла неизвестная ошибка.";
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user