Skip to content

feat: Check request body fields against OpenAPI - #4576

Merged
gmlewis merged 5 commits into
google:masterfrom
gmlewis:schemafields
Sep 20, 2026
Merged

gmlewis merged 5 commits into
google:masterfrom
gmlewis:schemafields

Conversation

@gmlewis

@gmlewis gmlewis commented Sep 18, 2026

Copy link
Copy Markdown
Collaborator

Checking new PRs against the official GitHub v3 API for required fields in body parameters is extremely time-consuming, monotonous, and error-prone. This new tool helps contributors and maintainers alike by automating the mechanically-verifiable parts.

This PR adds tools/schemafields, run by script/check-schema-fields.sh and wired into script/lint.sh and the linter workflow. It checks each request body struct field against the request body schema of the operations that send it, mapping structs to operations via the //meta:operation annotation and the by-value body parameter that paramcheck requires, so new endpoints are covered with no new annotation.

No breaking API changes are made in this PR, but an upcoming PR will -fix the problems found.

AI assistance was used to write this PR.

Supersedes #4375 (closed), which required a //meta:schema annotation on every struct.
Relates to #3644 and #4023, and to the required-but-omittable bugs in #3336, #3226 and #2909.

cc: @stevehipwell - @Not-Dhananjay-Mishra

Signed-off-by: Glenn Lewis <6598971+gmlewis@users.noreply.github.com>
@codecov

codecov Bot commented Sep 18, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 98.59%. Comparing base (8d9197f) to head (97e80e9).
⚠️ Report is 2 commits behind head on master.

Additional details and impacted files
@@           Coverage Diff           @@
##           master    #4576   +/-   ##
=======================================
  Coverage   98.59%   98.59%           
=======================================
  Files         197      197           
  Lines       18326    18326           
=======================================
  Hits        18068    18068           
  Misses        258      258           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@gmlewis gmlewis added the NeedsReview PR is awaiting a review before merging. label Sep 18, 2026
Signed-off-by: Glenn Lewis <6598971+gmlewis@users.noreply.github.com>
Comment thread tools/schemafields/check.go Outdated
Comment thread tools/schemafields/scan.go
Comment thread tools/schemafields/check.go Outdated

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we use .yml/.yaml file instead of .txt

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The file is a flat one-key-per-line list plus a comment header that the tool preserves verbatim when it rewrites the file. YAML buys nothing here but costs a parser dependency (the module currently requires only go-cmp) and comment round-tripping (yaml.v3 drops comments, so -fix/-write-exceptions would lose the header). If the format ever needs per-entry metadata, we can change it. But for now, txt is the better choice, IMO.

Signed-off-by: Glenn Lewis <6598971+gmlewis@users.noreply.github.com>
@gmlewis

gmlewis commented Sep 19, 2026

Copy link
Copy Markdown
Collaborator Author

@Not-Dhananjay-Mishra - thank you for the review! I addressed two of your comments, and pushed back on the other two 😄. PTAL.

Comment thread tools/schemafields/check.go Outdated
Comment on lines +284 to +291
case f.isStruct:
// CONTRIBUTING.md prefers omitzero over a pointer for structs, and it is the
// only omit option that works on a struct value.
c.add(&diagnostic{
sev: sevWarn, rule: ruleOptionalValueType, file: f.file, line: f.line,
owner: si.name, field: f.goName, info: f, action: &fixAction{addOmit: "omitzero"},
message: fmt.Sprintf("%q is optional in the schema, but the Go field is a value type, so it is always sent: add omitzero", f.jsonName),
})

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think our structfield linter will not allow this kind of case, where a non-pointer struct uses omitzero.

type SomeRequest struct {
	OptionalStruct            Struct         `json:"optional_struct,omitzero"`
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the reason that all tests pass is because our linters are guarded by the "github" package name.

@Not-Dhananjay-Mishra Not-Dhananjay-Mishra Sep 20, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the reason that all tests pass is because our linters are guarded by the "github" package name.

linters are not running inside testdata/... so that why linter didn't caught it.

tools/schemafields/testdata/golden/TestFix/github/runner_groups.go

// StructTypeRequest has an optional property of a struct value type.
type StructTypeRequest struct {
	// Inner is optional and CONTRIBUTING.md asks for omitzero on structs.
	Inner InnerConfig `json:"inner,omitzero"`
}

I moved it outside temporary to test it, and linters start complaning.

    tools/schemafields/runner_groups.go:23:2: change the "ResponseOnly" field type to "*string" in the struct "CreateRunnerGroupRequest" because its json tag uses "omitempty" (structfield)
        ResponseOnly string `json:"response_only,omitempty"`
        ^
    tools/schemafields/runner_groups.go:47:2: change the "Inner" field type to "*InnerConfig" in the struct "StructTypeRequest" (structfield)
        Inner InnerConfig `json:"inner,omitzero"`

I believe that we should suggest %q is optional in the schema, but the Go field is a value type, so it is always sent: use pointer with omitempty

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are absolutely right, @Not-Dhananjay-Mishra!

I believe this is now fixed and I removed the 19 false-positives that were caused by this error.

PTAL.

Signed-off-by: Glenn Lewis <6598971+gmlewis@users.noreply.github.com>
Comment thread script/lint.sh Outdated
Comment on lines +99 to +106

print_header "Checking request body fields against the OpenAPI schemas"
if script/check-schema-fields.sh; then
printf "${GREEN}✔ request body fields match the OpenAPI schemas${NC}\n"
else
printf "${RED}✘ request body fields disagree with the OpenAPI schemas${NC}\n"
fail
fi

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When running script/lint.sh locally, this doesn't get triggered because it requires the CHECK_GITHUB_OPENAPI=1 environment variable. Is this intentional?

@gmlewis gmlewis Sep 20, 2026

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call! I've removed it.

Comment thread CONTRIBUTING.md
Comment on lines +665 to +668
The `tools/schemafields` package checks Go request body struct field
optionality against GitHub's OpenAPI request body schemas. It is run by
`script/check-schema-fields.sh`, which `script/lint.sh` and the `linter`
workflow call, so you rarely need to run it directly.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel that this statement

It is run by
script/check-schema-fields.sh, which script/lint.sh and the linter
workflow call, so you rarely need to run it directly.

feels inconsistent since contributors need to set the environment variable first then only they can run script/check-schema-fields.sh via script/lint.sh.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. Updated.

@Not-Dhananjay-Mishra Not-Dhananjay-Mishra left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one small doubt. Otherwise, LGTM 🫠

Signed-off-by: Glenn Lewis <6598971+gmlewis@users.noreply.github.com>
@gmlewis

gmlewis commented Sep 20, 2026

Copy link
Copy Markdown
Collaborator Author

Updated. Thank you, @Not-Dhananjay-Mishra!

@gmlewis
gmlewis merged commit f88a6d1 into google:master Sep 20, 2026
15 checks passed
@gmlewis
gmlewis deleted the schemafields branch September 20, 2026 18:18
@gmlewis gmlewis removed the NeedsReview PR is awaiting a review before merging. label Sep 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants