Repository: googleapis/api-linter
Pull request #242 (2019-10-17 22:26:39)

AIP-122: Resource names / HTTP url patterns
AIP-122: Resource names / HTTP url patterns


Patch:
diff --git a/rules/aip0122/aip0122.go b/rules/aip0122/aip0122.go
new file mode 100644
index 0000000..81d35e5
--- /dev/null
+++ b/rules/aip0122/aip0122.go
@@ -0,0 +1,28 @@
+// 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 aip0122 contains rules defined in https://aip.dev/122.
+package aip0122
+
+import (
+	"github.com/googleapis/api-linter/lint"
+)
+
+// AddRules accepts a register function and registers each of
+// this AIP's rules to it.
+func AddRules(r lint.RuleRegistry) {
+	r.Register(
+		camelCase,
+	)
+}
diff --git a/rules/aip0122/http_url_pattern.go b/rules/aip0122/http_url_pattern.go
new file mode 100644
index 0000000..b583818
--- /dev/null
+++ b/rules/aip0122/http_url_pattern.go
@@ -0,0 +1,41 @@
+// 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 aip0122
+
+import (
+	"strings"
+
+	"github.com/googleapis/api-linter/lint"
+	"github.com/googleapis/api-linter/rules/internal/utils"
+	"github.com/jhump/protoreflect/desc"
+)
+
+// HTTP URL pattern shouldn't include underscore("_")
+var camelCase = &lint.MethodRule{
+	Name: lint.NewRuleName("core", "0122", "http-url-pattern"),
+	URI:  "https://aip.dev/122#guidance",
+	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
+		// Establish that the URI does not include a `_` character.
+		for _, httpRule := range utils.GetHTTPRules(m) {
+			if strings.Contains(httpRule.URI, "_") {
+				return []lint.Problem{{
+					Message:    "HTTP URL should use camel case, but not snake case.",
+					Descriptor: m,
+				}}
+			}
+		}
+		return nil
+	},
+}


