Files
gridpilot.gg/apps/website/lib/routing/search-params/SearchParamParser.ts
2026-01-17 02:03:19 +01:00

234 lines
6.7 KiB
TypeScript

/**
* @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<string, string | string[] | undefined> | 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<string, string | string[] | undefined> | undefined | null): Result<ParsedAuthParams, string> {
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<string, string | string[] | undefined> | undefined | null): Result<ParsedSponsorParams, string> {
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<string, string | string[] | undefined> | undefined | null): Result<ParsedPaginationParams, string> {
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<string, string | string[] | undefined> | undefined | null): Result<ParsedSortingParams, string> {
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<string, string | string[] | undefined> | undefined | null): Result<ParsedFilterParams, string> {
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<string, string | string[] | undefined> | undefined | null): Result<ParsedWizardParams, string> {
return Result.ok({
step: this.getParam(params, 'step'),
});
}
// Parse all parameters at once
static parseAll(params: URLSearchParams | Record<string, string | string[] | undefined> | 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(),
});
}
}