interface-forge
Version:
A TypeScript library for creating strongly typed mock data factories using Faker.js for test data generation
189 lines (161 loc) • 5.75 kB
text/typescript
/**
* Example: MaxDepth Behavior with Zod Schemas
*
* This example demonstrates how to properly use the maxDepth option
* with ZodFactory to control recursion depth in nested schemas.
*/
import { z } from 'zod/v4';
import { ZodFactory } from 'interface-forge/zod';
// Example 1: Understanding the Problem
// When maxDepth is reached, the factory returns empty objects {}
// This can cause validation errors with required fields
console.log('=== Example 1: MaxDepth Problem ===');
const problematicSchema = z.object({
data: z.object({
nested: z.object({
deeper: z.object({
deepest: z.object({
value: z.string(), // Required field
}),
}),
}),
}),
});
try {
const factory = new ZodFactory(problematicSchema, { maxDepth: 2 });
factory.build();
console.log('This should not print - validation should fail');
} catch {
console.log('Expected error: Schema validation failed at depth limit');
console.log('Reason: Required fields in objects beyond maxDepth');
}
// Example 2: Solution with Optional Fields
console.log('\n=== Example 2: Using Optional Fields ===');
const optionalSchema = z.object({
data: z.object({
nested: z.object({
deeper: z
.object({
deepest: z
.object({
value: z.string(),
})
.optional(), // Make deep nesting optional
})
.optional(), // Make intermediate levels optional too
}),
}),
});
const optionalFactory = new ZodFactory(optionalSchema, { maxDepth: 2 });
const optionalResult = optionalFactory.build();
console.log('Generated with optional fields:');
console.log(JSON.stringify(optionalResult, null, 2));
// Example 3: Recursive Schemas with Depth Control
console.log('\n=== Example 3: Recursive Schemas ===');
// Category tree with self-referencing schema
interface Category {
description: string;
id: string;
name: string;
subcategories?: Category[];
}
const CategorySchema: z.ZodType<Category> = z.lazy(() =>
z.object({
description: z.string().max(200),
id: z.uuid(),
name: z.string().min(3).max(50),
subcategories: z.array(CategorySchema).optional(), // Optional to handle depth limits
}),
);
const categoryFactory = new ZodFactory(CategorySchema as any, { maxDepth: 3 });
const categoryTree = categoryFactory.build() as Category;
// Helper to count depth
function countDepth(category: Category, currentDepth = 0): number {
if (!category.subcategories || category.subcategories.length === 0) {
return currentDepth;
}
return Math.max(
...category.subcategories.map((sub) =>
countDepth(sub, currentDepth + 1),
),
);
}
console.log('Generated category tree:');
console.log(`Root: ${categoryTree.name}`);
console.log(`Total depth: ${countDepth(categoryTree)}`);
// Example 4: Different MaxDepth Values
console.log('\n=== Example 4: Different MaxDepth Values ===');
interface TreeNode {
left?: TreeNode;
right?: TreeNode;
value: number;
}
const treeSchema: z.ZodType<TreeNode> = z.lazy(() =>
z.object({
left: treeSchema.optional(),
right: treeSchema.optional(),
value: z.number().int().min(1).max(100),
}),
);
// Test different depths
[1, 2, 3, 5].forEach((depth) => {
const factory = new ZodFactory(treeSchema as any, { maxDepth: depth });
const tree = factory.build() as TreeNode;
function getMaxDepth(node: TreeNode | undefined, current = 0): number {
if (!node) {
return current;
}
const leftDepth = node.left
? getMaxDepth(node.left, current + 1)
: current;
const rightDepth = node.right
? getMaxDepth(node.right, current + 1)
: current;
return Math.max(leftDepth, rightDepth);
}
console.log(`maxDepth=${depth}: Actual depth=${getMaxDepth(tree)}`);
});
// Example 5: Practical Use Case - Comment Thread
console.log('\n=== Example 5: Comment Thread with MaxDepth ===');
interface Comment {
author: string;
content: string;
id: string;
replies?: Comment[];
timestamp: Date;
}
const CommentSchema: z.ZodType<Comment> = z.lazy(() =>
z.object({
author: z.string(),
content: z.string().min(1).max(500),
id: z.uuid(),
replies: z.array(CommentSchema).max(5).optional(), // Limit replies and make optional
timestamp: z.date(),
}),
);
// Create a comment thread with limited depth
const commentFactory = new ZodFactory(CommentSchema as any, {
maxDepth: 4, // Allows for main comment + 3 levels of replies
});
const commentThread = commentFactory.build() as Comment;
function printCommentThread(comment: Comment, indent = ''): void {
console.log(
`${indent}[${comment.author}]: ${comment.content.slice(0, 50)}...`,
);
if (comment.replies) {
comment.replies.forEach((reply) => {
printCommentThread(reply, `${indent} `);
});
}
}
console.log('Generated comment thread:');
printCommentThread(commentThread);
// Example 6: Best Practices Summary
console.log('\n=== Best Practices for MaxDepth ===');
console.log(
'1. Always use .optional() for nested objects that might hit depth limits',
);
console.log('2. Design schemas with natural termination points');
console.log('3. Consider the total depth needed for your use case');
console.log('4. Test with different maxDepth values to ensure proper behavior');
console.log('5. Use lazy schemas (z.lazy) for true recursive structures');