checker: report a shared-schema change once, and list where else it applies - #1265
Open
reuvenharrison wants to merge 7 commits into
Open
reuvenharrison wants to merge 7 commits into
reuvenharrison wants to merge 7 commits into
Conversation
A schema reached from several properties of one payload is one schema, so a change in it is one change to the operation's contract. The walk already reported it once per parent; it now reports it once per schema, so the same schema reached through two different parents is one finding rather than two. The change carries a comment stating the principle, in all four locales, attached only where more than one property path of the payload reaches the schema, which the walk counts in a pass that reports nothing. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1265 +/- ##
==========================================
+ Coverage 92.73% 92.76% +0.02%
==========================================
Files 346 347 +1
Lines 14653 14716 +63
==========================================
+ Hits 13589 13651 +62
- Misses 1064 1065 +1
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
A schema several of a payload's property paths reach is walked once, so the change in it is reported at one of those paths. The path then no longer says which schema changed, and the other paths that reach it are absent from the changelog with nothing to account for them. The change now names the schema, from the component names the diff records on each node, so the reader has the thing to look at rather than one of its uses: added the optional property `first/shared/extra` to the response with the `200` status (schema: Shared) The name is reported for a change below the shared schema too, not only for one in it: that change is deduplicated the same way, so it owes the same account of what is missing. A schema that is not a components.schemas entry has no name, and the comment carries the change on its own. The name renders after Details rather than into it, so a check that sets its own details, as the deprecation checks do, does not drop it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
`(schema: X)` next to a property path reads as though the property is in X, and it is not always: the name is the innermost schema several of the payload's paths reach, which can enclose the one that changed. On the GitHub REST API description, adding a property to `simple-user` produces five findings whose path already names Simple User and whose shared schema is `nullable-integration`, the schema whose second use was dropped. Also widen the two-parent fixture, which now reaches First and Second through two properties each and adds a pattern inside Shared, and assert on the finding for the shared schema rather than on the total, since the payload now adds properties of its own as well. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
"is reported once" reads as once in the whole changelog. Every check walks the payload itself, so the schema is walked once per check, and a shared schema that both gained a property and gained a pattern on an existing one produces one finding from each check. The comment now says "once per check", in all four locales. Two more things the comment does not claim, now stated in the docs and pinned by a test: two properties added to the same shared schema are two changes, both at the same property path, and each operation reports the change too. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A pattern added to a schema that `userId` and `customerId` both reference
was reported at `customerId` alone. A reviewer looking for `userId` found
nothing, and the two properties can mean different things.
The change now names the other properties, in the text and as data:
the `customerId` response's property pattern `^[0-9]+$` was added for the
status `200` (shared schema: Id, also at `userId`)
"sharedSchema": {"name": "Id", "properties": ["customerId", "userId"]}
The text names one other property and counts the rest, since the paths are
long; JSON and YAML carry every one. The list has one entry per reference to
the schema, each written as the path the walk took to that reference, so it
grows with the number of references rather than with the number of paths
through them, which in a nested spec can run to millions. On the GitHub REST
API description the longest list is 44 and the findings, fingerprints and
running time are unchanged.
The shared schema is context for the reader, not part of the change's
identity: the fingerprint is computed from the arguments, so it does not
change. docs/FINGERPRINT.md said the text was hashed; it is the arguments.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
In OpenAPI 3.1 a description or summary beside a $ref overrides the component's, and the parser applies it to a copy of the component. The copy is not the components.schemas entry, so looking the name up by schema object missed it, and every such $ref was recorded as having no component name. On the spec reported in #1252, `company` is `{$ref: Company, description: ...}` in every schema that uses it, so changes reached through it lost the name entirely, and the checker, which names a schema several properties share from these fields, had no name to give. When the object lookup misses, the name now comes from the $ref itself. A $ref into a component (`#/components/schemas/Holder/properties/inner`) names a schema inside it and stays unnamed. Every path to a schema yields the same name, so the result does not depend on which reference the diff met first. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A description beside a $ref makes the parser copy the component, and the copies keep its children. On the spec reported in #1252, `company` is `{$ref: Company, description: ...}` in each schema that uses it, so what ten references reach is not Company, which is a different object at each, but Company's inline `users` array, which they all keep. The array has no name of its own, so all 31 findings named no schema. A shared schema written inline is now named after the innermost component the walk passed through on the way to it, which is the component it belongs to: those 31 findings name Company. A $ref straight to a schema inside a component passes through none, and stays unnamed. This needs the copies to be named, which the diff fix in this branch does. Without it the innermost named component on that spec is MarketPartner, a schema that merely references Company. The new test fails without either half. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements the #1256 decision, on top of #1263 (the walk memo) and #1264 (the diff carrying component names).
What changes
A schema that several properties of one payload reference is one schema, so a change in it, or below it, is one change to the operation's contract. Each check now reports it once, at one of those properties, and says where else it applies:
JSON and YAML carry the full list as a field, so a consumer never has to parse the text:
mainalready dropsuserIdhere with no explanation, since #1263 merged; v1.33.0 reported both. This PR keeps the single record and puts the information back.and 1 more, because these paths are long. The field lists every one.docs/FINGERPRINT.mdsaid the text was hashed, and is corrected.A fix to #1264 in the diff
In OpenAPI 3.1 a description beside a
$refoverrides the component's, and the parser applies it to a copy of the component. The copy is not thecomponents.schemasentry, so #1264's lookup by schema object missed it and recorded no name. The name now comes from the$refwhen that lookup misses. A$refto a schema inside a component stays unnamed.The copies keep the component's children, so what several references reach can be an inline child of the component. That is the case on the spec reported in #1252: ten references reach
Company's inlineusersarray. Such a schema is named after the component the walk passed through to reach it, so those findings nameCompany. Without the diff fix they would nameMarketPartner, a schema that only referencesCompany. A test fails without either half.Measured
Adding one optional property to
simple-userin the GitHub REST API description (13 MB):mainOn the spec from #1252, with one property added inside the cycle: 31 findings on
mainand here, all 31 now namingCompanywith ten references each. Fingerprints and finding counts are identical to this branch before the list was added, on both specs.Known limitation
The walk deduplicates by schema object, and a description beside each
$refgives each reference its own copy. So a property added toAddress, wherebillingandshippingboth$refit with a description, is still reported twice with no explanation. Without the descriptions it is reported once, with the list. That matches v1.33.0, so nothing regresses. Treating the copies as one schema would mean keying the walk on the component name, which could hide a real difference between copies. That needs a separate decision.Follow-up, not in this PR
Grouping across operations, which is where most of the remaining volume is: of the 358 findings above, 355 are the same property spread over 298 operations. The output has to keep one record per operation, because the fingerprint covers the operation and tools that consume the output key changes by it. So that grouping belongs in presentation: a schema identity field on every change, and a schemas view next to the existing endpoint grouping in the formatters.
Tests
userIdandcustomerIdshareId: one finding listing both, and three properties render as "and 1 more" (shared_schema_two_properties_*).shared_schema_two_parents_*).shared_schema_override_*), and a$refstraight into a component stays unnamed (shared_schema_unnamed_*).$refwith an override and leaves a$refinto a component unnamed. Mutation-checked.sharedSchema.Full suite,
go vet,golangci-lintandgofmtpass.For the release notes
sharedSchemafield in JSON and YAML output.--err-ignoreentry written against the old text of a shared-schema finding no longer matches, because the text now carries the schema and the other properties.🤖 Generated with Claude Code