- Created comprehensive Zod schemas (apiRequestSchemas.ts) for: * LoginRequest, RegisterRequest, CreateUserRequest * UpdateUserRequest, UpdateProfileRequest * SendMessageRequest, UpdateMessageRequest * CreateConversationRequest, UpdateConversationRequest * UploadTrackRequest, UpdateTrackRequest * PaginationParams and list/search request types - Added validation utilities: * validateApiRequest: Validate requests before sending * safeValidateApiRequest: Safe validation with error handling * validateApiRequestWithError: Validation with custom error handler - Integrated validation into API client request interceptor - Enhanced validatedApiClient with request validation support - Automatic validation prevents invalid requests from being sent - Comprehensive test suite (19 tests, all passing) - Ensures runtime type safety for all API requests
190 lines
5.3 KiB
TypeScript
190 lines
5.3 KiB
TypeScript
/**
|
|
* 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 { AxiosResponse } from 'axios';
|
|
import { z } from 'zod';
|
|
import { apiClient } from './client';
|
|
import { safeValidateApiResponse } from '@/schemas/apiSchemas';
|
|
import { safeValidateApiRequest } from '@/schemas/apiRequestSchemas';
|
|
import { logger } from '@/utils/logger';
|
|
|
|
// Extend InternalAxiosRequestConfig to include validation schemas
|
|
interface ValidatedRequestConfig {
|
|
_requestSchema?: z.ZodSchema;
|
|
_responseSchema?: z.ZodSchema;
|
|
[key: string]: any;
|
|
}
|
|
|
|
/**
|
|
* 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<T>(
|
|
requestFn: () => Promise<AxiosResponse<T>>,
|
|
schema: z.ZodSchema<T>,
|
|
options: {
|
|
throwOnError?: boolean;
|
|
} = {},
|
|
): Promise<T> {
|
|
const { throwOnError = false } = options;
|
|
|
|
const response = await requestFn();
|
|
const validation = safeValidateApiResponse(schema, response.data);
|
|
|
|
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: <T = any>(
|
|
url: string,
|
|
schema: z.ZodSchema<T>,
|
|
config?: ValidatedRequestConfig,
|
|
): Promise<T> => {
|
|
return validatedRequest(
|
|
() => apiClient.get<T>(url, { ...config, _responseSchema: schema } as any),
|
|
schema,
|
|
);
|
|
},
|
|
|
|
/**
|
|
* GET request with request params validation
|
|
*/
|
|
getWithParams: <T = any, P = any>(
|
|
url: string,
|
|
paramsSchema: z.ZodSchema<P>,
|
|
responseSchema: z.ZodSchema<T>,
|
|
params?: P,
|
|
config?: ValidatedRequestConfig,
|
|
): Promise<T> => {
|
|
// Validate params
|
|
if (params) {
|
|
const validation = safeValidateApiRequest(paramsSchema, params);
|
|
if (!validation.success) {
|
|
throw new Error(`Request params validation failed: ${validation.error?.errors.map(e => e.message).join(', ')}`);
|
|
}
|
|
params = validation.data;
|
|
}
|
|
return validatedRequest(
|
|
() => apiClient.get<T>(url, { ...config, params, _responseSchema: responseSchema } as any),
|
|
responseSchema,
|
|
);
|
|
},
|
|
|
|
/**
|
|
* POST request with validation
|
|
*/
|
|
post: <T = any, D = any>(
|
|
url: string,
|
|
data?: D,
|
|
requestSchema?: z.ZodSchema<D>,
|
|
responseSchema?: z.ZodSchema<T>,
|
|
config?: ValidatedRequestConfig,
|
|
): Promise<T> => {
|
|
// Validate request data if schema provided
|
|
let validatedData = data;
|
|
if (requestSchema && data !== undefined && data !== null) {
|
|
const validation = safeValidateApiRequest(requestSchema, data);
|
|
if (!validation.success) {
|
|
throw new Error(`Request validation failed: ${validation.error?.errors.map(e => e.message).join(', ')}`);
|
|
}
|
|
validatedData = validation.data;
|
|
}
|
|
|
|
if (!responseSchema) {
|
|
// If no response schema provided, use regular client
|
|
return apiClient.post<T>(url, validatedData, { ...config, _requestSchema: requestSchema } as any).then((res) => res.data);
|
|
}
|
|
return validatedRequest(
|
|
() => apiClient.post<T>(url, validatedData, { ...config, _requestSchema: requestSchema, _responseSchema: responseSchema } as any),
|
|
responseSchema,
|
|
);
|
|
},
|
|
|
|
/**
|
|
* PUT request with validation
|
|
*/
|
|
put: <T = any>(
|
|
url: string,
|
|
data?: any,
|
|
schema?: z.ZodSchema<T>,
|
|
config?: ValidatedRequestConfig,
|
|
): Promise<T> => {
|
|
if (!schema) {
|
|
return apiClient.put<T>(url, data, config as any).then((res) => res.data);
|
|
}
|
|
return validatedRequest(
|
|
() => apiClient.put<T>(url, data, { ...config, _responseSchema: schema } as any),
|
|
schema,
|
|
);
|
|
},
|
|
|
|
/**
|
|
* PATCH request with validation
|
|
*/
|
|
patch: <T = any>(
|
|
url: string,
|
|
data?: any,
|
|
schema?: z.ZodSchema<T>,
|
|
config?: ValidatedRequestConfig,
|
|
): Promise<T> => {
|
|
if (!schema) {
|
|
return apiClient.patch<T>(url, data, config as any).then((res) => res.data);
|
|
}
|
|
return validatedRequest(
|
|
() => apiClient.patch<T>(url, data, { ...config, _responseSchema: schema } as any),
|
|
schema,
|
|
);
|
|
},
|
|
|
|
/**
|
|
* DELETE request with validation
|
|
*/
|
|
delete: <T = any>(
|
|
url: string,
|
|
schema?: z.ZodSchema<T>,
|
|
config?: ValidatedRequestConfig,
|
|
): Promise<T> => {
|
|
if (!schema) {
|
|
return apiClient.delete<T>(url, config as any).then((res) => res.data);
|
|
}
|
|
return validatedRequest(
|
|
() => apiClient.delete<T>(url, { ...config, _responseSchema: schema } as any),
|
|
schema,
|
|
);
|
|
},
|
|
};
|
|
|