feat: Add a check-schema-fields command to the metadata tool#4375
feat: Add a check-schema-fields command to the metadata tool#4375JamBalaya56562 wants to merge 4 commits into
check-schema-fields command to the metadata tool#4375Conversation
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## master #4375 +/- ##
=======================================
Coverage 97.51% 97.51%
=======================================
Files 193 193
Lines 19526 19526
=======================================
Hits 19040 19040
+ Misses 269 268 -1
- Partials 217 218 +1 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
gmlewis
left a comment
There was a problem hiding this comment.
Thank you, @JamBalaya56562!
Overall, this is looking good, but there are many small helper functions in schema_fields.go that have no unit tests that I think could greatly benefit from some idiomatic Go table-driven unit tests to document their behavior.
Also, I would really like to see an example of the output of this tool (for example, when some of the exceptions are removed).
Finally, it would be nice if we didn't have to modify the code and that its exceptions could be updated by loading them from the config files like we do in the other tools.
| if smallest <= 2 { | ||
| return shared == smallest | ||
| } | ||
| return shared >= 3 || shared*10 >= smallest*6 |
There was a problem hiding this comment.
This function seems to have some magic numbers and calculations in it that need a comment as to how they were chosen.
There was a problem hiding this comment.
Good call. I pulled the numbers into named constants and documented them:
const (
minSharedFieldsForMatch = 3 // accept once this many JSON fields overlap
minSharedFieldPercent = 60 // else the overlap must cover this share of the smaller field set
)Rationale (now in a comment): a schema-name match already agrees on the Go type name, so this guard only rejects a name that coincidentally collides with an unrelated struct. It's deliberately lenient — a wrong match is dropped later as ambiguous, but a missed match silently skips a real check. So tiny types (≤2 fields) must share every field, while larger types match once 3 fields overlap or the overlap covers ≥60% of the smaller field set (compared as shared*100 >= smallest*minSharedFieldPercent to stay in integer math).
| return true | ||
| } | ||
|
|
||
| var goInitialisms = map[string]string{ |
There was a problem hiding this comment.
It looks like we now will have two sets of "Go initialisms" that we will have to keep track of.
Could they possibly be shared to reduce the maintenance burden?
There was a problem hiding this comment.
The other set lives in the tools/structfield module (initialisms / specialCases), which is a separate Go module from tools/metadata with no shared package, and it's keyed/shaped differently (an uppercase-keyed set used for a different job). Sharing them would mean introducing a new shared module that both tools depend on.
For now I've kept this list small — it only needs the initialisms that actually appear in OpenAPI schema names — and added a comment noting the duplication is intentional. Happy to do the shared-module extraction as a follow-up if you'd prefer; let me know.
- Load field-optionality exceptions from schema_field_exceptions.yaml instead of hardcoding them in Go, with a --exceptions flag. - Add table-driven unit tests for the schema_fields.go helpers and the new exceptions loader. - Document the hasEnoughSharedFields thresholds as named constants. - Use %v instead of %s in the check-schema-fields verbose output. - Note the intentional Go-initialisms duplication with tools/structfield.
|
Thanks for the review, @gmlewis! I've addressed all three points in c6425df.
$ script/metadata.sh check-schema-fields --exceptions empty.yaml --verbose
Found 7 schema field issues
Checked 4 OpenAPI schema/Go struct pairs; skipped 1138 OpenAPI schemas
github/dependency_graph_snapshots.go:83: DependencyGraphSnapshot.Detector (detector from snapshot): field is required and non-nullable in the OpenAPI schema but is a pointer [descriptions/api.github.com/api.github.com.json]
github/dependency_graph_snapshots.go:82: DependencyGraphSnapshot.Job (job from snapshot): field is required and non-nullable in the OpenAPI schema but is a pointer [descriptions/api.github.com/api.github.com.json]
github/dependency_graph_snapshots.go:81: DependencyGraphSnapshot.Ref (ref from snapshot): field is required and non-nullable in the OpenAPI schema but is a pointer [descriptions/api.github.com/api.github.com.json]
github/dependency_graph_snapshots.go:84: DependencyGraphSnapshot.Scanned (scanned from snapshot): field is required and non-nullable in the OpenAPI schema but is a pointer [descriptions/api.github.com/api.github.com.json]
github/dependency_graph_snapshots.go:80: DependencyGraphSnapshot.Sha (sha from snapshot): field is required and non-nullable in the OpenAPI schema but is a pointer [descriptions/api.github.com/api.github.com.json]
github/repos_deployment_branch_policies.go:29: DeploymentBranchPolicyRequest.Name (name from deployment-branch-policy-name-pattern-with-type): field is required and non-nullable in the OpenAPI schema but is a pointer [descriptions/api.github.com/api.github.com.json]
github/actions_workflow_runs.go:138: ReviewCustomDeploymentProtectionRuleRequest.Comment (comment from review-custom-gates-state-required): field is optional in the OpenAPI schema but is not a pointer, slice, map, interface, or selector type [descriptions/api.github.com/api.github.com.json](The three |
gmlewis
left a comment
There was a problem hiding this comment.
Thank you, @JamBalaya56562 - please fix the new lint errors caused by the new unit tests, then we should be able to proceed with this PR.
Use %v instead of %d/%s to satisfy the fmtpercentv linter, and replace a no-argument t.Errorf with t.Error for the revive unnecessary-format check.
Thanks again, fixed. |
gmlewis
left a comment
There was a problem hiding this comment.
Thank you, @JamBalaya56562!
LGTM.
Awaiting second LGTM+Approval from any other contributor to this repo before merging.
stevehipwell
left a comment
There was a problem hiding this comment.
@JamBalaya56562 I like the intent here but I'm not sure I'm onboard with the approach you've taken. Could you provide a list of the structs that are mapped and a list of the structs that are skipped? Ideally it'd be good to see how the mapped structs were mapped and if by name if the fields would have resulted in a match too?
What I'm currently thinking is that this linking is brittle and has the potential to cause churn for no real reason. IMHO it'd make more sense to use a //meta:schema comment on the struct to control the mapping and ignores as this would place the logic next to the code it's related to as well as making it deterministic.
|
@stevehipwell thanks for the thorough review — I ran the numbers and I think you're right. Here is the full mapped/skipped breakdown you asked for ( Mapped (only 4 pairs, all via To your question about name-vs-field cross-checking: nothing matched by name, so there is nothing to corroborate — the CI check relies entirely on exact field-set equality. Skipped (1138):
And the brittleness you flagged is concrete in that last bucket, e.g.: An unrelated struct coincidentally matching rule-params schemas purely on So I agree with your read: the exact-field-set heuristic gives very low recall (4 request structs) and real false positives — exactly the "brittle + churn" failure mode you described. The field-comparison engine itself is sound (it did surface genuine required-vs-pointer mismatches on the structs it reached), so I'd like to keep that and replace only the matching: move to an explicit I'll rework the PR along these lines. Since this started from @gmlewis's proposal in #4319, I want to make sure the annotation format works for all of you before I invest in the rewrite — happy to adjust the exact syntax. |
Address inline review feedback on the check-schema-fields command:
- Replace the sliceSet helper with []string plus slices.Contains for the
exceptions, schema-name filter, and required-field sets.
- Replace append([]string{}, s...) with slices.Clone(s).
- Drop the --exceptions flag and always read the fixed
tools/metadata/schema_field_exceptions.yaml path.
- Drop the unused --json flag from check-schema-fields.
- Use githubClient (which requires GITHUB_TOKEN) instead of a duplicate
publicGithubClient.
- Remove the trivial defaultJSONName pass-through.
- Wrap the added comments to the package's line-length convention and trim
the exceptions file header.
|
@alexandear thanks for the review — I pushed all nine inline fixes in On your main concern — only 4 schema/struct pairs get checked — you and @stevehipwell are right, and that's the real limitation rather than the nits. The exact-field-set matching is inherently low-recall: most go-github request structs don't have a JSON field set identical to their OpenAPI schema, so the heuristic simply can't link them. I'd like to replace the matching with an explicit Since this started from @gmlewis's proposal in #4319, I'd like to confirm the annotation format with the three of you before reworking it — happy to hear preferences. |

As proposed by @gmlewis in #4319, this adds a
check-schema-fieldssubcommand to themetadatatool that compares the JSON field optionality of Go request structs against GitHub's OpenAPI schemas, so that "required-but-pointer" (and similar) mismatches — which @stevehipwell noted are tedious to verify by hand — are caught automatically.What it does
For each mutating request-body struct it matches the struct to its OpenAPI component schema (by exact JSON field set, or by schema name in
--schemamode) and reports fields whose optionality disagrees with the schema:omitempty/omitzero,Only request-body structs are checked by default: a struct is treated as a request body when it is passed as the body argument to a mutating
client.NewRequestcall and is not also returned as a response (*T/[]*T), so response and shared all-pointer types are not flagged.--include-responsesand--schemaopt into broader checks.Exceptions
Following the same convention as
paramcheck/structfield, a smallschemaFieldExceptionslist intools/metadata/schema_fields.gorecordsStruct.Fielddeviations that are intentionally tolerated for now (each a known item to fix and remove over time). This keeps the check green while letting the codebase be cleaned up incrementally.CI
Wired into
script/lint.sh/.github/workflows/linter.ymlunderCHECK_GITHUB_OPENAPI, alongside the existingupdate-openapi --validatecheck. Documented inCONTRIBUTING.md.Ref #4319