/** * @file SearchParamParser.ts * Type-safe parser for search parameters from URL * Returns Result type for clean error handling */ import { Result } from '@/lib/contracts/Result'; import { SearchParamValidators } from './SearchParamValidators'; export interface ParsedAuthParams { returnTo?: string | null; token?: string | null; email?: string | null; error?: string | null; message?: string | null; } export interface ParsedSponsorParams { type?: string | null; campaignId?: string | null; } export interface ParsedPaginationParams { page?: number; limit?: number; offset?: number; } export interface ParsedSortingParams { sortBy?: string | null; order?: 'asc' | 'desc'; } export interface ParsedFilterParams { status?: string | null; role?: string | null; tier?: string | null; } export interface ParsedWizardParams { step?: string | null; } export class SearchParamParser { private static getParam(params: URLSearchParams | Record | undefined | null, key: string): string | null { if (!params) { return null; } if (params instanceof URLSearchParams) { return params.get(key); } const value = params[key]; if (Array.isArray(value)) { return value[0] ?? null; } return value ?? null; } // Parse auth parameters static parseAuth(params: URLSearchParams | Record | undefined | null): Result { const errors: string[] = []; const returnTo = this.getParam(params, 'returnTo'); if (returnTo !== null) { const validation = SearchParamValidators.validateReturnTo(returnTo); if (!validation.isValid) { errors.push(...validation.errors); } } const token = this.getParam(params, 'token'); if (token !== null) { const validation = SearchParamValidators.validateToken(token); if (!validation.isValid) { errors.push(...validation.errors); } } const email = this.getParam(params, 'email'); if (email !== null) { const validation = SearchParamValidators.validateEmail(email); if (!validation.isValid) { errors.push(...validation.errors); } } if (errors.length > 0) { return Result.err(errors.join(', ')); } return Result.ok({ returnTo: this.getParam(params, 'returnTo'), token: this.getParam(params, 'token'), email: this.getParam(params, 'email'), error: this.getParam(params, 'error'), message: this.getParam(params, 'message'), }); } // Parse sponsor parameters static parseSponsor(params: URLSearchParams | Record | undefined | null): Result { const errors: string[] = []; const type = this.getParam(params, 'type'); if (type !== null) { const validation = SearchParamValidators.validateCampaignType(type); if (!validation.isValid) { errors.push(...validation.errors); } } if (errors.length > 0) { return Result.err(errors.join(', ')); } return Result.ok({ type: this.getParam(params, 'type'), campaignId: this.getParam(params, 'campaignId'), }); } // Parse pagination parameters static parsePagination(params: URLSearchParams | Record | undefined | null): Result { const result: ParsedPaginationParams = {}; const errors: string[] = []; const page = this.getParam(params, 'page'); if (page !== null) { const validation = SearchParamValidators.validatePage(page); if (!validation.isValid) { errors.push(...validation.errors); } else { result.page = parseInt(page); } } const limit = this.getParam(params, 'limit'); if (limit !== null) { const validation = SearchParamValidators.validateLimit(limit); if (!validation.isValid) { errors.push(...validation.errors); } else { result.limit = parseInt(limit); } } const offset = this.getParam(params, 'offset'); if (offset !== null) { const num = parseInt(offset); if (!isNaN(num)) { result.offset = num; } } if (errors.length > 0) { return Result.err(errors.join(', ')); } return Result.ok(result); } // Parse sorting parameters static parseSorting(params: URLSearchParams | Record | undefined | null): Result { const errors: string[] = []; const order = this.getParam(params, 'order'); if (order !== null) { const validation = SearchParamValidators.validateOrder(order); if (!validation.isValid) { errors.push(...validation.errors); } } if (errors.length > 0) { return Result.err(errors.join(', ')); } return Result.ok({ sortBy: this.getParam(params, 'sortBy'), order: (this.getParam(params, 'order') as 'asc' | 'desc') || undefined, }); } // Parse filter parameters static parseFilters(params: URLSearchParams | Record | undefined | null): Result { return Result.ok({ status: this.getParam(params, 'status'), role: this.getParam(params, 'role'), tier: this.getParam(params, 'tier'), }); } // Parse wizard parameters static parseWizard(params: URLSearchParams | Record | undefined | null): Result { return Result.ok({ step: this.getParam(params, 'step'), }); } // Parse all parameters at once static parseAll(params: URLSearchParams | Record | undefined | null): Result< { auth: ParsedAuthParams; sponsor: ParsedSponsorParams; pagination: ParsedPaginationParams; sorting: ParsedSortingParams; filters: ParsedFilterParams; }, string > { const authResult = this.parseAuth(params); if (authResult.isErr()) return Result.err(authResult.getError()); const sponsorResult = this.parseSponsor(params); if (sponsorResult.isErr()) return Result.err(sponsorResult.getError()); const paginationResult = this.parsePagination(params); if (paginationResult.isErr()) return Result.err(paginationResult.getError()); const sortingResult = this.parseSorting(params); if (sortingResult.isErr()) return Result.err(sortingResult.getError()); const filtersResult = this.parseFilters(params); return Result.ok({ auth: authResult.unwrap(), sponsor: sponsorResult.unwrap(), pagination: paginationResult.unwrap(), sorting: sortingResult.unwrap(), filters: filtersResult.unwrap(), }); } }