Repository: googleapis/api-linter
Pull request #275 (2019-11-01 18:55:37)

[AIP-131] Document method rules.


Patch:
diff --git a/docs/rules/core/0131-http-body.md b/docs/rules/core/0131-http-body.md
new file mode 100644
index 0000000..1b1d37e
--- /dev/null
+++ b/docs/rules/core/0131-http-body.md
@@ -0,0 +1,68 @@
+---
+rule:
+  aip: 131
+  name: [core, '0131', http-body]
+  summary: Get methods must not have an HTTP body.
+---
+
+# Get methods: Request message
+
+This rule enforces that all `Get*` RPCs omit the HTTP `body`, as mandated in
+[AIP-131][].
+
+## Details
+
+This rule looks at any message matching beginning with `Get`, and complains if
+the HTTP `body` field is set.
+
+## Examples
+
+**Incorrect** code for this rule:
+
+```proto
+// Incorrect.
+rpc GetBook(GetBookRequest) returns (Book) {
+  option (google.api.http) = {
+    get: "/v1/{name=publishers/*/books/*}"
+    body: "*"  // This should be absent.
+  }
+}
+```
+
+**Correct** code for this rule:
+
+```proto
+// Correct.
+rpc GetBook(GetBookRequest) returns (Book) {
+  option (google.api.http) = {
+    get: "/v1/{name=publishers/*/books/*}"
+  }
+}
+```
+
+## Disabling
+
+If you need to violate this rule, use a leading comment above the method.
+Remember to also include an [aip.dev/not-precedent][] comment explaining why.
+
+```proto
+// (-- api-linter: core::0131::http-body=disabled
+//     api-linter: core::0131::http-method=disabled
+//     aip.dev/not-precedent: We need to do this because reasons. --)
+rpc GetBook(GetBookRequest) returns (Book) {
+  option (google.api.http) = {
+    post: "/v1/{name=publishers/*/books/*}"
+    body: "*"
+  }
+}
+```
+
+**Important:** HTTP `GET` requests are unable to have an HTTP body, due to the
+nature of the protocol. The only valid way to include a body is to also use a
+different HTTP method (as depicted above).
+
+If you need to violate this rule for an entire file, place the comment at the
+top of the file.
+
+[aip-131]: https://aip.dev/131
+[aip.dev/not-precedent]: https://aip.dev/not-precedent
diff --git a/docs/rules/core/0131-http-method.md b/docs/rules/core/0131-http-method.md
new file mode 100644
index 0000000..f04cb42
--- /dev/null
+++ b/docs/rules/core/0131-http-method.md
@@ -0,0 +1,62 @@
+---
+rule:
+  aip: 131
+  name: [core, '0131', http-method]
+  summary: Get methods must use the GET HTTP verb.
+---
+
+# Get methods: Request message
+
+This rule enforces that all `Get*` RPCs use the `GET` HTTP verb, as mandated in
+[AIP-131][].
+
+## Details
+
+This rule looks at any message matching beginning with `Get`, and complains if
+the HTTP verb is anything other than `GET`. It _does_ check additional bindings
+if they are present.
+
+## Examples
+
+**Incorrect** code for this rule:
+
+```proto
+// Incorrect.
+rpc GetBook(GetBookRequest) returns (Book) {
+  option (google.api.http) = {
+    post: "/v1/{name=publishers/*/books/*}"  // Should be `get:`.
+  }
+}
+```
+
+**Correct** code for this rule:
+
+```proto
+// Correct.
+rpc GetBook(GetBookRequest) returns (Book) {
+  option (google.api.http) = {
+    get: "/v1/{name=publishers/*/books/*}"
+  }
+}
+```
+
+## Disabling
+
+If you need to violate this rule, use a leading comment above the method.
+Remember to also include an [aip.dev/not-precedent][] comment explaining why.
+
+```proto
+// (-- api-linter: core::0131::http-method=disabled
+//     aip.dev/not-precedent: We need to do this because reasons. --)
+rpc GetBook(GetBookRequest) returns (Book) {
+  option (google.api.http) = {
+    post: "/v1/{name=publishers/*/books/*}"
+  }
+}
+```
+
+If you need to violate this rule for an entire file, place the comment at the
+top of the file.
+
+[aip-131]: https://aip.dev/131
+[aip.dev/not-precedent]: https://aip.dev/not-precedent
diff --git a/docs/rules/core/0131-http-uri-name.md b/docs/rules/core/0131-http-uri-name.md
new file mode 100644
index 0000000..5642f3f
--- /dev/null
+++ b/docs/rules/core/0131-http-uri-name.md
@@ -0,0 +1,62 @@
+---
+rule:
+  aip: 131
+  name: [core, '0131', http-uri-name]
+  summary: Get methods must map the name field to the URI.
+---
+
+# Get methods: Request message
+
+This rule enforces that all `Get*` RPCs map the `name` field to the HTTP URI,
+as mandated in [AIP-131][].
+
+## Details
+
+This rule looks at any message matching beginning with `Get`, and complains if
+the `name` variable is not included in the URI. It _does_ check additional
+bindings if they are present.
+
+## Examples
+
+**Incorrect** code for this rule:
+
+```proto
+// Incorrect.
+rpc GetBook(GetBookRequest) returns (Book) {
+  option (google.api.http) = {
+    get: "/v1/publishers/*/books/*"  // The `name` field should be extracted.
+  }
+}
+```
+
+**Correct** code for this rule:
+
+```proto
+// Correct.
+rpc GetBook(GetBookRequest) returns (Book) {
+  option (google.api.http) = {
+    get: "/v1/{name=publishers/*/books/*}"
+  }
+}
+```
+
+## Disabling
+
+If you need to violate this rule, use a leading comment above the method.
+Remember to also include an [aip.dev/not-precedent][] comment explaining why.
+
+```proto
+// (-- api-linter: core::0131::http-method=disabled
+//     aip.dev/not-precedent: We need to do this because reasons. --)
+rpc GetBook(GetBookRequest) returns (Book) {
+  option (google.api.http) = {
+    get: "/v1/publishers/*/books/*"
+  }
+}
+```
+
+If you need to violate this rule for an entire file, place the comment at the
+top of the file.
+
+[aip-131]: https://aip.dev/131
+[aip.dev/not-precedent]: https://aip.dev/not-precedent
diff --git a/docs/rules/core/0131-request-message-name.md b/docs/rules/core/0131-request-message-name.md
new file mode 100644
index 0000000..7b3e63e
--- /dev/null
+++ b/docs/rules/core/0131-request-message-name.md
@@ -0,0 +1,62 @@
+---
+rule:
+  aip: 131
+  name: [core, '0131', request-message-name]
+  summary: Get methods must have standardized request message names.
+---
+
+# Get methods: Request message
+
+This rule enforces that all `Get*` RPCs have a request message name of
+`Get*Request`, as mandated in [AIP-131][].
+
+## Details
+
+This rule looks at any message matching beginning with `Get`, and complains if
+the name of the corresponding input message does not match the name of the RPC
+with the suffix `Request` appended.
+
+## Examples
+
+**Incorrect** code for this rule:
+
+```proto
+// Incorrect.
+rpc GetBook(GetBookReq) returns (Book) {  // Should be `GetBookRequest`.
+  option (google.api.http) = {
+    get: "/v1/{name=publishers/*/books/*}"
+  }
+}
+```
+
+**Correct** code for this rule:
+
+```proto
+// Correct.
+rpc GetBook(GetBookRequest) returns (Book) {
+  option (google.api.http) = {
+    get: "/v1/{name=publishers/*/books/*}"
+  }
+}
+```
+
+## Disabling
+
+If you need to violate this rule, use a leading comment above the method.
+Remember to also include an [aip.dev/not-precedent][] comment explaining why.
+
+```proto
+// (-- api-linter: core::0131::request-message-name=disabled
+//     aip.dev/not-precedent: We need to do this because reasons. --)
+rpc GetBook(GetBookReq) returns (Book) {
+  option (google.api.http) = {
+    get: "/v1/{name=publishers/*/books/*}"
+  }
+}
+```
+
+If you need to violate this rule for an entire file, place the comment at the
+top of the file.
+
+[aip-131]: https://aip.dev/131
+[aip.dev/not-precedent]: https://aip.dev/not-precedent
diff --git a/docs/rules/core/0131-response-message-name.md b/docs/rules/core/0131-response-message-name.md
new file mode 100644
index 0000000..c0243a3
--- /dev/null
+++ b/docs/rules/core/0131-response-message-name.md
@@ -0,0 +1,62 @@
+---
+rule:
+  aip: 131
+  name: [core, '0131', response-message-name]
+  summary: Get methods must return the resource.
+---
+
+# Upper snake case values
+
+This rule enforces that all `Get*` RPCs have a response message of the
+resource, as mandated in [AIP-131][].
+
+## Details
+
+This rule looks at any message matching beginning with `Get`, and complains if
+the name of the corresponding output message does not match the name of the RPC
+with the prefix `Get` removed.
+
+## Examples
+
+**Incorrect** code for this rule:
+
+```proto
+// Incorrect.
+rpc GetBook(GetBookRequest) returns (GetBookResponse) {  // Should be `Book`.
+  option (google.api.http) = {
+    get: "/v1/{name=publishers/*/books/*}"
+  }
+}
+```
+
+**Correct** code for this rule:
+
+```proto
+// Correct.
+rpc GetBook(GetBookRequest) returns (Book) {
+  option (google.api.http) = {
+    get: "/v1/{name=publishers/*/books/*}"
+  }
+}
+```
+
+## Disabling
+
+If you need to violate this rule, use a leading comment above the method.
+Remember to also include an [aip.dev/not-precedent][] comment explaining why.
+
+```proto
+// (-- api-linter: core::0131::response-message-name=disabled
+//     aip.dev/not-precedent: We need to do this because reasons. --)
+rpc GetBook(GetBookRequest) returns (GetBookResponse) {
+  option (google.api.http) = {
+    get: "/v1/{name=publishers/*/books/*}"
+  }
+}
+```
+
+If you need to violate this rule for an entire file, place the comment at the
+top of the file.
+
+[aip-131]: https://aip.dev/131
+[aip.dev/not-precedent]: https://aip.dev/not-precedent
diff --git a/docs/rules/core/0131-synonyms.md b/docs/rules/core/0131-synonyms.md
new file mode 100644
index 0000000..e9a24e8
--- /dev/null
+++ b/docs/rules/core/0131-synonyms.md
@@ -0,0 +1,67 @@
+---
+rule:
+  aip: 131
+  name: [core, '0131', synonyms]
+  summary: Get methods must be named starting with "Get"
+---
+
+# Get methods: Request message
+
+This rule enforces that single-resource lookup methods are named `Get*`, as
+mandated in [AIP-131][].
+
+## Details
+
+This rule looks at any message with names similar to `Get`, and suggests using
+`Get` instead. It complains if it sees the following synonyms:
+
+- Acquire
+- Fetch
+- Lookup
+- Read
+- Retrieve
+
+## Examples
+
+**Incorrect** code for this rule:
+
+```proto
+// Incorrect.
+rpc FetchBook(FetchBookRequest) returns (Book) {  // Should be `GetBook`.
+  option (google.api.http) = {
+    get: "/v1/{name=publishers/*/books/*}"
+  }
+}
+```
+
+**Correct** code for this rule:
+
+```proto
+// Correct.
+rpc GetBook(GetBookRequest) returns (Book) {
+  option (google.api.http) = {
+    get: "/v1/{name=publishers/*/books/*}"
+  }
+}
+```
+
+## Disabling
+
+If you need to violate this rule, use a leading comment above the method.
+Remember to also include an [aip.dev/not-precedent][] comment explaining why.
+
+```proto
+// (-- api-linter: core::0131::synonyms=disabled
+//     aip.dev/not-precedent: We need to do this because reasons. --)
+rpc FetchBook(GetBookReq) returns (Book) {
+  option (google.api.http) = {
+    get: "/v1/{name=publishers/*/books/*}"
+  }
+}
+```
+
+If you need to violate this rule for an entire file, place the comment at the
+top of the file.
+
+[aip-131]: https://aip.dev/131
+[aip.dev/not-precedent]: https://aip.dev/not-precedent
diff --git a/rules/aip0131/http_body.go b/rules/aip0131/http_body.go
new file mode 100644
index 0000000..4a03b3c
--- /dev/null
+++ b/rules/aip0131/http_body.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 aip0131
+
+import (
+	"github.com/googleapis/api-linter/lint"
+	"github.com/googleapis/api-linter/rules/internal/utils"
+	"github.com/jhump/protoreflect/desc"
+)
+
+// Get methods should not have an HTTP body.
+var httpBody = &lint.MethodRule{
+	Name:   lint.NewRuleName("core", "0131", "http-body"),
+	URI:    "https://aip.dev/131#guidance",
+	OnlyIf: isGetMethod,
+	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
+		// Establish that the RPC has no HTTP body.
+		for _, httpRule := range utils.GetHTTPRules(m) {
+			if httpRule.Body != "" {
+				return []lint.Problem{{
+					Message:    "Get methods should not have an HTTP body.",
+					Descriptor: m,
+				}}
+			}
+		}
+
+		return nil
+	},
+}
diff --git a/rules/aip0131/http_method.go b/rules/aip0131/http_method.go
new file mode 100644
index 0000000..adb556f
--- /dev/null
+++ b/rules/aip0131/http_method.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 aip0131
+
+import (
+	"github.com/googleapis/api-linter/lint"
+	"github.com/googleapis/api-linter/rules/internal/utils"
+	"github.com/jhump/protoreflect/desc"
+)
+
+// Get methods should use the HTTP GET verb.
+var httpMethod = &lint.MethodRule{
+	Name:   lint.NewRuleName("core", "0131", "http-method"),
+	URI:    "https://aip.dev/131#guidance",
+	OnlyIf: isGetMethod,
+	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
+		// Rule check: Establish that the RPC uses HTTP GET.
+		for _, httpRule := range utils.GetHTTPRules(m) {
+			if httpRule.Method != "GET" {
+				return []lint.Problem{{
+					Message:    "Get methods must use the HTTP GET verb.",
+					Descriptor: m,
+				}}
+			}
+		}
+
+		return nil
+	},
+}
diff --git a/rules/aip0131/http_uri_name.go b/rules/aip0131/http_uri_name.go
new file mode 100644
index 0000000..7aede05
--- /dev/null
+++ b/rules/aip0131/http_uri_name.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 aip0131
+
+import (
+	"github.com/googleapis/api-linter/lint"
+	"github.com/googleapis/api-linter/rules/internal/utils"
+	"github.com/jhump/protoreflect/desc"
+)
+
+// Get methods should have a proper HTTP pattern.
+var httpNameField = &lint.MethodRule{
+	Name:   lint.NewRuleName("core", "0131", "http-uri-name"),
+	URI:    "https://aip.dev/131#guidance",
+	OnlyIf: isGetMethod,
+	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
+		// Establish that the RPC has no HTTP body.
+		for _, httpRule := range utils.GetHTTPRules(m) {
+			if !getURINameRegexp.MatchString(httpRule.URI) {
+				return []lint.Problem{{
+					Message:    "Get methods should include the `name` field in the URI.",
+					Descriptor: m,
+				}}
+			}
+		}
+
+		return nil
+	},
+}
diff --git a/rules/aip0131/method_rules.go b/rules/aip0131/method_rules.go
deleted file mode 100644
index a1e4372..0000000
--- a/rules/aip0131/method_rules.go
+++ /dev/null
@@ -1,153 +0,0 @@
-// 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 aip0131
-
-import (
-	"fmt"
-	"strings"
-
-	"github.com/googleapis/api-linter/lint"
-	"github.com/googleapis/api-linter/rules/internal/utils"
-	"github.com/jhump/protoreflect/desc"
-)
-
-// Get messages should have a properly named Request message.
-var requestMessageName = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0131", "request-message", "name"),
-	URI:    "https://aip.dev/131#guidance",
-	OnlyIf: isGetMethod,
-	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
-		// Rule check: Establish that for methods such as `GetFoo`, the request
-		// message is named `GetFooRequest`.
-		if got, want := m.GetInputType().GetName(), m.GetName()+"Request"; got != want {
-			return []lint.Problem{{
-				Message: fmt.Sprintf(
-					"Get RPCs should have a request message named after the RPC, such as %q.",
-					want,
-				),
-				Suggestion: want,
-				Descriptor: m,
-			}}
-		}
-
-		return nil
-	},
-}
-
-// Get messages should use the resource as the response message
-var responseMessageName = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0131", "response-message", "name"),
-	URI:    "https://aip.dev/131#guidance",
-	OnlyIf: isGetMethod,
-	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
-		// Rule check: Establish that for methods such as `GetFoo`, the response
-		// message is named `Foo`.
-		if got, want := m.GetOutputType().GetName(), m.GetName()[3:]; got != want {
-			return []lint.Problem{{
-				Message: fmt.Sprintf(
-					"Get RPCs should have the corresponding resource as the response message, such as %q.",
-					want,
-				),
-				Suggestion: want,
-				Descriptor: m,
-			}}
-		}
-
-		return nil
-	},
-}
-
-// Get methods should use the HTTP GET verb.
-var httpMethod = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0131", "http-method"),
-	URI:    "https://aip.dev/131#guidance",
-	OnlyIf: isGetMethod,
-	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
-		// Rule check: Establish that the RPC uses HTTP GET.
-		for _, httpRule := range utils.GetHTTPRules(m) {
-			if httpRule.Method != "GET" {
-				return []lint.Problem{{
-					Message:    "Get methods must use the HTTP GET verb.",
-					Descriptor: m,
-				}}
-			}
-		}
-
-		return nil
-	},
-}
-
-// Get methods should have a proper HTTP pattern.
-var httpNameField = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0131", "http-name"),
-	URI:    "https://aip.dev/131#guidance",
-	OnlyIf: isGetMethod,
-	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
-		// Establish that the RPC has no HTTP body.
-		for _, httpRule := range utils.GetHTTPRules(m) {
-			if !getURINameRegexp.MatchString(httpRule.URI) {
-				return []lint.Problem{{
-					Message:    "Get methods should include the `name` field in the URI.",
-					Descriptor: m,
-				}}
-			}
-		}
-
-		return nil
-	},
-}
-
-// Get methods should not have an HTTP body.
-var httpBody = &lint.MethodRule{
-	Name:   lint.NewRuleName("core", "0131", "http-body"),
-	URI:    "https://aip.dev/131#guidance",
-	OnlyIf: isGetMethod,
-	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
-		// Establish that the RPC has no HTTP body.
-		for _, httpRule := range utils.GetHTTPRules(m) {
-			if httpRule.Body != "" {
-				return []lint.Problem{{
-					Message:    "Get methods should not have an HTTP body.",
-					Descriptor: m,
-				}}
-			}
-		}
-
-		return nil
-	},
-}
-
-// Get methods should not generally use synonyms for "get".
-var synonyms = &lint.MethodRule{
-	Name: lint.NewRuleName("core", "0131", "synonyms"),
-	URI:  "https://aip.dev/131#guidance",
-	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
-		name := m.GetName()
-		for _, syn := range []string{"Acquire", "Fetch", "Lookup", "Read", "Retrieve"} {
-			if strings.HasPrefix(name, syn) {
-				return []lint.Problem{{
-					Message: fmt.Sprintf(
-						`%q can be a synonym for "Get". Should this be a Get method?`,
-						syn,
-					),
-					Suggestion: strings.Replace(name, syn, "Get", 1),
-					Descriptor: m,
-					Location:   lint.DescriptorNameLocation(m),
-				}}
-			}
-		}
-		return nil
-	},
-}
diff --git a/rules/aip0131/request_message_name.go b/rules/aip0131/request_message_name.go
new file mode 100644
index 0000000..f4c4ad8
--- /dev/null
+++ b/rules/aip0131/request_message_name.go
@@ -0,0 +1,45 @@
+// 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 aip0131
+
+import (
+	"fmt"
+
+	"github.com/googleapis/api-linter/lint"
+	"github.com/jhump/protoreflect/desc"
+)
+
+// Get messages should have a properly named Request message.
+var requestMessageName = &lint.MethodRule{
+	Name:   lint.NewRuleName("core", "0131", "request-message-name"),
+	URI:    "https://aip.dev/131#guidance",
+	OnlyIf: isGetMethod,
+	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
+		// Rule check: Establish that for methods such as `GetFoo`, the request
+		// message is named `GetFooRequest`.
+		if got, want := m.GetInputType().GetName(), m.GetName()+"Request"; got != want {
+			return []lint.Problem{{
+				Message: fmt.Sprintf(
+					"Get RPCs should have a request message named after the RPC, such as %q.",
+					want,
+				),
+				Suggestion: want,
+				Descriptor: m,
+			}}
+		}
+
+		return nil
+	},
+}
diff --git a/rules/aip0131/response_message_name.go b/rules/aip0131/response_message_name.go
new file mode 100644
index 0000000..37f9c66
--- /dev/null
+++ b/rules/aip0131/response_message_name.go
@@ -0,0 +1,45 @@
+// 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 aip0131
+
+import (
+	"fmt"
+
+	"github.com/googleapis/api-linter/lint"
+	"github.com/jhump/protoreflect/desc"
+)
+
+// Get messages should use the resource as the response message
+var responseMessageName = &lint.MethodRule{
+	Name:   lint.NewRuleName("core", "0131", "response-message-name"),
+	URI:    "https://aip.dev/131#guidance",
+	OnlyIf: isGetMethod,
+	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
+		// Rule check: Establish that for methods such as `GetFoo`, the response
+		// message is named `Foo`.
+		if got, want := m.GetOutputType().GetName(), m.GetName()[3:]; got != want {
+			return []lint.Problem{{
+				Message: fmt.Sprintf(
+					"Get RPCs should have the corresponding resource as the response message, such as %q.",
+					want,
+				),
+				Suggestion: want,
+				Descriptor: m,
+			}}
+		}
+
+		return nil
+	},
+}
diff --git a/rules/aip0131/synonyms.go b/rules/aip0131/synonyms.go
new file mode 100644
index 0000000..d49f98f
--- /dev/null
+++ b/rules/aip0131/synonyms.go
@@ -0,0 +1,46 @@
+// 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 aip0131
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/googleapis/api-linter/lint"
+	"github.com/jhump/protoreflect/desc"
+)
+
+// Get methods should not generally use synonyms for "get".
+var synonyms = &lint.MethodRule{
+	Name: lint.NewRuleName("core", "0131", "synonyms"),
+	URI:  "https://aip.dev/131#guidance",
+	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
+		name := m.GetName()
+		for _, syn := range []string{"Acquire", "Fetch", "Lookup", "Read", "Retrieve"} {
+			if strings.HasPrefix(name, syn) {
+				return []lint.Problem{{
+					Message: fmt.Sprintf(
+						`%q can be a synonym for "Get". Should this be a Get method?`,
+						syn,
+					),
+					Suggestion: strings.Replace(name, syn, "Get", 1),
+					Descriptor: m,
+					Location:   lint.DescriptorNameLocation(m),
+				}}
+			}
+		}
+		return nil
+	},
+}


