This commit is contained in:
John Yeo
2025-10-03 19:59:30 +01:00
parent 3b99b79ef8
commit 238ca5d180
5 changed files with 403 additions and 67 deletions

View File

@@ -0,0 +1,116 @@
import type { z } from "zod/v4";
import type { JSDocParam } from "../openApiHelpers.js";
type ZodAnyObject = z.ZodObject<z.ZodRawShape>;
type ZodAnyField = z.ZodTypeAny;
/**
* Extracts parameter information from a Zod schema to create JSDoc params
*
* @param schema - A Zod object schema (e.g., z.object({ ... }))
* @returns Array of JSDocParam objects with name, description, and optional flag
*
* @example
* ```typescript
* const AttachSchema = z.object({
* customer_id: z.string().describe("The customer ID"),
* product_id: z.string().describe("The product ID"),
* entity_id: z.string().optional().describe("Optional entity ID"),
* });
*
* const params = extractParamsFromSchema(AttachSchema);
* // Returns: [
* // { name: "customer_id", description: "The customer ID", optional: false },
* // { name: "product_id", description: "The product ID", optional: false },
* // { name: "entity_id", description: "Optional entity ID", optional: true },
* // ]
* ```
*/
export function extractParamsFromSchema(schema: ZodAnyObject): JSDocParam[] {
const params: JSDocParam[] = [];
// Get the shape of the object schema
const shape = schema.shape;
for (const [fieldName, fieldSchema] of Object.entries(shape)) {
const zodField = fieldSchema as ZodAnyField;
// Extract description from .describe() or .meta()
let description = "";
// biome-ignore lint/suspicious/noExplicitAny: accessing Zod internal properties
const def = (zodField as any)._def;
if (zodField.description) {
description = zodField.description;
} else if (def?.description) {
description = def.description;
}
// Check if field is optional/nullable
const isOptional = isZodFieldOptional(zodField);
// Only add params that have descriptions (to avoid cluttering docs)
if (description) {
params.push({
name: fieldName,
description,
optional: isOptional,
});
}
}
return params;
}
/**
* Checks if a Zod field is optional or nullable
*/
function isZodFieldOptional(field: ZodAnyField): boolean {
// biome-ignore lint/suspicious/noExplicitAny: accessing Zod internal properties
const def = (field as any)._def;
// Check for .optional()
if (def?.typeName === "ZodOptional") {
return true;
}
// Check for .nullish()
if (def?.typeName === "ZodNullable") {
return true;
}
// Check if it's wrapped in optional/nullable
if (def?.innerType) {
return isZodFieldOptional(def.innerType);
}
// Check for default values (also makes it optional)
if (def?.defaultValue !== undefined) {
return true;
}
return false;
}
/**
* Creates a filtered list of params from a schema, including only specified fields
*
* @param schema - A Zod object schema
* @param includeFields - Array of field names to include in the output
* @returns Filtered array of JSDocParam objects
*
* @example
* ```typescript
* const params = filterSchemaParams(AttachSchema, [
* 'customer_id',
* 'product_id',
* 'entity_id'
* ]);
* ```
*/
export function filterSchemaParams(
schema: ZodAnyObject,
includeFields: string[],
): JSDocParam[] {
const allParams = extractParamsFromSchema(schema);
return allParams.filter((param) => includeFields.includes(param.name));
}