/* * Copyright (C) 2025 TomTom Navigation B.V. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ import { logger } from "./logger"; import axios, { AxiosError } from "axios"; import { TomTomErrorResponse, UnavailableError, ErrorWithData, UnknownError, ForbiddenError, BusyError, FaultError, IncorrectError, } from "../types/types"; /** * Handles errors from API calls, providing standardized error handling across services * @param error The error object from the API call * @param context Optional context description for logging * @returns A standardized error object */ export function handleApiError(error: unknown, context: string = "API call"): Error { // Pass through ErrorWithData subclasses unchanged if (error instanceof ErrorWithData) { return error; } // Handle axios errors if (axios.isAxiosError(error)) { const axiosError = error as AxiosError; if (axiosError.response) { // Server responded with an error status const statusCode = axiosError.response.status; let errorMessage = ""; // Process TomTom specific error responses if (typeof axiosError.response.data === "object" && axiosError.response.data) { const responseData = axiosError.response.data; // Try to extract detailed error message from TomTom error format if (responseData.detailedError) { errorMessage = `${responseData.detailedError.code || ""}: ${responseData.detailedError.message || ""}`; } else if (responseData.error) { errorMessage = responseData.error; } else { errorMessage = JSON.stringify(responseData); } } else { errorMessage = String(axiosError.response.data); } // Map status codes to appropriate error categories const baseData = { domain: "tomtom_api", status_code: statusCode, context, error_details: errorMessage, }; logger.error( { context, status_code: statusCode, error: errorMessage }, "Request failed with status code" ); // 401/403: Authentication/Authorization errors if (statusCode === 401 || statusCode === 403) { return new ForbiddenError( "Your TomTom API key may be invalid, expired, or missing permissions for this request", baseData ); } // 429: Rate limiting if (statusCode === 429) { return new BusyError("Rate limit exceeded: Too many requests to the TomTom API", baseData); } // 400: Bad request (incorrect input) if (statusCode === 400) { return new IncorrectError("Bad request to TomTom API", baseData); } // 503: Service unavailable if (statusCode === 503) { if (errorMessage.includes("no healthy upstream")) { return new UnavailableError( `TomTom service temporarily unavailable: This specific service (${context}) is experiencing an outage`, baseData ); } return new UnavailableError( "TomTom service unavailable: The service might be temporarily down or undergoing maintenance", baseData ); } // 5xx: Server errors if (statusCode >= 500 && statusCode < 600) { return new FaultError( "TomTom server error: The service encountered an internal error", baseData ); } // Other errors: Unknown return new UnknownError(`API error: ${statusCode}`, baseData); } else if (axiosError.request) { // Request was made but no response received const userMessage = "No response received from TomTom API server. Please check your internet connection."; logger.error({ context, error: userMessage }, "Request failed"); return new UnavailableError(userMessage, { domain: "tomtom_api", context, }); } } // Handle other types of errors if (error instanceof Error) { logger.error({ context, error: error.message }, "Request failed with unknown error"); return new UnknownError("Unknown error", { context }, { cause: error }); } const errorMessage = String(error); logger.error({ context, error: errorMessage }, "Request failed with unknown error"); return new UnknownError("Unknown error", { context, error_value: errorMessage, }); }