import type { FetchOptions } from 'ofetch' import { $fetch, FetchError } from 'ofetch' export type AnyObject = Record export type ApiClient = { get(url: string, query?: AnyObject, options?: ApiFetchOptions<'json'>): Promise getBlob(url: string, query?: AnyObject, options?: ApiFetchOptions<'blob'>): Promise post(url: string, body?: AnyObject, options?: ApiFetchOptions<'json'>): Promise put(url: string, body?: AnyObject, options?: ApiFetchOptions<'json'>): Promise patch(url: string, body?: AnyObject, options?: ApiFetchOptions<'json'>): Promise delete(url: string, query?: AnyObject, options?: ApiFetchOptions<'json'>): Promise } export type ApiFetchOptions = FetchOptions & { toast?: boolean toastTitle?: string } export const useApi = (): ApiClient => { const config = useRuntimeConfig() const baseURL = config.public.apiBase ?? '/api' const toast = useToast() const extractErrorMessage = (error: unknown, responseData?: unknown): string => { const data = responseData ?? (error as FetchError)?.data if (typeof data === 'string') { return data } if (data && typeof data === 'object') { const record = data as Record return ( (record['hydra:description'] as string) || (record.detail as string) || (record.message as string) || (record.error as string) || (record.title as string) || (record['hydra:title'] as string) || '' ) } return (error as FetchError)?.message ?? 'Erreur inconnue.' } const client = $fetch.create({ baseURL, retry: 0, onResponseError({ response, error, options }) { const apiOptions = options as ApiFetchOptions<'json'> if (apiOptions?.toast === false) { return } const message = extractErrorMessage(error, response?._data) || 'Une erreur est survenue.' toast.error({ title: apiOptions?.toastTitle ?? 'Erreur', message }) } }) const request = ( method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE', url: string, options: ApiFetchOptions<'json'> = {} ) => { const needsJsonBody = method === 'POST' || method === 'PUT' const needsMergePatch = method === 'PATCH' const headers = new Headers(options.headers as HeadersInit | undefined) if (needsMergePatch && !headers.has('Content-Type')) { headers.set('Content-Type', 'application/merge-patch+json') } else if (needsJsonBody && !headers.has('Content-Type')) { headers.set('Content-Type', 'application/json') } return client(url, { ...options, method, headers }) } return { get(url: string, query: AnyObject = {}, options: ApiFetchOptions<'json'> = {}) { return request('GET', url, { ...options, query }) }, getBlob(url: string, query: AnyObject = {}, options: ApiFetchOptions<'blob'> = {}) { return client(url, { ...options, method: 'GET', query, responseType: 'blob' }) }, post(url: string, body: AnyObject = {}, options: ApiFetchOptions<'json'> = {}) { return request('POST', url, { ...options, body }) }, put(url: string, body: AnyObject = {}, options: ApiFetchOptions<'json'> = {}) { return request('PUT', url, { ...options, body }) }, patch(url: string, body: AnyObject = {}, options: ApiFetchOptions<'json'> = {}) { return request('PATCH', url, { ...options, body }) }, delete(url: string, query: AnyObject = {}, options: ApiFetchOptions<'json'> = {}) { return request('DELETE', url, { ...options, query }) } } }