/** * Errors the API reports to clients: an HTTP status, a stable machine-readable `code` and a * message ready to show. Clients branch on the code, never on the wording of the message. * * 400 bad_request the request itself is wrong (missing or malformed parameters) * 404 unknown_endpoint no such API address: the server is older than the client * 404 document_not_found the document left the catalog * 404 group_not_found the document has no such group * 413 file_too_large an import over 10 MB * 422 unreadable_file an import that is not a readable Word table * 429 too_many_requests the client sends too much; Retry-After says when to come back * 429 busy the server is parsing other imports * 502 source_unavailable the university catalog did not answer * 502 source_invalid the catalog answered, but its folders or tables are not recognised * 503 not_ready /api/ready only: the server is still loading the catalog or its index * 504 source_timeout the university catalog answered too slowly * 500 internal a fault of the server itself */ export class ApiError extends Error { constructor(status, code, message) { super(message); this.status = status; this.code = code; } } export const apiError = (status, code, message) => new ApiError(status, code, message); const SOURCE_DOWN = "Каталог университета сейчас не отвечает. Попробуйте позже."; const SOURCE_SLOW = "Каталог университета отвечает слишком долго. Попробуйте позже."; /** * A failed download from the university as an API error: fetch aborted on the time limit is a * timeout, any other failure (no connection, an HTTP error, a redirect) means the source is unavailable. */ export function sourceError(error) { if (error instanceof ApiError) return error; const timedOut = error?.name === "TimeoutError" || error?.name === "AbortError"; return timedOut ? new ApiError(504, "source_timeout", SOURCE_SLOW) : new ApiError(502, "source_unavailable", SOURCE_DOWN); } /** Status, code and message for any error that reached the error handler. */ export function describe(error) { if (error instanceof ApiError) return { status: error.status, code: error.code, message: error.message }; if (error?.code === "LIMIT_FILE_SIZE") return { status: 413, code: "file_too_large", message: "Файл больше 10 МБ. Выберите документ меньшего размера.", }; return { status: 500, code: "internal", message: "Не удалось загрузить расписание. Проверьте подключение и попробуйте ещё раз.", }; }