import CommonError from '@/components/errorBoundary/CommonError';
// fetch의 RequestInit을 확장하여 params 옵션 추가
interface FetchOptions extends RequestInit {
params?: Record<string, string>;
}
// http 메서드에서 사용할 옵션 타입
type HttpOptions = {
method?: 'GET' | 'POST' | 'PUT' | 'DELETE';
body?: BodyInit | null;
headers?: HeadersInit;
};
/**
* 기본 요청 핸들러
* - baseURL 자동 적용
* - params 쿼리스트링 변환
* - 'Content-Type': 'application/json' 기본 적용
* - API 응답 에러(!response.ok)를 CommonError로 throw
* - 네트워크 에러 등 fetch 실패 시 CommonError(500)로 throw
*/
async function request<T>(url: string, options?: FetchOptions): Promise<T> {
const { params, ...fetchOptions } = options || {};
// params가 있으면 쿼리스트링으로 변환
const urlWithParams = params ? `${url}?${new URLSearchParams(params)}` : url;
try {
const response = await fetch(`${import.meta.env.VITE_API_URL}${urlWithParams}`, {
...fetchOptions,
headers: {
'Content-Type': 'application/json',
...fetchOptions.headers
}
});
// API 레벨 에러 처리 (4xx, 5xx 등)
if (!response.ok) {
// 에러 응답 본문을 파싱하되, 파싱 실패(예: 500 에러) 시 빈 객체로 대체
const errorData = await response.json().catch(() => ({}));
throw new CommonError(response.status, errorData.message || `HTTP ${response.status} 에러`);
}
// 성공 시 JSON 파싱하여 반환
return await response.json();
} catch (error) {
// 이미 CommonError인 경우 그대로 throw (위의 !response.ok)
if (error instanceof CommonError) throw error;
// 네트워크 에러 등 fetch 자체의 실패
throw new CommonError(500, error instanceof Error ? error.message : '알 수 없는 네트워크 에러');
}
}
/**
* http 클라이언트 객체
* - get, post, put, delete 메서드 제공
* - post, put은 data 객체를 자동으로 JSON.stringify
*/
export const http = {
get: <T>(path: string, options?: Omit<HttpOptions, 'method' | 'body'>) =>
request<T>(path, { ...options, method: 'GET' }),
post: <K, T>(path: string, data: K, options?: Omit<HttpOptions, 'method' | 'body'>) =>
request<T>(path, {
...options,
method: 'POST',
body: JSON.stringify(data)
}),
put: <K, T>(path: string, data: K, options?: Omit<HttpOptions, 'method' | 'body'>) =>
request<T>(path, {
...options,
method: 'PUT',
body: JSON.stringify(data)
}),
delete: <T>(path: string, options?: Omit<HttpOptions, 'method' | 'body'>) =>
request<T>(path, { ...options, method: 'DELETE' })
};