hardening

This commit is contained in:
Gregor Lohaus
2026-05-22 08:55:28 +02:00
parent ca4a02ab4e
commit 3110eefcbd
8 changed files with 293 additions and 173 deletions

View File

@@ -13,12 +13,37 @@ interface Issue {
export class SchemaMismatchError extends Error {
constructor(issue: Issue) {
super(`Shema doesnt match used template variables: ${issue.path}: ${issue.message}` )
super(`Schema doesn't match used template variables: ${issue.path}: ${issue.message}` )
this.name = "SchemaMismatchError"
}
}
function zodTypeName(schema: z.ZodType): string {
if (schema instanceof z.ZodObject) return "object"
if (schema instanceof z.ZodString) return "string"
if (schema instanceof z.ZodBoolean) return "boolean"
if (schema instanceof z.ZodNumber) return "number"
return schema.constructor.name
}
function zodDisplayName(schema: z.ZodType): string {
const type = zodTypeName(schema)
return type.startsWith("Zod") ? type : `z.${type}()`
}
function setExpectedType(expected: Map<string, string>, path: string, type: string) {
const existing = expected.get(path)
if (existing && existing !== type) {
throw new SchemaMismatchError({
path,
message: `conflicting template variable types: expected both z.${existing}() and z.${type}()`,
})
}
expected.set(path, type)
}
function validateSchemaMatchesTemplates(userSchema: z.ZodType, variables: TemplateVariable[]) {
if (userSchema._zod.def.type !== "object") {
if (!(userSchema instanceof z.ZodObject)) {
throw new SchemaMismatchError({ path: "", message: "Schema must be a z.object()" })
}
@@ -28,31 +53,29 @@ function validateSchemaMatchesTemplates(userSchema: z.ZodType, variables: Templa
const segments = v.path.split(".")
for (let i = 0; i < segments.length - 1; i++) {
const intermediate = segments.slice(0, i + 1).join(".")
if (!expected.has(intermediate)) {
expected.set(intermediate, "object")
}
setExpectedType(expected, intermediate, "object")
}
expected.set(v.path, v.type)
setExpectedType(expected, v.path, v.type)
}
for (const [path, expectedType] of expected) {
const segments = path.split(".")
let current = userSchema
let current: z.ZodType = userSchema
let currentPath = ""
for (const seg of segments) {
currentPath = currentPath ? `${currentPath}.${seg}` : seg
if (current._zod.def.type !== "object") {
throw new SchemaMismatchError({ path: currentPath, message: `expected z.object() but schema has z.${current._zod.def.type as string}()` })
if (!(current instanceof z.ZodObject)) {
throw new SchemaMismatchError({ path: currentPath, message: `expected z.object() but schema has ${zodDisplayName(current)}` })
}
const shape = (current as z.ZodObject<any>).shape
const shape = current.shape
if (!(seg in shape)) {
throw new SchemaMismatchError({ path: currentPath, message: `missing in schema` })
}
current = shape[seg]
current = shape[seg]!
}
const actual = current._zod.def.type as string
const actual = zodTypeName(current)
if (actual !== expectedType) {
throw new SchemaMismatchError({ path, message: `expected z.${expectedType}() but schema has z.${actual}()` })
throw new SchemaMismatchError({ path, message: `expected z.${expectedType}() but schema has ${zodDisplayName(current)}` })
}
}
}
@@ -70,4 +93,3 @@ export const initRenderer = (dirPath: string) => {
return createRenderer
}