Zod Schema Generation
Overview
This guide explains how to use the Zod schema generation feature in PEDAL, including supported features, usage examples, and troubleshooting tips.
Usage
CLI
To generate Zod schemas from an OpenAPI or AST file:
node scripts/generate-zod.js --input path/to/openapi.yaml --output path/to/zod-schemas.ts
--input
: Path to your OpenAPI YAML/JSON or AST file--output
: Path to write the generated Zod schemas
API
You can also use the generator programmatically:
import { generateZodSchema } from 'src/pipeline/stages/zod-generator';
import openApiDoc from './openapi.json';
const { schemas } = generateZodSchema({ oas: openApiDoc });
console.log(schemas.User.parse({ ... }));
Supported Features
Type Mapping: string, number, integer, boolean, array, object, enum, union (oneOf/anyOf), intersection (allOf)
Format Support: uuid, email, uri, date, date-time
Validation Rules: minLength, maxLength, pattern, minimum, maximum, exclusiveMinimum, exclusiveMaximum, multipleOf, minItems, maxItems, uniqueItems, minProperties, maxProperties
References:
$ref
resolution for local schemasEdge Cases: Handles empty schemas, optional-only objects, invalid patterns
Limitations
Circular References: Not fully supported; will throw or skip
Polymorphism: Discriminators and advanced polymorphic constructs are not supported
External $ref: Only local references are supported
Custom Zod Refinements: Only standard OpenAPI validation rules are mapped
Example
OpenAPI Input:
User:
type: object
properties:
id:
type: string
format: uuid
email:
type: string
format: email
age:
type: integer
minimum: 0
required: [id, email]
Generated Zod Schema:
export const UserSchema = z.object({
id: z.string().uuid(),
email: z.string().email(),
age: z.number().int().gte(0).optional()
});
export type User = z.infer<typeof UserSchema>;
FAQ
Q: How do I add custom validation? A: Extend the generated schema with .refine()
in your own code.
Q: What if my OpenAPI uses unsupported features? A: The generator will skip or throw for unsupported constructs. See limitations above.
Q: How do I run tests? A: See src/pipeline/stages/__tests__/README.md
for test instructions.
Q: How do I contribute? A: See CONTRIBUTING.md for guidelines.
Schema Patterns & Best Practices
Common Schema Patterns
Basic Types
Simple:
type: object
properties:
name: { type: string }
age: { type: integer }
isActive: { type: boolean }
z.object({
name: z.string(),
age: z.number().int(),
isActive: z.boolean()
})
Validation Rules
Validated:
type: object
properties:
username: { type: string, minLength: 3, maxLength: 20, pattern: '^[a-zA-Z0-9_]+$' }
score: { type: number, minimum: 0, maximum: 100, multipleOf: 0.5 }
z.object({
username: z.string().min(3).max(20).regex(/^[a-zA-Z0-9_]+$/),
score: z.number().gte(0).lte(100).refine(v => v % 0.5 === 0)
})
Required vs. Optional
User:
type: object
properties:
id: { type: string }
email: { type: string }
age: { type: integer }
required: [id, email]
z.object({
id: z.string(),
email: z.string(),
age: z.number().int().optional()
})
Enums, Unions, Intersections
Status:
enum: [active, inactive, pending]
Response:
oneOf:
- type: string
- type: number
Manager:
allOf:
- $ref: '#/components/schemas/Person'
- $ref: '#/components/schemas/Employee'
z.enum(['active', 'inactive', 'pending'])
z.union([z.string(), z.number()])
z.intersection(PersonSchema, EmployeeSchema)
Not/Inverse Validation
NonEmptyString:
type: string
not:
type: string
maxLength: 0
z.string().refine(val => val.length > 0, { message: 'Must not be empty' })
Advanced & Real-World Patterns
Nested Objects & Arrays
Order:
type: object
properties:
items:
type: array
items:
type: object
properties:
sku: { type: string }
qty: { type: integer, minimum: 1 }
minItems: 1
z.object({
items: z.array(z.object({
sku: z.string(),
qty: z.number().int().gte(1)
})).min(1)
})
Cross-Referenced Schemas
UserProfile:
type: object
properties:
user: { $ref: '#/components/schemas/User' }
preferences:
type: object
properties:
theme: { type: string, enum: [light, dark] }
notifications: { type: boolean }
Format-Specific Strings
Email:
type: string
format: email
z.string().email()
Best Practices
Prefer explicit required/optional fields for clarity.
Use OpenAPI validation keywords to maximize Zod's static validation.
For custom logic, extend generated schemas with
.refine()
in your codebase.Avoid circular references and unsupported polymorphism.
Keep schemas modular and reusable.
Usage Examples
See the test suite for real-world and edge-case examples.
Use generated Zod schemas for API validation, form validation, and data transformation.
Last updated