Files
plex-playlist/frontend/src/validation.ts

127 lines
3.6 KiB
TypeScript
Raw Normal View History

/**
* Hypermodern Zod validation setup - automatic runtime validation without explicit .parse() calls
* Follows the same philosophy as typeguard for Python
*/
import { z } from 'zod';
// Global schemas for automatic validation
export const PlaylistSchema = z.object({
id: z.string().uuid(),
name: z.string().min(1),
description: z.string().optional(),
tracks: z.array(z.string()).default([]),
});
export const UserSchema = z.object({
id: z.number(),
username: z.string().min(1),
email: z.string().email(),
preferences: z
.object({
theme: z.enum(['light', 'dark']).default('light'),
notifications: z.boolean().default(true),
})
.default({}),
});
// Inferred types (no manual type definitions needed)
export type Playlist = z.infer<typeof PlaylistSchema>;
export type User = z.infer<typeof UserSchema>;
/**
* Global schema registry for automatic validation
*/
class SchemaRegistry {
private static schemas = new Map<string, z.ZodTypeAny>([
['Playlist', PlaylistSchema as z.ZodTypeAny],
['User', UserSchema as z.ZodTypeAny],
]);
static register<T extends z.ZodTypeAny>(name: string, schema: T): void {
this.schemas.set(name, schema as z.ZodTypeAny);
}
static get(name: string): z.ZodTypeAny | undefined {
return this.schemas.get(name);
}
static validate<T>(typeName: string, data: unknown): T {
const schema = this.schemas.get(typeName);
if (!schema) {
throw new Error(`No schema registered for type: ${typeName}`);
}
return schema.parse(data) as T;
}
static safeValidate<T>(
typeName: string,
data: unknown
): { success: true; data: T } | { success: false; error: z.ZodError } {
const schema = this.schemas.get(typeName);
if (!schema) {
return {
success: false,
error: new z.ZodError([
{
code: 'custom',
message: `No schema registered for type: ${typeName}`,
path: [],
},
]),
};
}
const result = schema.safeParse(data);
return result.success
? { success: true, data: result.data as T }
: { success: false, error: result.error };
}
}
/**
* Automatic validation wrapper - works like typeguard for Python
* In development/test: validates automatically
* In production: can be stripped by build process
*/
export class AutoValidator {
static validate<T>(typeName: string, data: unknown): T {
// Check if auto-validation is enabled (development/test mode)
if (typeof globalThis !== 'undefined' && (globalThis as any).__AUTO_VALIDATE__) {
return SchemaRegistry.validate<T>(typeName, data);
}
// In production, validation is skipped for performance
return data as T;
}
static safeValidate<T>(
typeName: string,
data: unknown
): { success: true; data: T } | { success: false; error: z.ZodError } {
if (typeof globalThis !== 'undefined' && (globalThis as any).__AUTO_VALIDATE__) {
return SchemaRegistry.safeValidate<T>(typeName, data);
}
// In production, assume data is valid
return { success: true, data: data as T };
}
static register<T extends z.ZodTypeAny>(name: string, schema: T): void {
SchemaRegistry.register(name, schema);
}
}
// Enable automatic validation in development/test environments
declare global {
var __AUTO_VALIDATE__: boolean;
}
// Check environment and log status
const isDev = typeof globalThis !== 'undefined' && (globalThis as any).__AUTO_VALIDATE__;
if (isDev) {
console.log('🛡️ Hypermodern Zod validation enabled (development/test mode)');
} else {
console.log('🏃‍♂️ Production mode - Zod validation optimized away');
}