(
schema: z.ZodTypeAny,
options: ValidationOptions = {}
)
| 60 | * @returns Validation result with any errors found |
| 61 | */ |
| 62 | export function validateComponentSchema( |
| 63 | schema: z.ZodTypeAny, |
| 64 | options: ValidationOptions = {} |
| 65 | ): SchemaValidationResult { |
| 66 | const errors: string[] = []; |
| 67 | const objectSchema = unwrapToObject(schema); |
| 68 | if (!objectSchema) { |
| 69 | return { valid: true, errors }; |
| 70 | } |
| 71 | const shape = getObjectShape(objectSchema); |
| 72 | |
| 73 | for (const [fieldName, fieldSchema] of Object.entries(shape)) { |
| 74 | const typedSchema = fieldSchema as z.ZodTypeAny; |
| 75 | const portMeta = getPortMeta(typedSchema); |
| 76 | if (!portMeta) { |
| 77 | continue; |
| 78 | } |
| 79 | try { |
| 80 | deriveConnectionType(typedSchema); |
| 81 | } catch (error) { |
| 82 | errors.push( |
| 83 | `Field "${fieldName}": ${error instanceof Error ? error.message : String(error)}`, |
| 84 | ); |
| 85 | } |
| 86 | |
| 87 | // Rule: Require label or default to field name |
| 88 | if (!portMeta?.label) { |
| 89 | // OK - will default to field name in extractPorts |
| 90 | } |
| 91 | |
| 92 | // Rule: Block z.any()/z.unknown() without explicit allowAny |
| 93 | const unwrapped = unwrapEffects(typedSchema); |
| 94 | if (isAnyOrUnknown(unwrapped) && !portMeta?.allowAny) { |
| 95 | errors.push( |
| 96 | `Field "${fieldName}": z.any() or z.unknown() requires explicit meta.allowAny=true${portMeta?.reason ? ` (${portMeta.reason})` : ''}` |
| 97 | ); |
| 98 | } |
| 99 | |
| 100 | // Rule: If allowAny is set, require reason |
| 101 | if (portMeta?.allowAny && !portMeta?.reason) { |
| 102 | errors.push(`Field "${fieldName}": meta.allowAny=true requires meta.reason explaining why`); |
| 103 | } |
| 104 | |
| 105 | // Rule: Check depth limit (default 1 level) |
| 106 | if (options.maxDepth !== undefined) { |
| 107 | const depth = calculateDepth(typedSchema); |
| 108 | if (depth > options.maxDepth) { |
| 109 | errors.push( |
| 110 | `Field "${fieldName}": Nesting depth ${depth} exceeds max depth ${options.maxDepth}. Use meta.connectionType for complex nested types.` |
| 111 | ); |
| 112 | } |
| 113 | } |
| 114 | |
| 115 | // Rule: If schemaName is set, it's a contract export |
| 116 | if (portMeta?.schemaName) { |
| 117 | // OK - explicit contract export |
| 118 | } |
| 119 |
no test coverage detected