Skip to content

fix: Respect google.api.resource singular annotation in batch method rules#1629

Open
stevenwarejones wants to merge 4 commits into
googleapis:mainfrom
stevenwarejones:fix/batch-resource-singular-annotation
Open

fix: Respect google.api.resource singular annotation in batch method rules#1629
stevenwarejones wants to merge 4 commits into
googleapis:mainfrom
stevenwarejones:fix/batch-resource-singular-annotation

Conversation

@stevenwarejones
Copy link
Copy Markdown

@stevenwarejones stevenwarejones commented May 13, 2026

Summary

Batch method rules (AIP-0231, 0233, 0234, 0235) for request-requests-field, request-names-field, and response-resource-field used go-pluralize to singularize the resource name extracted from batch message names. This produced incorrect results for words like "Metadata", which go-pluralize singularizes to "Metadatum" using Latin grammar rules.

This PR adds a ResourceSingular() helper that first checks for a google.api.resource annotation with a singular field on sibling or imported resource messages before falling back to go-pluralize. This is analogous to the fix in #1573 for the plural-method-name rule.

When no google.api.resource annotation is found, the function falls back to go-pluralize, preserving existing behavior for all other resources.

Affected Rules

  • core::0231::request-names-field
  • core::0231::response-resource-field
  • core::0233::request-requests-field
  • core::0233::response-resource-field
  • core::0234::request-requests-field
  • core::0234::response-resource-field
  • core::0235::request-names-field
  • core::0235::response-resource-field

Changes

  • rules/internal/utils/string_pluralize.go: Add ResourceSingular() function that searches messages in the current file and directly imported files for a google.api.resource annotation with a matching singular value, converting it to UpperCamelCase. Falls back to go-pluralize if no annotation is found.
  • 8 rule files across AIP-0231, 0233, 0234, 0235: Replace pluralize.NewClient().Singular() calls with utils.ResourceSingular().
  • AIP-0234 tests: Add test cases for ImpressionMetadata with google.api.resource annotation verifying both request-requests-field and response-resource-field.

Test plan

  • Added resource annotation tests for all 8 affected rules across AIP-0231, 0233, 0234, and 0235
  • Added unit tests for ResourceSingular() covering: annotation in same file, annotation in imported file, fallback to go-pluralize, uncountable nouns, message name matching, and no-annotation Latin words
  • Added multi-file import test (TestResourceSingularImportedFile) exercising the cross-file import search path
  • go test ./... passes (56 packages, 0 failures)
  • golangci-lint run ./... reports 0 issues
  • Verified against cross-media-measurement protos: removing all 6 Metadatum disable comments produces zero false positives with the fixed linter

Issue: #1628

Batch method rules (AIP-0231, 0233, 0234, 0235) for request-requests-field,
request-names-field, and response-resource-field used go-pluralize to
singularize the resource name extracted from batch message names. This
produced incorrect results for words like "Metadata", which go-pluralize
singularizes to "Metadatum" using Latin grammar rules.

Add ResourceSingular() helper that first checks for a google.api.resource
annotation with a singular field on sibling or imported resource messages
before falling back to go-pluralize. This is analogous to the fix in googleapis#1573
for the plural-method-name rule.

Issue: googleapis#1566
@stevenwarejones stevenwarejones requested a review from a team as a code owner May 13, 2026 23:03
@google-cla
Copy link
Copy Markdown

google-cla Bot commented May 13, 2026

Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

View this failed invocation of the CLA check for more information.

For the most up to date status, view the checks section at the bottom of the pull request.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist Bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request replaces the go-pluralize library with a custom ResourceSingular utility across AIP rules 0231, 0233, 0234, and 0235 to correctly handle resource singularization by checking google.api.resource annotations. This change prevents incorrect singularization of words like "Metadata" and includes new test cases for verification. Feedback includes a suggestion to use consistent string trimming methods across the packages and a recommendation to implement recursive import searching to ensure resource annotations are found in complex proto hierarchies.

