guardz-axios
Version:
Type-safe HTTP client built on top of Axios with runtime validation using guardz. Part of the guardz ecosystem for comprehensive TypeScript type safety.
208 lines • 7.47 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.safeExtractData = safeExtractData;
exports.extractPaginatedData = extractPaginatedData;
exports.handleAxiosError = handleAxiosError;
exports.createResponseValidator = createResponseValidator;
exports.defaultRetryCondition = defaultRetryCondition;
exports.calculateBackoffDelay = calculateBackoffDelay;
exports.shouldTriggerAuth = shouldTriggerAuth;
exports.extractErrorMessage = extractErrorMessage;
const guardz_1 = require("guardz");
const axios_response_guards_1 = require("../guards/axios-response-guards");
const axios_error_guards_1 = require("../guards/axios-error-guards");
/**
* Safely extract data from Axios response with type validation
*/
function safeExtractData(response, dataGuard) {
if (!(0, axios_response_guards_1.isAxiosResponse)(response)) {
return { success: false, error: "Invalid Axios response structure" };
}
if (!(0, axios_response_guards_1.isSuccessResponse)(response)) {
return {
success: false,
error: `HTTP error: ${response.status} ${response.statusText}`,
};
}
if (!dataGuard(response.data)) {
return {
success: false,
error: "Response data does not match expected type",
};
}
return { success: true, data: response.data, response };
}
/**
* Validate and extract paginated response data
*/
function extractPaginatedData(response, itemGuard) {
const paginatedResponseGuard = (0, guardz_1.isType)({
data: (value) => {
if (!Array.isArray(value))
return false;
return value.every((item) => itemGuard(item));
},
page: guardz_1.isNumber,
total: guardz_1.isNumber,
hasNext: (value) => typeof value === "boolean",
});
const extraction = safeExtractData(response, paginatedResponseGuard);
if (!extraction.success) {
return extraction;
}
return {
success: true,
data: extraction.data.data,
pagination: {
page: extraction.data.page,
total: extraction.data.total,
hasNext: extraction.data.hasNext,
},
response: extraction.response,
};
}
/**
* Handle Axios errors with detailed categorization
*/
function handleAxiosError(error) {
const categorization = (0, axios_error_guards_1.categorizeAxiosError)(error);
if (!categorization.isAxiosError) {
return {
category: "unknown",
message: error instanceof Error ? error.message : "Unknown error occurred",
isRetryable: false,
};
}
const axiosError = error;
let message = axiosError.message || "Axios error occurred";
let isRetryable = false;
switch (categorization.category) {
case "network":
message = "Network error - please check your connection";
isRetryable = true;
break;
case "timeout":
message = "Request timeout - please try again";
isRetryable = true;
break;
case "cancel":
message = "Request was cancelled";
isRetryable = false;
break;
case "client":
message = `Client error: ${categorization.statusCode} ${axiosError.response?.statusText || ""}`;
isRetryable = categorization.statusCode === 429; // Rate limit
break;
case "server":
message = `Server error: ${categorization.statusCode} ${axiosError.response?.statusText || ""}`;
isRetryable = [500, 502, 503, 504].includes(categorization.statusCode || 0);
break;
default:
message = axiosError.message || "Unknown error occurred";
isRetryable = false;
}
return {
category: categorization.category,
message,
statusCode: categorization.statusCode,
errorCode: categorization.errorCode,
isRetryable,
details: axiosError.response?.data,
};
}
/**
* Create a response validator with custom error messages
*/
function createResponseValidator(dataGuard, options = {}) {
const { allowedStatuses = [200, 201], customErrorMessages = {}, validateContentType, } = options;
return function (response) {
if (!(0, axios_response_guards_1.isAxiosResponse)(response)) {
return { success: false, error: "Invalid response structure" };
}
if (!allowedStatuses.includes(response.status)) {
const customMessage = customErrorMessages[response.status];
return {
success: false,
error: customMessage || `Unexpected status code: ${response.status}`,
statusCode: response.status,
};
}
if (validateContentType) {
const contentType = response.headers["content-type"] || response.headers["Content-Type"];
if (!contentType || !contentType.includes(validateContentType)) {
return {
success: false,
error: `Expected content type '${validateContentType}' but got '${contentType}'`,
};
}
}
if (!dataGuard(response.data)) {
return {
success: false,
error: "Response data validation failed",
statusCode: response.status,
};
}
return {
success: true,
data: response.data,
response,
};
};
}
/**
* Default retry condition - retry on network errors, timeouts, and 5xx server errors
*/
function defaultRetryCondition(error) {
if ((0, axios_error_guards_1.isNetworkError)(error) || (0, axios_error_guards_1.isTimeoutError)(error)) {
return true;
}
if ((0, axios_error_guards_1.isAxiosError)(error) && error.response) {
const status = error.response.status;
return status >= 500 || status === 429; // Server errors or rate limiting
}
return false;
}
/**
* Calculate exponential backoff delay
*/
function calculateBackoffDelay(attempt, baseDelay, maxDelay) {
const delay = baseDelay * Math.pow(2, attempt - 1);
return Math.min(delay, maxDelay);
}
/**
* Utility to check if error should trigger authentication flow
*/
function shouldTriggerAuth(error) {
if (!(0, axios_error_guards_1.isAxiosError)(error) || !error.response) {
return false;
}
return error.response.status === 401 || error.response.status === 403;
}
/**
* Extract error message from Axios error
*/
function extractErrorMessage(error, fallback = "An error occurred") {
if ((0, axios_error_guards_1.isAxiosError)(error)) {
// Try to extract message from response data
if (error.response?.data) {
const data = error.response.data;
if (typeof data === "string")
return data;
if (typeof data === "object" && data !== null) {
const obj = data;
if (typeof obj.message === "string")
return obj.message;
if (typeof obj.error === "string")
return obj.error;
}
}
// Fallback to error message
return error.message || fallback;
}
if (error instanceof Error) {
return error.message;
}
return fallback;
}
//# sourceMappingURL=axios-utils.js.map