Skip to content

Commit 9d5eb4e

Browse files
committed
add zoddify and serialize fcn for DataShapes
1 parent 18603e4 commit 9d5eb4e

File tree

1 file changed

+119
-0
lines changed

1 file changed

+119
-0
lines changed
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
import { z, ZodRawShape } from 'zod';
2+
import { DataShape, FieldDefinition, FieldType, Status } from '../index.js';
3+
4+
/**
5+
* Converts a FieldType enum to appropriate Zod schema
6+
*/
7+
function fieldTypeToZodSchema(field: FieldDefinition): z.ZodTypeAny {
8+
let baseSchema: z.ZodTypeAny;
9+
10+
switch (field.type) {
11+
case FieldType.STRING:
12+
baseSchema = z.string().min(1, `${field.name} cannot be empty`);
13+
break;
14+
15+
case FieldType.MARKDOWN:
16+
baseSchema = z.string().min(1, `${field.name} cannot be empty`);
17+
18+
// Special handling for known patterns like Blanks
19+
if (field.name === 'Input') {
20+
baseSchema = baseSchema
21+
.refine(
22+
(value) => value.includes('{{') && value.includes('}}'),
23+
'Must contain at least one blank in format {{answer}} or {{answer1|answer2||distractor}}'
24+
)
25+
.describe(
26+
"Markdown text with blanks. Format: {{answer}} for fill-in or {{answer1|answer2||distractor1|distractor2}} for multiple choice. Use the 'fill-in-card-authoring' MCP prompt for detailed syntax guidance."
27+
);
28+
} else {
29+
baseSchema = baseSchema.describe('Markdown content');
30+
}
31+
break;
32+
33+
case FieldType.NUMBER:
34+
baseSchema = z.number().describe(`Numeric value for ${field.name}`);
35+
break;
36+
37+
case FieldType.INT:
38+
baseSchema = z.number().int().describe(`Integer value for ${field.name}`);
39+
break;
40+
41+
case FieldType.IMAGE:
42+
baseSchema = z.any().optional().describe('Image file');
43+
break;
44+
45+
case FieldType.AUDIO:
46+
baseSchema = z.any().optional().describe('Audio file');
47+
break;
48+
49+
case FieldType.MIDI:
50+
baseSchema = z.any().optional().describe('MIDI file');
51+
break;
52+
53+
case FieldType.MEDIA_UPLOADS:
54+
baseSchema = z
55+
.array(z.any())
56+
.optional()
57+
.describe('Optional media files (images, audio, etc.)');
58+
break;
59+
60+
case FieldType.CHESS_PUZZLE:
61+
baseSchema = z
62+
.string()
63+
.min(1, 'Chess puzzle cannot be empty')
64+
.describe('Chess puzzle in FEN or PGN format');
65+
break;
66+
67+
default:
68+
baseSchema = z.any().describe(`Field of type ${field.type}`);
69+
}
70+
71+
// Add custom validation if present
72+
if (field.validator) {
73+
const originalValidator = field.validator;
74+
baseSchema = baseSchema.refine(
75+
(value) => {
76+
try {
77+
const result = originalValidator.test(String(value));
78+
return result.status === Status.ok;
79+
} catch {
80+
return false;
81+
}
82+
},
83+
{
84+
message: originalValidator.instructions || `Validation failed for ${field.name}`,
85+
}
86+
);
87+
88+
// Add validator instructions as description if available
89+
if (originalValidator.instructions) {
90+
baseSchema = baseSchema.describe(
91+
`${baseSchema.description || ''}\nInstructions: ${originalValidator.instructions}`.trim()
92+
);
93+
}
94+
}
95+
96+
return baseSchema;
97+
}
98+
99+
/**
100+
* Converts a DataShape to a Zod schema
101+
*/
102+
export function toZod(dataShape: DataShape): z.ZodObject<ZodRawShape> {
103+
const schemaFields: Record<string, z.ZodTypeAny> = {};
104+
105+
dataShape.fields.forEach((field) => {
106+
schemaFields[field.name] = fieldTypeToZodSchema(field);
107+
});
108+
109+
return z.object(schemaFields).describe(`DataShape: ${dataShape.name} - Schema for card creation`);
110+
}
111+
112+
/**
113+
* Converts a DataShape to JSON Schema string using Zod v4 native conversion
114+
*/
115+
export function toZodJSON(dataShape: DataShape): string {
116+
const zodSchema = toZod(dataShape);
117+
const jsonSchema = z.toJSONSchema(zodSchema);
118+
return JSON.stringify(jsonSchema, null, 2);
119+
}

0 commit comments

Comments
 (0)