Test patch:
diff --git a/cmd/api-linter/integration_test.go b/cmd/api-linter/integration_test.go
index 85e66f1..5372927 100644
--- a/cmd/api-linter/integration_test.go
+++ b/cmd/api-linter/integration_test.go
@@ -32,7 +32,7 @@ var testCases = []struct {
 }{
 	{
 		testName: "GetRequestMessage",
-		rule:     "core::0131::request-message::name",
+		rule:     "core::0131::request-message-name",
 		proto: `
 		syntax = "proto3";
 
diff --git a/rules/aip0131/http_body_test.go b/rules/aip0131/http_body_test.go
new file mode 100644
index 0000000..2a07781
--- /dev/null
+++ b/rules/aip0131/http_body_test.go
@@ -0,0 +1,72 @@
+// 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 aip0131
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/golang/protobuf/proto"
+	dpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
+	"github.com/jhump/protoreflect/desc/builder"
+	"google.golang.org/genproto/googleapis/api/annotations"
+)
+
+func TestHttpBody(t *testing.T) {
+	tests := []struct {
+		testName   string
+		body       string
+		methodName string
+		msg        string
+	}{
+		{"Valid", "", "GetBook", ""},
+		{"Invalid", "*", "GetBook", "HTTP body"},
+		{"Irrelevant", "*", "AcquireBook", ""},
+	}
+
+	for _, test := range tests {
+		t.Run(test.testName, func(t *testing.T) {
+			// Create a MethodOptions with the annotation set.
+			opts := &dpb.MethodOptions{}
+			httpRule := &annotations.HttpRule{
+				Pattern: &annotations.HttpRule_Get{
+					Get: "/v1/{name=publishers/*/books/*}",
+				},
+				Body: test.body,
+			}
+			if err := proto.SetExtension(opts, annotations.E_Http, httpRule); err != nil {
+				t.Fatalf("Failed to set google.api.http annotation.")
+			}
+
+			// Create a minimal service with a AIP-131 Get method
+			// (or with a different method, in the "Irrelevant" case).
+			service, err := builder.NewService("Library").AddMethod(builder.NewMethod(test.methodName,
+				builder.RpcTypeMessage(builder.NewMessage("GetBookRequest"), false),
+				builder.RpcTypeMessage(builder.NewMessage("Book"), false),
+			).SetOptions(opts)).Build()
+			if err != nil {
+				t.Fatalf("Could not build %s method.", test.methodName)
+			}
+
+			// Run the method, ensure we get what we expect.
+			problems := httpBody.Lint(service.GetFile())
+			if test.msg == "" && len(problems) > 0 {
+				t.Errorf("Got %v, expected no problems.", problems)
+			} else if test.msg != "" && !strings.Contains(problems[0].Message, test.msg) {
+				t.Errorf("Got %q, expected message containing %q", problems[0].Message, test.msg)
+			}
+		})
+	}
+}
diff --git a/rules/aip0131/http_method_test.go b/rules/aip0131/http_method_test.go
new file mode 100644
index 0000000..4a76162
--- /dev/null
+++ b/rules/aip0131/http_method_test.go
@@ -0,0 +1,80 @@
+// 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 aip0131
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/golang/protobuf/proto"
+	dpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
+	"github.com/jhump/protoreflect/desc/builder"
+	"google.golang.org/genproto/googleapis/api/annotations"
+)
+
+func TestHttpMethod(t *testing.T) {
+	// Set up GET and POST HTTP annotations.
+	httpGet := &annotations.HttpRule{
+		Pattern: &annotations.HttpRule_Get{
+			Get: "/v1/{name=publishers/*/books/*}",
+		},
+	}
+	httpPost := &annotations.HttpRule{
+		Pattern: &annotations.HttpRule_Post{
+			Post: "/v1/{name=publishers/*/books/*}",
+		},
+	}
+
+	// Set up testing permutations.
+	tests := []struct {
+		testName   string
+		httpRule   *annotations.HttpRule
+		methodName string
+		msg        string
+	}{
+		{"Valid", httpGet, "GetBook", ""},
+		{"Invalid", httpPost, "GetBook", "HTTP GET"},
+		{"Irrelevant", httpPost, "AcquireBook", ""},
+	}
+
+	// Run each test.
+	for _, test := range tests {
+		t.Run(test.testName, func(t *testing.T) {
+			// Create a MethodOptions with the annotation set.
+			opts := &dpb.MethodOptions{}
+			if err := proto.SetExtension(opts, annotations.E_Http, test.httpRule); err != nil {
+				t.Fatalf("Failed to set google.api.http annotation.")
+			}
+
+			// Create a minimal service with a AIP-131 Get method
+			// (or with a different method, in the "Irrelevant" case).
+			service, err := builder.NewService("Library").AddMethod(builder.NewMethod(test.methodName,
+				builder.RpcTypeMessage(builder.NewMessage("GetBookRequest"), false),
+				builder.RpcTypeMessage(builder.NewMessage("Book"), false),
+			).SetOptions(opts)).Build()
+			if err != nil {
+				t.Fatalf("Could not build %s method.", test.methodName)
+			}
+
+			// Run the method, ensure we get what we expect.
+			problems := httpMethod.Lint(service.GetFile())
+			if test.msg == "" && len(problems) > 0 {
+				t.Errorf("Got %v, expected no problems.", problems)
+			} else if test.msg != "" && !strings.Contains(problems[0].Message, test.msg) {
+				t.Errorf("Got %q, expected message containing %q", problems[0].Message, test.msg)
+			}
+		})
+	}
+}
diff --git a/rules/aip0131/http_uri_name_test.go b/rules/aip0131/http_uri_name_test.go
new file mode 100644
index 0000000..8a02542
--- /dev/null
+++ b/rules/aip0131/http_uri_name_test.go
@@ -0,0 +1,74 @@
+// 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 aip0131
+
+import (
+	"strings"
+	"testing"
+
+	"github.com/golang/protobuf/proto"
+	dpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
+	"github.com/jhump/protoreflect/desc/builder"
+	"google.golang.org/genproto/googleapis/api/annotations"
+)
+
+func TestHttpNameField(t *testing.T) {
+	tests := []struct {
+		testName   string
+		uri        string
+		methodName string
+		msg        string
+	}{
+		{"Valid", "/v1/{name=publishers/*/books/*}", "GetBook", ""},
+		{"InvalidVarName", "/v1/{book=publishers/*/books/*}", "GetBook", "`name` field"},
+		{"NoVarName", "/v1/publishers/*/books/*", "GetBook", "`name` field"},
+		{"Irrelevant", "/v1/{book=publishers/*/books/*}", "AcquireBook", ""},
+	}
+
+	for _, test := range tests {
+		t.Run(test.testName, func(t *testing.T) {
+			// Create a MethodOptions with the annotation set.
+			opts := &dpb.MethodOptions{}
+			httpRule := &annotations.HttpRule{
+				Pattern: &annotations.HttpRule_Get{
+					Get: test.uri,
+				},
+			}
+			if err := proto.SetExtension(opts, annotations.E_Http, httpRule); err != nil {
+				t.Fatalf("Failed to set google.api.http annotation.")
+			}
+
+			// Create a minimal service with a AIP-131 Get method
+			// (or with a different method, in the "Irrelevant" case).
+			service, err := builder.NewService("Library").AddMethod(builder.NewMethod(test.methodName,
+				builder.RpcTypeMessage(builder.NewMessage("GetBookRequest"), false),
+				builder.RpcTypeMessage(builder.NewMessage("Book"), false),
+			).SetOptions(opts)).Build()
+			if err != nil {
+				t.Fatalf("Could not build %s method.", test.methodName)
+			}
+
+			// Run the method, ensure we get what we expect.
+			problems := httpNameField.Lint(service.GetFile())
+			if test.msg == "" && len(problems) > 0 {
+				t.Errorf("Got %v, expected no problems.", problems)
+			} else if test.msg != "" && len(problems) == 0 {
+				t.Errorf("Got no problems, expected 1.")
+			} else if test.msg != "" && !strings.Contains(problems[0].Message, test.msg) {
+				t.Errorf("Got %q, expected message containing %q", problems[0].Message, test.msg)
+			}
+		})
+	}
+}
diff --git a/rules/aip0131/method_rules_test.go b/rules/aip0131/method_rules_test.go
deleted file mode 100644
index 2dd7659..0000000
--- a/rules/aip0131/method_rules_test.go
+++ /dev/null
@@ -1,276 +0,0 @@
-// 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 aip0131
-
-import (
-	"strings"
-	"testing"
-
-	"github.com/golang/protobuf/proto"
-	dpb "github.com/golang/protobuf/protoc-gen-go/descriptor"
-	"github.com/googleapis/api-linter/rules/internal/testutils"
-	"github.com/jhump/protoreflect/desc/builder"
-	"google.golang.org/genproto/googleapis/api/annotations"
-)
-
-func TestRequestMessageName(t *testing.T) {
-	// Set up the testing permutations.
-	tests := []struct {
-		testName       string
-		methodName     string
-		reqMessageName string
-		problems       testutils.Problems
-	}{
-		{"Valid", "GetBook", "GetBookRequest", testutils.Problems{}},
-		{"Invalid", "GetBook", "Book", testutils.Problems{{Suggestion: "GetBookRequest"}}},
-		{"GetIamPolicy", "GetIamPolicy", "GetIamPolicyRequest", testutils.Problems{}},
-		{"Irrelevant", "AcquireBook", "Book", testutils.Problems{}},
-	}
-
-	// Run each test individually.
-	for _, test := range tests {
-		t.Run(test.testName, func(t *testing.T) {
-			// Create a minimal service with a AIP-131 Get method
-			// (or with a different method, in the "Irrelevant" case).
-			service, err := builder.NewService("Library").AddMethod(builder.NewMethod(test.methodName,
-				builder.RpcTypeMessage(builder.NewMessage(test.reqMessageName), false),
-				builder.RpcTypeMessage(builder.NewMessage("Book"), false),
-			)).Build()
-			if err != nil {
-				t.Fatalf("Could not build %s method.", test.methodName)
-			}
-
-			// Run the lint rule, and establish that it returns the expected problems.
-			problems := requestMessageName.Lint(service.GetFile())
-			if diff := test.problems.SetDescriptor(service.GetMethods()[0]).Diff(problems); diff != "" {
-				t.Errorf(diff)
-			}
-		})
-	}
-}
-
-func TestResponseMessageName(t *testing.T) {
-	// Set up the testing permutations.
-	tests := []struct {
-		testName        string
-		methodName      string
-		respMessageName string
-		problems        testutils.Problems
-	}{
-		{"Valid", "GetBook", "Book", testutils.Problems{}},
-		{"Invalid", "GetBook", "GetBookResponse", testutils.Problems{{Suggestion: "Book"}}},
-		{"Irrelevant", "AcquireBook", "AcquireBookResponse", testutils.Problems{}},
-	}
-
-	// Run each test individually.
-	for _, test := range tests {
-		t.Run(test.testName, func(t *testing.T) {
-			// Create a minimal service with a AIP-131 Get method
-			// (or with a different method, in the "Irrelevant" case).
-			service, err := builder.NewService("Library").AddMethod(builder.NewMethod(test.methodName,
-				builder.RpcTypeMessage(builder.NewMessage("GetBookRequest"), false),
-				builder.RpcTypeMessage(builder.NewMessage(test.respMessageName), false),
-			)).Build()
-			if err != nil {
-				t.Fatalf("Could not build %s method.", test.methodName)
-			}
-
-			// Run the lint rule, and establish that it returns the correct
-			// number of problems.
-			problems := responseMessageName.Lint(service.GetFile())
-			if diff := test.problems.SetDescriptor(service.GetMethods()[0]).Diff(problems); diff != "" {
-				t.Errorf(diff)
-			}
-		})
-	}
-}
-
-func TestHttpMethod(t *testing.T) {
-	// Set up GET and POST HTTP annotations.
-	httpGet := &annotations.HttpRule{
-		Pattern: &annotations.HttpRule_Get{
-			Get: "/v1/{name=publishers/*/books/*}",
-		},
-	}
-	httpPost := &annotations.HttpRule{
-		Pattern: &annotations.HttpRule_Post{
-			Post: "/v1/{name=publishers/*/books/*}",
-		},
-	}
-
-	// Set up testing permutations.
-	tests := []struct {
-		testName   string
-		httpRule   *annotations.HttpRule
-		methodName string
-		msg        string
-	}{
-		{"Valid", httpGet, "GetBook", ""},
-		{"Invalid", httpPost, "GetBook", "HTTP GET"},
-		{"Irrelevant", httpPost, "AcquireBook", ""},
-	}
-
-	// Run each test.
-	for _, test := range tests {
-		t.Run(test.testName, func(t *testing.T) {
-			// Create a MethodOptions with the annotation set.
-			opts := &dpb.MethodOptions{}
-			if err := proto.SetExtension(opts, annotations.E_Http, test.httpRule); err != nil {
-				t.Fatalf("Failed to set google.api.http annotation.")
-			}
-
-			// Create a minimal service with a AIP-131 Get method
-			// (or with a different method, in the "Irrelevant" case).
-			service, err := builder.NewService("Library").AddMethod(builder.NewMethod(test.methodName,
-				builder.RpcTypeMessage(builder.NewMessage("GetBookRequest"), false),
-				builder.RpcTypeMessage(builder.NewMessage("Book"), false),
-			).SetOptions(opts)).Build()
-			if err != nil {
-				t.Fatalf("Could not build %s method.", test.methodName)
-			}
-
-			// Run the method, ensure we get what we expect.
-			problems := httpMethod.Lint(service.GetFile())
-			if test.msg == "" && len(problems) > 0 {
-				t.Errorf("Got %v, expected no problems.", problems)
-			} else if test.msg != "" && !strings.Contains(problems[0].Message, test.msg) {
-				t.Errorf("Got %q, expected message containing %q", problems[0].Message, test.msg)
-			}
-		})
-	}
-}
-
-func TestHttpBody(t *testing.T) {
-	tests := []struct {
-		testName   string
-		body       string
-		methodName string
-		msg        string
-	}{
-		{"Valid", "", "GetBook", ""},
-		{"Invalid", "*", "GetBook", "HTTP body"},
-		{"Irrelevant", "*", "AcquireBook", ""},
-	}
-
-	for _, test := range tests {
-		t.Run(test.testName, func(t *testing.T) {
-			// Create a MethodOptions with the annotation set.
-			opts := &dpb.MethodOptions{}
-			httpRule := &annotations.HttpRule{
-				Pattern: &annotations.HttpRule_Get{
-					Get: "/v1/{name=publishers/*/books/*}",
-				},
-				Body: test.body,
-			}
-			if err := proto.SetExtension(opts, annotations.E_Http, httpRule); err != nil {
-				t.Fatalf("Failed to set google.api.http annotation.")
-			}
-
-			// Create a minimal service with a AIP-131 Get method
-			// (or with a different method, in the "Irrelevant" case).
-			service, err := builder.NewService("Library").AddMethod(builder.NewMethod(test.methodName,
-				builder.RpcTypeMessage(builder.NewMessage("GetBookRequest"), false),
-				builder.RpcTypeMessage(builder.NewMessage("Book"), false),
-			).SetOptions(opts)).Build()
-			if err != nil {
-				t.Fatalf("Could not build %s method.", test.methodName)
-			}
-
-			// Run the method, ensure we get what we expect.
-			problems := httpBody.Lint(service.GetFile())
-			if test.msg == "" && len(problems) > 0 {
-				t.Errorf("Got %v, expected no problems.", problems)
-			} else if test.msg != "" && !strings.Contains(problems[0].Message, test.msg) {
-				t.Errorf("Got %q, expected message containing %q", problems[0].Message, test.msg)
-			}
-		})
-	}
-}
-
-func TestHttpNameField(t *testing.T) {
-	tests := []struct {
-		testName   string
-		uri        string
-		methodName string
-		msg        string
-	}{
-		{"Valid", "/v1/{name=publishers/*/books/*}", "GetBook", ""},
-		{"InvalidVarName", "/v1/{book=publishers/*/books/*}", "GetBook", "`name` field"},
-		{"NoVarName", "/v1/publishers/*/books/*", "GetBook", "`name` field"},
-		{"Irrelevant", "/v1/{book=publishers/*/books/*}", "AcquireBook", ""},
-	}
-
-	for _, test := range tests {
-		t.Run(test.testName, func(t *testing.T) {
-			// Create a MethodOptions with the annotation set.
-			opts := &dpb.MethodOptions{}
-			httpRule := &annotations.HttpRule{
-				Pattern: &annotations.HttpRule_Get{
-					Get: test.uri,
-				},
-			}
-			if err := proto.SetExtension(opts, annotations.E_Http, httpRule); err != nil {
-				t.Fatalf("Failed to set google.api.http annotation.")
-			}
-
-			// Create a minimal service with a AIP-131 Get method
-			// (or with a different method, in the "Irrelevant" case).
-			service, err := builder.NewService("Library").AddMethod(builder.NewMethod(test.methodName,
-				builder.RpcTypeMessage(builder.NewMessage("GetBookRequest"), false),
-				builder.RpcTypeMessage(builder.NewMessage("Book"), false),
-			).SetOptions(opts)).Build()
-			if err != nil {
-				t.Fatalf("Could not build %s method.", test.methodName)
-			}
-
-			// Run the method, ensure we get what we expect.
-			problems := httpNameField.Lint(service.GetFile())
-			if test.msg == "" && len(problems) > 0 {
-				t.Errorf("Got %v, expected no problems.", problems)
-			} else if test.msg != "" && len(problems) == 0 {
-				t.Errorf("Got no problems, expected 1.")
-			} else if test.msg != "" && !strings.Contains(problems[0].Message, test.msg) {
-				t.Errorf("Got %q, expected message containing %q", problems[0].Message, test.msg)
-			}
-		})
-	}
-}
-
-func TestSynonyms(t *testing.T) {
-	tests := []struct {
-		MethodName string
-		problems   testutils.Problems
-	}{
-		{"GetBook", testutils.Problems{}},
-		{"AcquireBook", testutils.Problems{{Suggestion: "GetBook"}}},
-		{"FetchBook", testutils.Problems{{Suggestion: "GetBook"}}},
-		{"LookupBook", testutils.Problems{{Suggestion: "GetBook"}}},
-		{"ReadBook", testutils.Problems{{Suggestion: "GetBook"}}},
-		{"RetrieveBook", testutils.Problems{{Suggestion: "GetBook"}}},
-	}
-	for _, test := range tests {
-		file := testutils.ParseProto3Tmpl(t, `
-			service Library {
-				rpc {{.MethodName}}({{.MethodName}}Request) returns (Book);
-			}
-			message {{.MethodName}}Request {}
-			message Book {}
-		`, test)
-		m := file.GetServices()[0].GetMethods()[0]
-		if diff := test.problems.SetDescriptor(m).Diff(synonyms.Lint(file)); diff != "" {
-			t.Errorf(diff)
-		}
-	}
-}
diff --git a/rules/aip0131/request_message_name_test.go b/rules/aip0131/request_message_name_test.go
new file mode 100644
index 0000000..5f78864
--- /dev/null
+++ b/rules/aip0131/request_message_name_test.go
@@ -0,0 +1,58 @@
+// 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 aip0131
+
+import (
+	"testing"
+
+	"github.com/googleapis/api-linter/rules/internal/testutils"
+	"github.com/jhump/protoreflect/desc/builder"
+)
+
+func TestRequestMessageName(t *testing.T) {
+	// Set up the testing permutations.
+	tests := []struct {
+		testName       string
+		methodName     string
+		reqMessageName string
+		problems       testutils.Problems
+	}{
+		{"Valid", "GetBook", "GetBookRequest", testutils.Problems{}},
+		{"Invalid", "GetBook", "Book", testutils.Problems{{Suggestion: "GetBookRequest"}}},
+		{"GetIamPolicy", "GetIamPolicy", "GetIamPolicyRequest", testutils.Problems{}},
+		{"Irrelevant", "AcquireBook", "Book", testutils.Problems{}},
+	}
+
+	// Run each test individually.
+	for _, test := range tests {
+		t.Run(test.testName, func(t *testing.T) {
+			// Create a minimal service with a AIP-131 Get method
+			// (or with a different method, in the "Irrelevant" case).
+			service, err := builder.NewService("Library").AddMethod(builder.NewMethod(test.methodName,
+				builder.RpcTypeMessage(builder.NewMessage(test.reqMessageName), false),
+				builder.RpcTypeMessage(builder.NewMessage("Book"), false),
+			)).Build()
+			if err != nil {
+				t.Fatalf("Could not build %s method.", test.methodName)
+			}
+
+			// Run the lint rule, and establish that it returns the expected problems.
+			problems := requestMessageName.Lint(service.GetFile())
+			if diff := test.problems.SetDescriptor(service.GetMethods()[0]).Diff(problems); diff != "" {
+				t.Errorf(diff)
+			}
+		})
+	}
+}
diff --git a/rules/aip0131/response_message_name_test.go b/rules/aip0131/response_message_name_test.go
new file mode 100644
index 0000000..fc902ea
--- /dev/null
+++ b/rules/aip0131/response_message_name_test.go
@@ -0,0 +1,58 @@
+// 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 aip0131
+
+import (
+	"testing"
+
+	"github.com/googleapis/api-linter/rules/internal/testutils"
+	"github.com/jhump/protoreflect/desc/builder"
+)
+
+func TestResponseMessageName(t *testing.T) {
+	// Set up the testing permutations.
+	tests := []struct {
+		testName        string
+		methodName      string
+		respMessageName string
+		problems        testutils.Problems
+	}{
+		{"Valid", "GetBook", "Book", testutils.Problems{}},
+		{"Invalid", "GetBook", "GetBookResponse", testutils.Problems{{Suggestion: "Book"}}},
+		{"Irrelevant", "AcquireBook", "AcquireBookResponse", testutils.Problems{}},
+	}
+
+	// Run each test individually.
+	for _, test := range tests {
+		t.Run(test.testName, func(t *testing.T) {
+			// Create a minimal service with a AIP-131 Get method
+			// (or with a different method, in the "Irrelevant" case).
+			service, err := builder.NewService("Library").AddMethod(builder.NewMethod(test.methodName,
+				builder.RpcTypeMessage(builder.NewMessage("GetBookRequest"), false),
+				builder.RpcTypeMessage(builder.NewMessage(test.respMessageName), false),
+			)).Build()
+			if err != nil {
+				t.Fatalf("Could not build %s method.", test.methodName)
+			}
+
+			// Run the lint rule, and establish that it returns the correct
+			// number of problems.
+			problems := responseMessageName.Lint(service.GetFile())
+			if diff := test.problems.SetDescriptor(service.GetMethods()[0]).Diff(problems); diff != "" {
+				t.Errorf(diff)
+			}
+		})
+	}
+}
diff --git a/rules/aip0131/synonyms_test.go b/rules/aip0131/synonyms_test.go
new file mode 100644
index 0000000..cdee5da
--- /dev/null
+++ b/rules/aip0131/synonyms_test.go
@@ -0,0 +1,48 @@
+// 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 aip0131
+
+import (
+	"testing"
+
+	"github.com/googleapis/api-linter/rules/internal/testutils"
+)
+
+func TestSynonyms(t *testing.T) {
+	tests := []struct {
+		MethodName string
+		problems   testutils.Problems
+	}{
+		{"GetBook", testutils.Problems{}},
+		{"AcquireBook", testutils.Problems{{Suggestion: "GetBook"}}},
+		{"FetchBook", testutils.Problems{{Suggestion: "GetBook"}}},
+		{"LookupBook", testutils.Problems{{Suggestion: "GetBook"}}},
+		{"ReadBook", testutils.Problems{{Suggestion: "GetBook"}}},
+		{"RetrieveBook", testutils.Problems{{Suggestion: "GetBook"}}},
+	}
+	for _, test := range tests {
+		file := testutils.ParseProto3Tmpl(t, `
+			service Library {
+				rpc {{.MethodName}}({{.MethodName}}Request) returns (Book);
+			}
+			message {{.MethodName}}Request {}
+			message Book {}
+		`, test)
+		m := file.GetServices()[0].GetMethods()[0]
+		if diff := test.problems.SetDescriptor(m).Diff(synonyms.Lint(file)); diff != "" {
+			t.Errorf(diff)
+		}
+	}
+}