// The singular form the resource message name; the first letter capitalized.
plural := strings.TrimSuffix(strings.TrimPrefix(string(m.Name()), "BatchGet"), "Response")
resourceMsgName := pluralize.NewClient().Singular(plural)
pluralName := strings.TrimSuffix(strings.TrimPrefix(string(m.Name()), "BatchGet"), "Response")
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.

medium

For consistency with other rules in this PR (including the request rule in this package), consider using TrimPrefix on the result of TrimSuffix.

Suggested change
pluralName := strings.TrimSuffix(strings.TrimPrefix(string(m.Name()), "BatchGet"), "Response")
pluralName := strings.TrimPrefix(strings.TrimSuffix(string(m.Name()), "Response"), "BatchGet")

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Fixed in 9c9e3a5 — reordered to TrimPrefix(TrimSuffix(...)) to match all other rules.

Comment on lines +60 to +65
imports := f.Imports()
for i := 0; i < imports.Len(); i++ {
if s := findResourceSingularInFile(pluralName, imports.Get(i).FileDescriptor); s != "" {
return s
}
}
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.

medium

The current implementation only searches direct imports for the resource annotation. In complex proto structures, the resource might be defined in a file that is imported transitively. Consider implementing a recursive search to ensure the resource annotation is found even if it's not in a directly imported file.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Good callout. Intentionally keeping this as direct imports only — in standard proto layouts the resource message is in a sibling file directly imported by the service file, so single-depth is sufficient. Recursive search would add complexity for a case we haven't seen in practice. Updated the comment to make this intentional, and added a multi-file test (TestResourceSingularImportedFile) that exercises the import search path. See 9c9e3a5.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

On reflection, agreed — implemented recursive search with cycle detection via a visited set in 027891b. Also added that exercises a 3-file chain (service.proto → common.proto → resource.proto) to verify the annotation is found through transitive imports.

@stevenwarejones stevenwarejones changed the title fix: Use google.api.resource singular for batch method field type checks fix: Respect google.api.resource singular annotation in batch method rules May 14, 2026
… lookup

Add test cases across all 8 modified batch rules (AIP-0231, 0233, 0234,
0235) and the ResourceSingular utility to verify that google.api.resource
singular annotations are respected. Uses ImpressionMetadata as the test
resource since go-pluralize incorrectly singularizes it to Metadatum.

Also fix copyright year in string_pluralize.go to 2026 for new code.

Issue: googleapis#1628
… docs

- Make TrimPrefix/TrimSuffix ordering consistent in aip0231
  response_resource_field.go (was TrimSuffix(TrimPrefix(...)), now
  TrimPrefix(TrimSuffix(...)) like all other rules)
- Clarify that single-depth import search is intentional and sufficient
  for standard proto layouts
- Add TestResourceSingularImportedFile exercising the cross-file import
  search path using ParseProtoStrings with separate resource.proto and
  service.proto files

Issue: googleapis#1628
Copy link
Copy Markdown
Contributor

@noahdietz noahdietz left a comment

Choose a reason for hiding this comment

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

Hi there, thanks for the extensive improvement. Couple of comments.

//
// This avoids incorrect singularization of words like "Metadata" (which
// go-pluralize converts to "Metadatum" using Latin grammar rules).
func ResourceSingular(pluralName string, m protoreflect.MessageDescriptor) string {
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.

Let's use a different function name as I think this is a bit misleading alongside other utilities like GetResourceSingular (which arguably should've been named ResourceSingular).

This might be something like DeriveResourceSingular as it tries to find a resource from a derived name and then get the singular.

Comment on lines +219 to +223
m := serviceFile.Messages().Get(0)
got := ResourceSingular("ImpressionMetadata", m)
if got != "ImpressionMetadata" {
t.Errorf("ResourceSingular(\"ImpressionMetadata\") = %q, want \"ImpressionMetadata\"", got)
}
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.

Please make "ImpressionMetadata" a variable want, then use that throughout and change the failure message from using \"ImpressionMetadata\" to %q

Here and everywhere similar

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