Skip to content

Commit

Permalink
feat(AIP-122): resource patterns must start with lowercase letter (#995)
Browse files Browse the repository at this point in the history
  • Loading branch information
shwoodard authored Aug 5, 2022
1 parent 6c7cd9b commit a8bbf7c
Show file tree
Hide file tree
Showing 3 changed files with 26 additions and 1 deletion.
14 changes: 13 additions & 1 deletion docs/rules/0122/resource-collection-identifiers.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ in [AIP-122][].

This rule scans messages with a `google.api.resource` annotation, and validates
the format of `pattern` collection identifiers, specifically that they are in
lowerCamelCase form.
lowerCamelCase form and must start with a lowercase letter.

## Examples

Expand All @@ -36,6 +36,18 @@ message Book {
}
```

```proto
// Incorrect.
message Book {
option (google.api.resource) = {
type: "library.googleapis.com/Book"
// Collection identifiers must begin with a lower-cased letter.
pattern: "/publishers/{publisher}/Books/{book}"
};
string name = 1;
}
```

**Correct** code for this rule:

```proto
Expand Down
11 changes: 11 additions & 0 deletions rules/aip0122/resource_collection_identifiers.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
package aip0122

import (
"regexp"
"strings"
"unicode"

Expand All @@ -24,6 +25,8 @@ import (
"github.com/jhump/protoreflect/desc"
)

var firstCharRegexp = regexp.MustCompile(`^[a-z]`)

var resourceCollectionIdentifiers = &lint.MessageRule{
Name: lint.NewRuleName(122, "resource-collection-identifiers"),
OnlyIf: func(m *desc.MessageDescriptor) bool {
Expand All @@ -33,6 +36,14 @@ var resourceCollectionIdentifiers = &lint.MessageRule{
var problems []lint.Problem
resource := utils.GetResource(m)
for _, p := range resource.GetPattern() {
if !firstCharRegexp.MatchString(p) {
return append(problems, lint.Problem{
Message: "Resource patterns must start with a lowercase letter.",
Descriptor: m,
Location: locations.MessageResource(m),
})
}

segs := strings.Split(p, "/")
for _, seg := range segs {
// Get first rune of each pattern segment.
Expand Down
2 changes: 2 additions & 0 deletions rules/aip0122/resource_collection_identifiers_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@ func TestResourceCollectionIdentifiers(t *testing.T) {
}{
{"Valid", "author/{author}/books/{book}", testutils.Problems{}},
{"InvalidUpperCase", "author/{author}/Books/{book}", testutils.Problems{{Message: "lowerCamelCase"}}},
{"InvalidStartsWithSlash", "/author/{author}/Books/{book}", testutils.Problems{{Message: "lowercase letter"}}},
{"InvalidStartsWithCapitalLetter", "Author/{author}/Books/{book}", testutils.Problems{{Message: "lowercase letter"}}},
} {
t.Run(test.name, func(t *testing.T) {
f := testutils.ParseProto3Tmpl(t, `
Expand Down

0 comments on commit a8bbf7c

Please sign in to comment.