Runtime Validation Strategy with Zod
Overview
Section titled “Overview”Aichaku uses Zod for runtime validation of external data inputs, providing type safety beyond TypeScript’s compile-time checks. This document explains our validation strategy and implementation decisions.
Why Zod?
Section titled “Why Zod?”The Problem
Section titled “The Problem”TypeScript only provides compile-time type checking. When Aichaku:
- Loads configuration files from disk
- Parses YAML/JSON content
- Processes CLI arguments
- Receives data from external sources
…there’s no guarantee the data matches our expected types at runtime. Users might manually edit config files, or files might become corrupted.
Why We Chose Zod
Section titled “Why We Chose Zod”After evaluating alternatives (see Deno Validation Libraries Report), we selected Zod because:
-
Best Developer Experience
- Exceptional TypeScript inference
- Single source of truth (schema generates types)
- Extensive documentation
- Large, active community
-
Perfect Fit for CLI Tools
- Bundle size (60kb) irrelevant for CLI applications
- Server-side focus matches our use case
- Mature, stable, production-ready
-
Ecosystem Compatibility
- Works seamlessly with Deno via
npm:specifier - Already using npm packages (MCP SDK)
- Rich ecosystem of integrations
- Works seamlessly with Deno via
-
Superior to Alternatives
- Typebox: More verbose, less intuitive API
- Valibot: Younger ecosystem, limited documentation
- ArkType: Still in beta, too risky
- Native validation: Would require excessive boilerplate
Implementation Strategy
Section titled “Implementation Strategy”Boundary Validation Pattern
Section titled “Boundary Validation Pattern”External World → [Zod Validation] → Your Code → [TypeScript Types] → Internal LogicWe validate data at system boundaries, not everywhere:
✅ Where We Use Zod
Section titled “✅ Where We Use Zod”-
Configuration Files (
src/utils/config-manager.ts)const rawConfig = JSON.parse(content);const config = AichakuConfigSchema.parse(rawConfig); // Validated! -
YAML Parsing (
src/utils/yaml-config-reader.ts)const parsed = parse(yamlContent);return YamlConfigSchema.parse(parsed); // Safe parsing -
CLI Arguments (
src/utils/config-schemas.ts)export const CLIArgsSchema = z.object({help: z.boolean().optional(),version: z.boolean().optional(),// ... validated argument structure});
❌ Where We Don’t Use Zod
Section titled “❌ Where We Don’t Use Zod”- Internal function calls between modules
- Test code
- Generated or computed data
- Type-safe builder patterns
Schema Definitions
Section titled “Schema Definitions”All Zod schemas are centralized in src/utils/config-schemas.ts:
// Example: Project Configuration Schemaexport const ProjectConfigSchema = z.object({ version: z.string().regex(/^\d+\.\d+\.\d+$/), selected: z.object({ methodologies: z.array(z.string()).default([]), standards: z.array(z.string()).default([]), principles: z.array(z.string()).default([]), agents: z.array(z.string()).default([]), }), metadata: z.object({ lastUpdated: z.string().datetime().optional(), projectPath: z.string().optional(), }).optional(),});
// Type inference - no duplication!export type ProjectConfig = z.infer<typeof ProjectConfigSchema>;Best Practices
Section titled “Best Practices”1. Fail Fast with Clear Errors
Section titled “1. Fail Fast with Clear Errors”try { const config = ConfigSchema.parse(data);} catch (error) { if (error instanceof z.ZodError) { console.error("Invalid configuration:", error.format()); }}2. Use Safe Parsing When Appropriate
Section titled “2. Use Safe Parsing When Appropriate”const result = ConfigSchema.safeParse(data);if (result.success) { // Use result.data} else { // Handle result.error}3. Provide Defaults
Section titled “3. Provide Defaults”const ConfigSchema = z.object({ theme: z.string().default("light"), items: z.array(z.string()).default([]),});4. Compose Schemas
Section titled “4. Compose Schemas”const BaseSchema = z.object({ id: z.string() });const ExtendedSchema = BaseSchema.extend({ name: z.string(), description: z.string(),});Performance Considerations
Section titled “Performance Considerations”- Overhead: <5ms for typical config validation
- Bundle Size: 60kb (acceptable for CLI tools)
- Runtime Impact: Negligible for non-hot-path validation
Migration Guide
Section titled “Migration Guide”When adding validation to existing code:
-
Identify External Data Entry Points
- File reads
- User inputs
- Network responses
-
Create Schema
const DataSchema = z.object({// Define expected structure}); -
Replace Type Assertions
// Before (unsafe)const data = JSON.parse(content) as DataType;// After (safe)const data = DataSchema.parse(JSON.parse(content)); -
Handle Validation Errors
try {const data = DataSchema.parse(input);} catch (error) {// Provide user-friendly error message}
Security Benefits
Section titled “Security Benefits”Zod validation helps prevent:
- Injection Attacks (OWASP A03): Validates and sanitizes inputs
- Data Integrity Issues (OWASP A08): Ensures data structure compliance
- Type Confusion Vulnerabilities: Runtime type enforcement
Future Considerations
Section titled “Future Considerations”Potential areas for expanded validation:
- MCP server responses
- File system operation results
- Plugin/extension data
- Network API responses