From a8bbf7cea0e858fb66a1d18b5a1ed6f36ca44441 Mon Sep 17 00:00:00 2001 From: Sam Woodard Date: Fri, 5 Aug 2022 08:41:36 -0700 Subject: [PATCH] feat(AIP-122): resource patterns must start with lowercase letter (#995) --- docs/rules/0122/resource-collection-identifiers.md | 14 +++++++++++++- rules/aip0122/resource_collection_identifiers.go | 11 +++++++++++ .../resource_collection_identifiers_test.go | 2 ++ 3 files changed, 26 insertions(+), 1 deletion(-) diff --git a/docs/rules/0122/resource-collection-identifiers.md b/docs/rules/0122/resource-collection-identifiers.md index ea9c08517..8eaac6664 100644 --- a/docs/rules/0122/resource-collection-identifiers.md +++ b/docs/rules/0122/resource-collection-identifiers.md @@ -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 @@ -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 diff --git a/rules/aip0122/resource_collection_identifiers.go b/rules/aip0122/resource_collection_identifiers.go index 2de9eafce..32655544b 100644 --- a/rules/aip0122/resource_collection_identifiers.go +++ b/rules/aip0122/resource_collection_identifiers.go @@ -15,6 +15,7 @@ package aip0122 import ( + "regexp" "strings" "unicode" @@ -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 { @@ -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. diff --git a/rules/aip0122/resource_collection_identifiers_test.go b/rules/aip0122/resource_collection_identifiers_test.go index 586773a75..9dc285129 100644 --- a/rules/aip0122/resource_collection_identifiers_test.go +++ b/rules/aip0122/resource_collection_identifiers_test.go @@ -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, `