fix(jsonschema): emit $ref/$defs for recursive input types#1513
Conversation
BuildJsonSchema inlined GraphQL input types and truncated recursion at a fixed depth, silently dropping self-referential fields (e.g. left/right). Because every object is emitted with additionalProperties: false, a valid nested payload was then rejected at the validation boundary with "additional properties 'left', 'right' not allowed", even though the same input passes normal GraphQL validation. Represent recursive input types via "$ref"/"$defs": each recursive type is emitted once under the root "$defs" and referenced with "$ref" everywhere it occurs (including its own self-references). Recursion now terminates by construction and nesting is supported to any depth. Non-recursive input types are unchanged and remain inlined. The maxRecursionDepth option is retained on the exported signatures for API compatibility but is now a no-op; the recursion-tracker leak it caused is gone with the tracker. Adds a regression test that validates a nested recursive payload against the generated schema using a real JSON Schema validator. Closes ENG-9631
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (1)
🚧 Files skipped from review as they are similar to previous changes (1)
📝 WalkthroughWalkthroughRefactor variable JSON Schema emission to detect recursive input-object types, emit reusable definitions under ChangesRecursive Input Types via $ref/$defs
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes 🚥 Pre-merge checks | ✅ 4✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Claude Code Review
This repository is configured for manual code reviews. Comment @claude review to trigger a review and subscribe this PR to future pushes, or @claude review once for a one-time review.
Tip: disable this comment in your organization's Code Review settings.
The *WithOptions variants accepted a depth parameter that is now a no-op since recursive types are emitted via $ref/$defs. With no in-repo callers, keep only BuildJsonSchema and NewVariablesSchemaBuilder.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
v2/pkg/engine/jsonschema/variables_schema.go (2)
124-127: 💤 Low valueStale comment: recursion depth truncation no longer applies.
The comment references "maximum recursion depth" but that behavior was removed. The
nilcheck is still valid for error cases (e.g., undefined types), but the comment should reflect the actual reason.📝 Suggested comment update
- // Skip this variable if we reached maximum recursion depth + // Skip this variable if type processing failed (e.g., undefined type) if varSchema == nil { return }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@v2/pkg/engine/jsonschema/variables_schema.go` around lines 124 - 127, The existing comment above the nil-check for varSchema is stale (mentions "maximum recursion depth"); update it to accurately state why varSchema can be nil and why we return: replace the recursion-related text with a brief note like "skip this variable if schema resolution failed or type is undefined" so readers know varSchema == nil represents an unresolved/invalid schema/error case (refer to the varSchema variable and the nil check in variables_schema.go).
394-397: 💤 Low valueStale comment: recursion depth truncation no longer applies.
Same issue as line 124 - the comment references removed behavior.
📝 Suggested comment update
- // Skip this field if we reached maximum recursion depth + // Skip this field if type processing failed if fieldSchema == nil { return }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@v2/pkg/engine/jsonschema/variables_schema.go` around lines 394 - 397, The comment above the nil-check for fieldSchema is stale (it mentions recursion depth truncation which no longer applies); update or remove that comment so it accurately states why we return when fieldSchema == nil (e.g., "skip processing when schema resolution returned nil / no schema available for this field") and make the same change to the duplicate stale comment referenced earlier (the one similar to line 124); ensure the comment references fieldSchema and the nil-return behavior rather than recursion depth.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@v2/pkg/engine/jsonschema/variables_schema.go`:
- Around line 124-127: The existing comment above the nil-check for varSchema is
stale (mentions "maximum recursion depth"); update it to accurately state why
varSchema can be nil and why we return: replace the recursion-related text with
a brief note like "skip this variable if schema resolution failed or type is
undefined" so readers know varSchema == nil represents an unresolved/invalid
schema/error case (refer to the varSchema variable and the nil check in
variables_schema.go).
- Around line 394-397: The comment above the nil-check for fieldSchema is stale
(it mentions recursion depth truncation which no longer applies); update or
remove that comment so it accurately states why we return when fieldSchema ==
nil (e.g., "skip processing when schema resolution returned nil / no schema
available for this field") and make the same change to the duplicate stale
comment referenced earlier (the one similar to line 124); ensure the comment
references fieldSchema and the nil-return behavior rather than recursion depth.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: f32b0f20-a5d9-4635-bbaa-bdc19d9fdba5
📒 Files selected for processing (2)
v2/pkg/engine/jsonschema/variables_schema.gov2/pkg/engine/jsonschema/variables_schema_test.go
Problem
BuildJsonSchemainlines input types and truncates recursion at a fixed depth, dropping self-referential fields (e.g.left/right). WithadditionalProperties: false, valid nested payloads are then rejected:additional properties 'left', 'right' not allowed. The same input passes normal GraphQL; only the generated JSON Schema (MCP path) rejects it.Also fixes a tracker leak: the early
return nilat the depth limit skipped cleanup, leaking the recursion counter across sibling uses of a type.Fix
Emit
$ref/$defsfor recursive input types: a first pass finds self-/mutually-recursive types; each is emitted once under root$defsand referenced via$ref. Recursion terminates by construction and nesting works to any depth. Non-recursive types are unchanged (still inlined).maxRecursionDepthis kept on the exported signatures for compatibility but is now a no-op.Design note
Revisits #1124, which deliberately dropped
$deffor LLM compatibility. That holds for non-recursive inputs (kept inline); for recursive inputs the only alternative to$refis a schema that rejects valid data, so$defsis reintroduced for recursive types only. The validator (santhosh-tekuri/jsonschema) supports it.Tests
recursive_input_test.go: validates a nested recursive payload against the generated schema usingsanthosh-tekuri/jsonschema/v5.Closes ENG-9631