fix(server)!: resolve a $ref to a non-object alias component in the response type (#171) - #181
Conversation
…esponse type (#171) A component that is a bare `type: array` (or a scalar, or a union) is a TYPE ALIAS, not a Data class, so it never enters the model registry. The response path checked the registry only, so a `$ref` to such a component degraded to JsonResponse, while the SAME schema written inline produced DataCollection<int, XData>. Resolve the alias (following an `allOf: [{$ref}]` chain to its terminal schema) and run the existing shape checks on it, so a named list or union component types exactly like its inline twin. Also close the last silent degradations. A JsonResponse return enforces nothing about the body, so falling back without a warning let a non-conforming implementation look conformant to the generator: the whole point of the scaffold is that the signature pins the documented shape. A response whose schema could not be typed, and a success response that does not resolve at all, now reach the diagnostics channel alongside every other degradation (issue #67). A response that declares NO schema stays silent by design: the spec promised no shape, so nothing is lost and there is nothing to enforce. 35 corpus specs rebaseline, each audited in READER_BASELINE_REBASELINED_171 with the measured change. Six specs gain typed returns (circleci, redocly-museum and soundcloud as DataCollection; dnd5e and stripe as Data-class unions); the rest move only because previously silent degradations are now reported. No signature moves the other way. BREAKING CHANGE: an operation whose success response `$ref`s a named array or union component changes its generated abstract signature from JsonResponse to DataCollection<int, XData> or a Data-class union. Concrete controllers implementing the old signature must be updated to the new return type. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CqTZBkmVyV8QkyzgmpoFNR
|
Warning Review limit reached
Next review available in: 26 minutes You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (7)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Closes #171.
The bug
A component that is a bare
type: array(or a scalar, or a union) is a type alias, not a Data class, soModelGeneratordeliberately skips it (isNonObjectAlias()->continue) and it never enters the registry.OperationCollector::responseType()checked the registry only, so a$refto such a component fell through to theJsonResponsefallback, while the same schema written inline producedDataCollection<int, XData>.Naming the list is the more idiomatic spec, and it was the one that lost its typing.
Why it matters
A
JsonResponsereturn enforces nothing about the body. The scaffold exists so the abstract signature pins the documented shape; when it silently degrades, an implementation can return anything and still satisfy the generated contract.This was found in the field: a Laravel API generated from a spec whose list endpoints all use named array components returned a paginated envelope (
{data, links, meta}) where the spec declared a bare array. Nothing in the chain objected. The generated signature saidJsonResponse, so it could not object, and no warning was emitted, soopenapi-laravel.unsupported.jsonreported"unsupported": [].The fix
Resolve the alias.
ModelGenerator::aliasSchemaFor()exposes the existing alias machinery (referencedAliasSchema+terminalAliasSchema, which already back property-level rule derivation).responseType()resolves a$refto a non-object alias to its terminal schema, following anallOf: [{$ref}]chain, then runs the existing shape checks on it. Named array components now type asDataCollection<int, XData>and named union components as a Data-class union.Stop degrading silently. A response whose schema could not be typed, and a success response that does not resolve at all, now reach the diagnostics channel (issue 1.0.0: every silent degradation to mixed or Request must hit the warnings channel #67). The class docblock already promised "every such fallback is surfaced through the warnings channel"; the response path was the case where that was not true. A response declaring no schema stays silent by design: the spec promised no shape, so nothing is lost.
Corpus impact
35 specs rebaseline, each audited in
READER_BASELINE_REBASELINED_171with its measured change. Six gain typed returns:DataCollectionDataCollectionDataCollectionThe other 29 move only because previously silent degradations are now reported. No signature moves the other way - the change can only add typing where
JsonResponsestood. The before/after was computed with the frozen baseline recipe and diffed per operation, so each entry is measured rather than estimated.Gate
composer test(2315 passed, 0 failed),stan(no errors),lint(262 files),deptrac(0 violations),test:type(100%).Breaking
An operation whose success response
$refs a named array or union component changes its generated abstract signature fromJsonResponsetoDataCollection<int, XData>or a Data-class union. Concrete controllers implementing the old signature must be updated.Important
Do not merge before the 0.16.0 release PR (#178) lands, or release-please will fold this into 0.16.0. This is intended for 0.17.0.
🤖 Generated with Claude Code
https://claude.ai/code/session_01CqTZBkmVyV8QkyzgmpoFNR