import axios, { AxiosInstance, AxiosResponse } from "axios";
import { Config, RequestOptions, ApiResponse } from "./types.js";
import { AuthManager } from "./auth.js";
import { SwaggerDocumentationParser } from "./swagger.js";
export class RestClient {
private httpClient: AxiosInstance;
private authManager: AuthManager;
private swaggerParser?: SwaggerDocumentationParser;
private config: Config;
constructor(config: Config) {
this.config = config;
this.httpClient = axios.create({
baseURL: config.baseUrl,
timeout: config.timeout,
});
this.authManager = new AuthManager(
config.auth,
config.baseUrl,
config.timeout
);
if (config.swaggerUrl) {
this.swaggerParser = new SwaggerDocumentationParser(config.swaggerUrl);
}
this.setupInterceptors();
}
private setupInterceptors(): void {
// Request interceptor to add authentication headers
this.httpClient.interceptors.request.use(
async (config) => {
await this.authManager.ensureAuthenticated();
const authHeaders = this.authManager.getAuthHeaders();
Object.assign(config.headers, authHeaders);
return config;
},
(error) => Promise.reject(error)
);
// Response interceptor for error handling and retry logic
this.httpClient.interceptors.response.use(
(response) => response,
async (error) => {
if (error.response?.status === 401) {
// Token might be expired, try to re-authenticate
this.authManager.logout();
await this.authManager.authenticate();
// Retry the original request
const originalRequest = error.config;
if (!originalRequest._retry) {
originalRequest._retry = true;
const authHeaders = this.authManager.getAuthHeaders();
Object.assign(originalRequest.headers, authHeaders);
return this.httpClient(originalRequest);
}
}
return Promise.reject(error);
}
);
}
async initialize(): Promise<void> {
await this.authManager.authenticate();
if (this.swaggerParser) {
try {
console.log(
`🔍 Attempting to parse Swagger documentation from: ${this.config.swaggerUrl}`
);
await this.swaggerParser.parseDocumentation();
console.log("✅ Swagger documentation parsed successfully");
} catch (error) {
console.error(
"❌ Failed to parse Swagger documentation:",
error instanceof Error ? error.message : "Unknown error"
);
// Don't throw the error, just log it so the client can still work without Swagger
// this.swaggerParser = undefined; // Optionally disable swagger parser
}
}
}
async makeRequest<T = any>(options: RequestOptions): Promise<ApiResponse<T>> {
const { method, path, params, body, headers } = options;
try {
let response: AxiosResponse<T>;
const config = {
headers: headers || {},
params: method === "GET" ? params : undefined,
};
switch (method) {
case "GET":
response = await this.httpClient.get(path, config);
break;
case "POST":
response = await this.httpClient.post(path, body || params, config);
break;
case "PUT":
response = await this.httpClient.put(path, body || params, config);
break;
case "DELETE":
response = await this.httpClient.delete(path, config);
break;
case "PATCH":
response = await this.httpClient.patch(path, body || params, config);
break;
default:
throw new Error(`Unsupported HTTP method: ${method}`);
}
return {
data: response.data,
status: response.status,
statusText: response.statusText,
headers: response.headers as Record<string, string>,
};
} catch (error) {
if (axios.isAxiosError(error)) {
throw new Error(
`HTTP ${error.response?.status || "Unknown"}: ${
error.response?.data?.message || error.message
}`
);
}
throw error;
}
}
async get<T = any>(
path: string,
params?: Record<string, any>,
headers?: Record<string, string>
): Promise<ApiResponse<T>> {
return this.makeRequest<T>({ method: "GET", path, params, headers });
}
async post<T = any>(
path: string,
body?: any,
headers?: Record<string, string>
): Promise<ApiResponse<T>> {
return this.makeRequest<T>({ method: "POST", path, body, headers });
}
async put<T = any>(
path: string,
body?: any,
headers?: Record<string, string>
): Promise<ApiResponse<T>> {
return this.makeRequest<T>({ method: "PUT", path, body, headers });
}
async delete<T = any>(
path: string,
headers?: Record<string, string>
): Promise<ApiResponse<T>> {
return this.makeRequest<T>({ method: "DELETE", path, headers });
}
async patch<T = any>(
path: string,
body?: any,
headers?: Record<string, string>
): Promise<ApiResponse<T>> {
return this.makeRequest<T>({ method: "PATCH", path, body, headers });
}
getSwaggerDocumentation(): string {
if (!this.swaggerParser) {
return "Swagger documentation not configured";
}
return this.swaggerParser.getEndpointSummary();
}
searchEndpoints(query: string): string {
if (!this.swaggerParser) {
return "Swagger documentation not configured";
}
const endpoints = this.swaggerParser.searchEndpoints(query);
if (endpoints.length === 0) {
return `No endpoints found matching "${query}"`;
}
return endpoints
.map(
(endpoint) =>
`${endpoint.method} ${endpoint.path}${
endpoint.summary ? ` - ${endpoint.summary}` : ""
}`
)
.join("\n");
}
getEndpointInfo(path: string, method: string): string {
if (!this.swaggerParser) {
return "Swagger documentation not configured";
}
const endpoint = this.swaggerParser.getEndpoint(path, method);
if (!endpoint) {
return `Endpoint ${method} ${path} not found`;
}
let info = `${endpoint.method} ${endpoint.path}\n`;
if (endpoint.summary) info += `Summary: ${endpoint.summary}\n`;
if (endpoint.description) info += `Description: ${endpoint.description}\n`;
if (endpoint.parameters && endpoint.parameters.length > 0) {
info += "\nParameters:\n";
endpoint.parameters.forEach((param) => {
info += ` - ${param.name} (${param.in})${
param.required ? " *required*" : ""
}: ${param.description || "No description"}\n`;
});
}
return info;
}
isAuthenticated(): boolean {
return this.authManager.isAuthenticated();
}
async logout(): Promise<void> {
this.authManager.logout();
}
}