diff --git a/skills/rig/rig.ts b/skills/rig/rig.ts index cb89df7..d659929 100644 --- a/skills/rig/rig.ts +++ b/skills/rig/rig.ts @@ -1455,7 +1455,7 @@ function validateSchema(value: unknown, schema: Schema, path: string, optional: const fieldPath = `${path}.${key}`; const isOptional = isOptionalSchema(fieldSchema); if (fieldValue === undefined && !isOptional) { - const expectedType = "type" in fieldSchema ? (fieldSchema as { type: string }).type : "enum" in fieldSchema ? "enum value" : "value"; + const expectedType = describeSchemaType(fieldSchema); return { ok: false, error: `${fieldPath}: missing required field (expected ${expectedType})` }; } const result = validateSchema(fieldValue, fieldSchema, fieldPath, false); @@ -1789,6 +1789,25 @@ function previewWithFirstDiff(value: string | undefined, expected: string, maxPr return `${prefixEllipsis}${preview}${suffixEllipsis}`; } +function describeSchemaType(schema: Schema): string { + if ("nullable" in schema && schema.nullable === true) { + return `${describeSchemaType((schema as NullableSchema).inner)} | null`; + } + if ("type" in schema) { + return (schema as { type: string }).type; + } + if ("enum" in schema) { + return (schema as EnumSchema).enum.map((v: Json) => JSON.stringify(v)).join(" | "); + } + if ("items" in schema) { + return "array"; + } + if ("properties" in schema || "additionalProperties" in schema) { + return "object"; + } + return "value"; +} + function bad(path: string, expected: string, actual: unknown): ValidationResult { const actualType = actual === null ? "null" : Array.isArray(actual) ? "array" : typeof actual; const actualRepr = JSON.stringify(actual); diff --git a/src/rig.test.ts b/src/rig.test.ts index 6eb51e4..f559da9 100644 --- a/src/rig.test.ts +++ b/src/rig.test.ts @@ -1246,4 +1246,37 @@ describe("missing required field error message", () => { const result = analyzeResponse(JSON.stringify({ name: "Alice" }), schema, "test", 1); expect(result.ok).toBe(true); }); + + it("reports a missing required array field with type 'array'", () => { + const schema = s.object({ items: s.array(s.string), name: s.string }); + const result = analyzeResponse(JSON.stringify({ name: "x" }), schema, "test", 1); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error.message).toContain("missing required field"); + expect(result.error.message).toContain("$.items"); + expect(result.error.message).toContain("array"); + } + }); + + it("reports a missing required nullable field with ' | null'", () => { + const schema = s.object({ score: s.nullable(s.number), name: s.string }); + const result = analyzeResponse(JSON.stringify({ name: "x" }), schema, "test", 1); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error.message).toContain("missing required field"); + expect(result.error.message).toContain("$.score"); + expect(result.error.message).toContain("number | null"); + } + }); + + it("reports a missing required enum field with enum values", () => { + const schema = s.object({ status: s.enum("open", "closed"), name: s.string }); + const result = analyzeResponse(JSON.stringify({ name: "x" }), schema, "test", 1); + expect(result.ok).toBe(false); + if (!result.ok) { + expect(result.error.message).toContain("missing required field"); + expect(result.error.message).toContain("$.status"); + expect(result.error.message).toContain('"open"'); + } + }); });