Press n or j to go to the next uncovered block, b, p or k for the previous block.
| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 | /** * API Route Validation Module * * Provides type-safe request validation for API routes with * NextRequest/NextResponse integration and security headers. */ import { NextRequest, NextResponse } from 'next/server'; import { ZodError, ZodSchema } from 'zod'; import { addSecurityHeaders } from '@/lib/security'; import { formatValidationErrors, type ValidationErrorDetail } from './index'; // ============================================================================ // TYPES // ============================================================================ /** * Result of an API validation operation */ export type ApiValidationResult<T> = | { success: true; data: T } | { success: false; error: NextResponse }; /** * Formatted validation error response body */ export interface ApiValidationErrorBody { error: string; details: ValidationErrorDetail[]; } // ============================================================================ // REQUEST VALIDATION // ============================================================================ /** * Validate request body against a Zod schema * * Returns either the validated data or a formatted error response. * * @example * ```ts * const result = await validateRequestBody(request, MySchema); * if (!result.success) return result.error; * const data = result.data; // Fully typed * ``` */ export async function validateRequestBody<T>( request: NextRequest, schema: ZodSchema<T> ): Promise<ApiValidationResult<T>> { try { const body = await request.json(); const data = schema.parse(body); return { success: true, data }; } catch (error) { if (error instanceof ZodError) { return { success: false, error: createApiValidationErrorResponse(error, 'Invalid request body')}; } // JSON parsing error if (error instanceof SyntaxError) { const response = NextResponse.json( { error: 'Invalid JSON in request body' }, { status: 400 } ); return { success: false, error: addSecurityHeaders(response) }; } // Re-throw unexpected errors throw error; } } /** * Validate query parameters against a Zod schema * * @example * ```ts * const result = validateQueryParams(request, paginationSchema); * if (!result.success) return result.error; * const { page, limit } = result.data; * ``` */ export function validateQueryParams<T>( request: NextRequest, schema: ZodSchema<T> ): ApiValidationResult<T> { try { const { searchParams } = new URL(request.url); const params = Object.fromEntries(searchParams.entries()); const data = schema.parse(params); return { success: true, data }; } catch (error) { if (error instanceof ZodError) { return { success: false, error: createApiValidationErrorResponse(error, 'Invalid query parameters')}; } throw error; } } /** * Validate route parameters (from URL path) * * @example * ```ts * const result = validateApiRouteParams({ id: params.id }, z.object({ id: idSchema })); * if (!result.success) return result.error; * const { id } = result.data; * ``` */ export function validateApiRouteParams<T>( params: Record<string, string | string[] | undefined>, schema: ZodSchema<T> ): ApiValidationResult<T> { try { const data = schema.parse(params); return { success: true, data }; } catch (error) { if (error instanceof ZodError) { return { success: false, error: createApiValidationErrorResponse(error, 'Invalid route parameters')}; } throw error; } } // ============================================================================ // ERROR RESPONSES // ============================================================================ /** * Create a standardized API validation error response */ export function createApiValidationErrorResponse( error: ZodError, message: string = 'Validation failed' ): NextResponse { const errorBody: ApiValidationErrorBody = { error: message, details: formatValidationErrors(error)}; const response = NextResponse.json(errorBody, { status: 400 }); return addSecurityHeaders(response); } |