/** * API Client with Validation * FE-TYPE-002: Enhanced API client methods with automatic Zod validation * * Provides typed API client methods that automatically validate responses * using Zod schemas. */ import { InternalAxiosRequestConfig, AxiosResponse } from 'axios'; import { z } from 'zod'; import { apiClient } from './client'; import { safeValidateApiResponse } from '@/schemas/apiSchemas'; import { logger } from '@/utils/logger'; /** * Create a validated API request * * @param requestFn - Function that makes the API request * @param schema - Zod schema to validate the response * @param options - Validation options * @returns Validated response */ export async function validatedRequest( requestFn: () => Promise>, schema: z.ZodSchema, options: { strict?: boolean; throwOnError?: boolean; } = {}, ): Promise { const { strict = false, throwOnError = false } = options; const response = await requestFn(); const validation = safeValidateApiResponse(schema, response.data, { strict }); if (!validation.success) { const errorMessage = `API response validation failed: ${validation.error?.errors.map(e => e.message).join(', ')}`; logger.error('[API Validation]', { errors: validation.error?.errors, data: response.data, }); if (throwOnError) { throw new Error(errorMessage); } // In development, log warning but continue if (import.meta.env.DEV) { console.warn('[API Validation Warning]', validation.error); } } // Return validated data if validation succeeded, otherwise return original data return validation.data ?? (response.data as T); } /** * Validated API client methods * Automatically validates responses using provided schemas */ export const validatedApiClient = { /** * GET request with validation */ get: ( url: string, schema: z.ZodSchema, config?: InternalAxiosRequestConfig & { _responseSchema?: z.ZodSchema }, ): Promise => { return validatedRequest( () => apiClient.get(url, { ...config, _responseSchema: schema }), schema, ); }, /** * POST request with validation */ post: ( url: string, data?: any, schema?: z.ZodSchema, config?: InternalAxiosRequestConfig & { _responseSchema?: z.ZodSchema }, ): Promise => { if (!schema) { // If no schema provided, use regular client return apiClient.post(url, data, config).then((res) => res.data); } return validatedRequest( () => apiClient.post(url, data, { ...config, _responseSchema: schema }), schema, ); }, /** * PUT request with validation */ put: ( url: string, data?: any, schema?: z.ZodSchema, config?: InternalAxiosRequestConfig & { _responseSchema?: z.ZodSchema }, ): Promise => { if (!schema) { return apiClient.put(url, data, config).then((res) => res.data); } return validatedRequest( () => apiClient.put(url, data, { ...config, _responseSchema: schema }), schema, ); }, /** * PATCH request with validation */ patch: ( url: string, data?: any, schema?: z.ZodSchema, config?: InternalAxiosRequestConfig & { _responseSchema?: z.ZodSchema }, ): Promise => { if (!schema) { return apiClient.patch(url, data, config).then((res) => res.data); } return validatedRequest( () => apiClient.patch(url, data, { ...config, _responseSchema: schema }), schema, ); }, /** * DELETE request with validation */ delete: ( url: string, schema?: z.ZodSchema, config?: InternalAxiosRequestConfig & { _responseSchema?: z.ZodSchema }, ): Promise => { if (!schema) { return apiClient.delete(url, config).then((res) => res.data); } return validatedRequest( () => apiClient.delete(url, { ...config, _responseSchema: schema }), schema, ); }, };