UNPKG

@syntropylog/adapters

Version:
133 lines (129 loc) 5.45 kB
import axios, { isAxiosError } from 'axios'; /** * @file src/http/adapters/AxiosAdapter.ts * @description An implementation of the IHttpClientAdapter for the Axios library. * This class acts as a "translator," converting requests and responses * between the framework's generic format and the Axios-specific format. */ /** * A helper function to normalize the Axios headers object. * The Axios header type is complex (`AxiosResponseHeaders` | `RawAxiosResponseHeaders`), * while our adapter interface expects a simple `Record<string, ...>`. * This function performs the conversion safely. * @param {RawAxiosResponseHeaders | AxiosResponseHeaders} headers - The Axios headers object. * @returns {Record<string, string | number | string[]>} A simple, normalized headers object. */ function normalizeHeaders(headers) { const normalized = {}; for (const key in headers) { if (Object.prototype.hasOwnProperty.call(headers, key)) { // Axios headers can be undefined, so we ensure they are not included. const value = headers[key]; if (value !== undefined && value !== null) { normalized[key] = value; } } } return normalized; } /** * @class AxiosAdapter * @description An adapter that allows SyntropyLog to instrument HTTP requests * made with the Axios library. It implements the `IHttpClientAdapter` interface. * @implements {IHttpClientAdapter} */ class AxiosAdapter { /** * @constructor * @param {AxiosRequestConfig | AxiosInstance} config - Either a pre-configured * Axios instance or a configuration object to create a new instance. */ constructor(config) { if ('request' in config && typeof config.request === 'function') { this.axiosInstance = config; } else { this.axiosInstance = axios.create(config); } } /** * Executes an HTTP request using the configured Axios instance. * It translates the generic `AdapterHttpRequest` into an `AxiosRequestConfig`, * sends the request, and then normalizes the Axios response or error back * into the framework's generic format (`AdapterHttpResponse` or `AdapterHttpError`). * @template T The expected type of the response data. * @param {AdapterHttpRequest} request The generic request object. * @returns {Promise<AdapterHttpResponse<T>>} A promise that resolves with the normalized response. * @throws {AdapterHttpError} Throws a normalized error if the request fails. */ async request(request) { try { // Sanitize headers before passing them to Axios. // The `request.headers` object from the instrumenter contains the full context, // which might include non-string values or keys that are not valid HTTP headers. // This ensures we only pass valid, string-based headers to the underlying client. const sanitizedHeaders = {}; const excludedHeaders = ['host', 'connection', 'content-length']; // Headers to exclude for (const key in request.headers) { if (Object.prototype.hasOwnProperty.call(request.headers, key) && typeof request.headers[key] === 'string' && !excludedHeaders.includes(key.toLowerCase()) // Exclude problematic headers ) { sanitizedHeaders[key] = request.headers[key]; } } const axiosConfig = { url: request.url, method: request.method, headers: sanitizedHeaders, params: request.queryParams, data: request.body, }; const response = await this.axiosInstance.request(axiosConfig); return { statusCode: response.status, data: response.data, headers: normalizeHeaders(response.headers), }; } catch (error) { if (isAxiosError(error)) { const normalizedError = { name: 'AdapterHttpError', message: error.message, stack: error.stack, isAdapterError: true, request: request, response: error.response ? { statusCode: error.response.status, data: error.response.data, headers: normalizeHeaders(error.response.headers), } : undefined, }; throw normalizedError; } throw error; } } } class FetchAdapter { async request(request) { const response = await fetch(request.url, { method: request.method, headers: request.headers, body: request.body ? JSON.stringify(request.body) : undefined, }); // Handle cases where the response body might be empty const text = await response.text(); const data = (text ? JSON.parse(text) : {}); return { statusCode: response.status, data: data, headers: Object.fromEntries(response.headers.entries()), }; } } export { AxiosAdapter, FetchAdapter }; //# sourceMappingURL=index.mjs.map