Test patch:
diff --git a/rules/aip0122/aip0122_test.go b/rules/aip0122/aip0122_test.go
new file mode 100644
index 0000000..ae83714
--- /dev/null
+++ b/rules/aip0122/aip0122_test.go
@@ -0,0 +1,32 @@
+// 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 aip0122
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/googleapis/api-linter/lint"
+)
+
+func TestAddRules(t *testing.T) {
+	rules := make(lint.RuleRegistry)
+	AddRules(rules)
+	for ruleName := range rules {
+		if !strings.HasPrefix(string(ruleName), "core::0122") {
+			t.Errorf("Rule %s is not namespaced to core::0122.", ruleName)
+		}
+	}
+}
diff --git a/rules/aip0122/http_url_pattern_test.go b/rules/aip0122/http_url_pattern_test.go
new file mode 100644
index 0000000..0268d9e
--- /dev/null
+++ b/rules/aip0122/http_url_pattern_test.go
@@ -0,0 +1,185 @@
+// 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 aip0122
+
+import (
+	"testing"
+
+	"github.com/googleapis/api-linter/rules/internal/testutils"
+)
+
+func TestHttpUriField(t *testing.T) {
+	tests := []struct {
+		testName string
+		src      string
+		problems testutils.Problems
+	}{
+		{
+			testName: "Valid",
+			src: `import "google/api/annotations.proto";
+
+service InstanceGroupService {
+	rpc CreateInstanceGroup(CreateInstanceGroupsRequest) returns (InstanceGroup) {
+		option (google.api.http) = {
+			post:"/v1/{parent=publishers/*/instanceGroups/*}"
+			body:"instanceGroup"
+		};
+	}
+}
+
+message CreateInstanceGroupsRequest {}
+
+message InstanceGroup{}
+`,
+			problems: nil,
+		},
+		{
+			testName: "InValid-Get",
+			src: `import "google/api/annotations.proto";
+
+service InstanceGroupService {
+	rpc GetInstanceGroup(GetInstanceGroupsRequest) returns (InstanceGroup) {
+	 option (google.api.http) = {
+		 get: "/v1/{name=publishers/*/instance_groups/*}"
+	 };
+	}
+}
+
+message GetInstanceGroupsRequest {}
+
+message InstanceGroup{}
+`,
+			problems: testutils.Problems{{
+				Message: "HTTP URL should use camel case, but not snake case.",
+			}},
+		},
+		{
+			testName: "InValid-Create",
+			src: `import "google/api/annotations.proto";
+
+service InstanceGroupService {
+	rpc CreateInstanceGroup(CreateInstanceGroupsRequest) returns (InstanceGroup) {
+	 option (google.api.http) = {
+		 post: "/v1/{parent=publishers/*/instance_groups/*}"
+		 body: "instanceGroup"
+	 };
+	}
+}
+
+message CreateInstanceGroupsRequest {}
+
+message InstanceGroup{}
+`,
+			problems: testutils.Problems{{
+				Message: "HTTP URL should use camel case, but not snake case.",
+			}},
+		},
+		{
+			testName: "InValid-Delete",
+			//addionalImport:`import "google/protobuf/empty.proto";`,
+			src: `import "google/api/annotations.proto";
+import "google/protobuf/empty.proto";
+
+service InstanceGroupService {
+	rpc DeleteInstanceGroup(DeleteInstanceGroupsRequest) returns (google.protobuf.Empty) {
+	 option (google.api.http) = {
+		 delete: "/v1/{name=publishers/*/instance_groups/*}"
+	 };
+	}
+}
+
+message DeleteInstanceGroupsRequest {}
+`,
+			problems: testutils.Problems{{
+				Message: "HTTP URL should use camel case, but not snake case.",
+			}},
+		},
+		{
+			testName: "InValid-Update_Patch",
+			src: `import "google/api/annotations.proto";
+
+service InstanceGroupService {
+	rpc UpdateInstanceGroup(UpdateInstanceGroupsRequest) returns (InstanceGroup) {
+	 option (google.api.http) = {
+		 patch: "/v1/{book.name=publishers/*/instance_groups/*}"
+		 body: "instanceGroup"
+	 };
+	}
+}
+
+message UpdateInstanceGroupsRequest {}
+
+message InstanceGroup{}
+`,
+			problems: testutils.Problems{{
+				Message: "HTTP URL should use camel case, but not snake case.",
+			}},
+		},
+		{
+			testName: "InValid-Update_Put",
+			src: `import "google/api/annotations.proto";
+
+service InstanceGroupService {
+	rpc UpdateInstanceGroup(UpdateInstanceGroupsRequest) returns (InstanceGroup) {
+	 option (google.api.http) = {
+		 put: "/v1/{book.name=publishers/*/instance_groups/*}"
+		 body: "book"
+	 };
+	}
+}
+
+message UpdateInstanceGroupsRequest {}
+
+message InstanceGroup{}
+`,
+			problems: testutils.Problems{{
+				Message: "HTTP URL should use camel case, but not snake case.",
+			}},
+		},
+		{
+
+			testName: "InValid-Custom",
+			src: `import "google/api/annotations.proto";
+
+service InstanceGroupService {
+	rpc MoveInstanceGroup(MoveInstanceGroupsRequest) returns (InstanceGroup) {
+	 option (google.api.http) = {
+			custom: { path: "/v1/{book.name=publishers/*/instance_groups/*}" }
+	 };
+	}
+}
+
+message MoveInstanceGroupsRequest {}
+
+message InstanceGroup{}
+`,
+			problems: testutils.Problems{{
+				Message: "HTTP URL should use camel case, but not snake case.",
+			}},
+		},
+	}
+
+	for _, test := range tests {
+		t.Run(test.testName, func(t *testing.T) {
+			file := testutils.ParseProto3String(t, test.src)
+
+			f := file.GetServices()[0].GetMethods()[0]
+			problems := camelCase.Lint(file)
+			if diff := test.problems.SetDescriptor(f).Diff(problems); diff != "" {
+				t.Errorf(diff)
+			}
+		})
+	}
+}