Repository: googleapis/api-linter
Pull request #211 (2019-10-04 19:20:56)

[AIP-126] Require the first value to end in _UNSPECIFIED.


Patch:
diff --git a/rules/aip0126/aip0126.go b/rules/aip0126/aip0126.go
index a287300..c820b16 100644
--- a/rules/aip0126/aip0126.go
+++ b/rules/aip0126/aip0126.go
@@ -21,5 +21,6 @@ import "github.com/googleapis/api-linter/lint"
 func AddRules(r lint.RuleRegistry) {
 	r.Register(
 		enumValueUpperSnakeCase,
+		unspecified,
 	)
 }
diff --git a/rules/aip0126/unspecified.go b/rules/aip0126/unspecified.go
new file mode 100644
index 0000000..9d8834e
--- /dev/null
+++ b/rules/aip0126/unspecified.go
@@ -0,0 +1,43 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package aip0126
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/googleapis/api-linter/lint"
+	"github.com/jhump/protoreflect/desc"
+	"github.com/stoewer/go-strcase"
+)
+
+var unspecified = &lint.EnumRule{
+	Name: lint.NewRuleName("core", "0126", "unspecified"),
+	URI:  "https://aip.dev/126#guidance",
+	LintEnum: func(e *desc.EnumDescriptor) []lint.Problem {
+		firstValue := e.GetValues()[0]
+		want := strings.ToUpper(strcase.SnakeCase(e.GetName()) + "_UNSPECIFIED")
+		if firstValue.GetName() != want {
+			return []lint.Problem{{
+				Message:    fmt.Sprintf("The first enum value should be %q", want),
+				Suggestion: want,
+				Descriptor: firstValue,
+				Location:   lint.DescriptorNameLocation(firstValue),
+			}}
+		}
+
+		return nil
+	},
+}


Test patch:
diff --git a/rules/aip0126/unspecified_test.go b/rules/aip0126/unspecified_test.go
new file mode 100644
index 0000000..b974125
--- /dev/null
+++ b/rules/aip0126/unspecified_test.go
@@ -0,0 +1,52 @@
+// Copyright 2019 Google LLC
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+//     https://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+package aip0126
+
+import (
+	"testing"
+
+	"github.com/googleapis/api-linter/rules/internal/testutils"
+)
+
+func TestUnspecified(t *testing.T) {
+	tests := []struct {
+		testName  string
+		ValueName string
+		problems  testutils.Problems
+	}{
+		{"Valid", "BOOK_FORMAT_UNSPECIFIED", testutils.Problems{}},
+		{"InvalidNoPrefix", "UNSPECIFIED", testutils.Problems{{Suggestion: "BOOK_FORMAT_UNSPECIFIED"}}},
+		{"InvalidWrongSuffix", "BOOK_FORMAT_UNKNOWN", testutils.Problems{{Suggestion: "BOOK_FORMAT_UNSPECIFIED"}}},
+		{"InvalidJustWrong", "UNKNOWN", testutils.Problems{{Suggestion: "BOOK_FORMAT_UNSPECIFIED"}}},
+	}
+	for _, test := range tests {
+		t.Run(test.testName, func(t *testing.T) {
+			// Create the proto with the enum.
+			f := testutils.ParseProto3Tmpl(t, `
+				enum BookFormat {
+					{{.ValueName}} = 0;
+					HARDBACK = 1;
+					PAPERBACK = 2;
+				}
+			`, test)
+
+			// Run the lint rule and establish we get the correct problems.
+			problems := unspecified.Lint(f)
+			if diff := test.problems.SetDescriptor(f.GetEnumTypes()[0].GetValues()[0]).Diff(problems); diff != "" {
+				t.Errorf(diff)
+			}
+		})
+	}
+}