Files
wishlist/src/lib/server/validation.ts
T

133 lines
3.8 KiB
TypeScript

export function sanitizeString(
input: string | null | undefined,
maxLength: number = 1000
): string | null {
if (input === null || input === undefined) {
return null;
}
const trimmed = input.trim();
if (trimmed.length > maxLength) {
throw new Error(`Input exceeds maximum length of ${maxLength}`);
}
return trimmed;
}
export function sanitizeUrl(url: string | null | undefined): string | null {
if (!url) {
return null;
}
return url.trim();
}
export function sanitizeText(
input: string | null | undefined,
maxLength: number = 10000
): string | null {
if (input === null || input === undefined) {
return null;
}
const trimmed = input.trim();
if (trimmed.length > maxLength) {
throw new Error(`Text exceeds maximum length of ${maxLength}`);
}
return trimmed;
}
export function sanitizePrice(price: string | null | undefined): number | null {
if (!price) {
return null;
}
const parsed = parseFloat(price.trim());
if (isNaN(parsed)) {
throw new Error('Invalid price format');
}
if (parsed < 0) {
throw new Error('Price cannot be negative');
}
return parsed;
}
export function sanitizeColor(color: string | null | undefined): string | null {
if (!color) {
return null;
}
return color.trim();
}
export function sanitizeCurrency(currency: string | null | undefined): string {
if (!currency) {
return 'DKK';
}
return currency.trim().toUpperCase();
}
export function sanitizeUsername(username: string): string {
const trimmed = username.trim().toLowerCase();
if (trimmed.length < 3 || trimmed.length > 50) {
throw new Error('Username must be between 3 and 50 characters');
}
return trimmed;
}
export function sanitizeId(id: string | null | undefined): string {
if (!id) {
throw new Error('ID is required');
}
const trimmed = id.trim();
if (trimmed.length === 0 || trimmed.length > 100) {
throw new Error('Invalid ID');
}
return trimmed;
}
export function sanitizeToken(token: string | null | undefined): string {
if (!token) {
throw new Error('Token is required');
}
const trimmed = token.trim();
if (trimmed.length === 0 || trimmed.length > 100) {
throw new Error('Invalid token');
}
return trimmed;
}
// Zod schemas for type-safe form validation
import { z } from 'zod';
import { CURRENCIES } from '$lib/utils/currency';
export const currencySchema = z.enum(CURRENCIES);
export const itemSchema = z.object({
title: z.string().min(1, 'Title is required').max(255),
description: z.preprocess(val => (val === '' ? null : val), z.string().max(2000).optional().nullable()),
link: z.preprocess(val => (val === '' ? null : val), z.string().url('Invalid URL').optional().nullable()),
imageUrl: z.preprocess(val => (val === '' ? null : val), z.string().url('Invalid image URL').optional().nullable()),
price: z.coerce.number().nonnegative().optional().nullable(),
currency: currencySchema.default('DKK'),
color: z.preprocess(val => (val === '' ? null : val), z.string().optional().nullable()),
order: z.coerce.number().int().nonnegative().optional()
});
export const updateItemSchema = itemSchema.extend({
itemId: z.string().min(1, 'Item ID is required')
});
export const wishlistSchema = z.object({
title: z.string().min(1, 'Title is required').max(255),
description: z.string().max(2000).optional().nullable(),
color: z.string().optional().nullable(),
theme: z.string().optional().nullable().transform((val) => val || 'none'),
endDate: z.coerce.date().optional().nullable()
});
export const reorderSchema = z.array(
z.object({
id: z.string(),
order: z.number().int().nonnegative()
})
);
export type ItemFormData = z.infer<typeof itemSchema>;
export type UpdateItemFormData = z.infer<typeof updateItemSchema>;
export type WishlistFormData = z.infer<typeof wishlistSchema>;