Repository: googleapis/api-linter
Pull request #359 (2019-11-19 02:55:52)

AIP234 batach update left rules and related docs
AIP234 batach update left rules and related docs


Patch:
diff --git a/docs/rules/0234/plural-method-name.md b/docs/rules/0234/plural-method-name.md
new file mode 100644
index 0000000..34904a3
--- /dev/null
+++ b/docs/rules/0234/plural-method-name.md
@@ -0,0 +1,68 @@
+---
+rule:
+  aip: 234
+  name: [core, '0234', plural-method-name]
+  summary: Batch Update methods must have plural names.
+permalink: /234/plural-method-name
+redirect_from:
+  - /0234/plural-method-name
+---
+
+# Batch Update methods: Plural method name
+
+This rule enforces that all `BatchUpdate` RPCs have a plural resource in the
+method name, as mandated in [AIP-234][].
+
+## Details
+
+This rule looks at any method whose name begins with `BatchUpdate`, and complains
+if the name of the resource in the method name is singular.
+
+## Examples
+
+**Incorrect** code for this rule:
+
+```proto
+// Incorrect.
+// Method name should be `BatchUpdateBooks`
+rpc BatchUpdateBook(BatchUpdateBookRequest) returns (BatchUpdateBookResponse) {
+  option (google.api.http) = {
+    post: "/v1/{parent=publishers/*}/books:batchUpdate"
+    body: "*"
+  };
+}
+```
+
+**Correct** code for this rule:
+
+```proto
+// Correct.
+rpc BatchUpdateBooks(BatchUpdateBooksRequest) returns (BatchUpdateBooksResponse) {
+  option (google.api.http) = {
+    post: "/v1/{parent=publishers/*}/books:batchUpdate"
+    body: "*"
+  };
+}
+```
+
+## 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::0234::plural-method-name=disabled
+//     aip.dev/not-precedent: We need to do this because reasons. --)
+rpc BatchUpdateBook(BatchUpdateBookRequest) returns (BatchUpdateBookResponse) {
+  option (google.api.http) = {
+    post: "/v1/{parent=publishers/*}/books:batchUpdate"
+    body: "*"
+  };
+}
+```
+
+If you need to violate this rule for an entire file, place the comment at the
+top of the file.
+
+[aip-234]: https://aip.dev/234
+[aip.dev/not-precedent]: https://aip.dev/not-precedent
diff --git a/docs/rules/0234/request-message-name.md b/docs/rules/0234/request-message-name.md
new file mode 100644
index 0000000..deb6552
--- /dev/null
+++ b/docs/rules/0234/request-message-name.md
@@ -0,0 +1,69 @@
+---
+rule:
+  aip: 234
+  name: [core, '0234', request-message-name]
+  summary: Batch Update methods must have standardized request message names.
+permalink: /234/request-message-name
+redirect_from:
+  - /0234/request-message-name
+---
+
+# Batch Update methods: Request message
+
+This rule enforces that all `BatchUpdate` RPCs have a request message name of
+`BatchUpdate*Request`, as mandated in [AIP-234][].
+
+## Details
+
+This rule looks at any RPCs whose name starts with `BatchUpdate`, 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.
+// Method input should be "BatchUpdateBooksRequest"
+rpc BatchUpdateBooks(UpdateBookRequest) returns (BatchUpdateBooksResponse) {
+  option (google.api.http) = {
+    post: "/v1/{parent=publishers/*}/books:batchUpdate"
+    body: "*"
+  };
+}
+```
+
+**Correct** code for this rule:
+
+```proto
+// Correct.
+rpc BatchUpdateBooks(BatchUpdateBooksRequest) returns (BatchUpdateBooksResponse) {
+  option (google.api.http) = {
+    post: "/v1/{parent=publishers/*}/books:batchUpdate"
+    body: "*"
+  };
+}
+```
+
+## 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::0234::request-message-name=disabled
+//     aip.dev/not-precedent: We need to do this because reasons. --)
+rpc BatchUpdateBooks(UpdateBookRequest) returns (BatchUpdateBooksResponse) {
+  option (google.api.http) = {
+    post: "/v1/{parent=publishers/*}/books:batchUpdate"
+    body: "*"
+  };
+}
+```
+
+If you need to violate this rule for an entire file, place the comment at the
+top of the file.
+
+[aip-234]: https://aip.dev/234
+[aip.dev/not-precedent]: https://aip.dev/not-precedent
diff --git a/docs/rules/0234/request-parent-field.md b/docs/rules/0234/request-parent-field.md
new file mode 100644
index 0000000..40cb26e
--- /dev/null
+++ b/docs/rules/0234/request-parent-field.md
@@ -0,0 +1,71 @@
+---
+rule:
+  aip: 234
+  name: [core, '0234', request-parent-field]
+  summary: Batch Update RPCs must have a `parent` field in the request.
+permalink: /234/request-parent-field
+redirect_from:
+  - /0234/request-parent-field
+---
+
+# Batch Update methods: Parent field
+
+This rule enforces that all `BatchUpdate` methods have a `string parent` field
+in the request message, as mandated in [AIP-234][].
+
+## Details
+
+This rule looks at any message matching `BatchUpdate*Request` and complains if
+either the `parent` field is missing, or if it has any type other than
+`string`.
+
+## Examples
+
+**Incorrect** code for this rule:
+
+```proto
+// Incorrect.
+message BatchUpdateBooksRequest {
+  string publisher = 1;  // Field name should be `parent`.
+  repeated UpdateBookRequest requests = 2;
+}
+```
+
+```proto
+// Incorrect.
+message BatchUpdateBookRequest {
+  bytes parent = 1;  // Field type should be `string`.
+  repeated UpdateBookRequest requests = 2;
+}
+```
+
+**Correct** code for this rule:
+
+```proto
+// Correct.
+message BatchUpdateBookRequest {
+  string parent = 1;
+  repeated UpdateBookRequest requests = 2;
+}
+```
+
+## Disabling
+
+If you need to violate this rule, use a leading comment above the message (if
+the `parent` field is missing) or above the field (if it is the wrong type).
+Remember to also include an [aip.dev/not-precedent][] comment explaining why.
+
+```proto
+// (-- api-linter: core::0234::request-parent-field=disabled
+//     aip.dev/not-precedent: We need to do this because reasons. --)
+message BatchUpdateBooksRequest {
+  string publisher = 1;  // Field name should be `parent`.
+  repeated UpdateBookRequest requests = 2;
+}
+```
+
+If you need to violate this rule for an entire file, place the comment at the
+top of the file.
+
+[aip-234]: https://aip.dev/234
+[aip.dev/not-precedent]: https://aip.dev/not-precedent
diff --git a/docs/rules/0234/request-requests-field.md b/docs/rules/0234/request-requests-field.md
new file mode 100644
index 0000000..1967929
--- /dev/null
+++ b/docs/rules/0234/request-requests-field.md
@@ -0,0 +1,73 @@
+---
+rule:
+  aip: 234
+  name: [core, '0234', request-requests-field]
+  summary: Batch Update RPCs should have a `requests` field in the request.
+permalink: /234/request-requests-field
+redirect_from:
+  - /0234/request-requests-field
+---
+
+# Batch Update methods: Requests field
+
+This rule enforces that all `BatchUpdate` methods have a repeated `requests`
+field, the type of which is the standard Update request (`Update*Request`)
+in the request message, as mandated in [AIP-234][].
+
+## Details
+
+This rule looks at any message matching `BatchUpdate*Request` and complains if
+the `requests` field is missing, if it has any type other than `Update*Request`,
+or if it is not `repeated`.
+
+
+## Examples
+
+**Incorrect** code for this rule:
+
+```proto
+// Incorrect.
+message BatchUpdateBooksRequest {
+  string parent = 1;
+  repeated UpdateBookRequest req = 2;  // Field name should be `requests`.
+}
+```
+
+```proto
+// Incorrect.
+message BatchUpdateBooksRequest {
+  string parent = 1;
+  UpdateBookRequest requests = 2;  // Field should be repeated.
+}
+```
+
+**Correct** code for this rule:
+
+```proto
+// Correct.
+message BatchUpdateBookRequest {
+  string parent = 1;
+  repeated UpdateBookRequest requests = 2;
+}
+```
+
+## Disabling
+
+If you need to violate this rule, use a leading comment above the message (if
+the `requests` field is missing) or above the field (if it is the wrong type).
+Remember to also include an [aip.dev/not-precedent][] comment explaining why.
+
+```proto
+// (-- api-linter: core::0234::request-requests-field=disabled
+//     aip.dev/not-precedent: We need to do this because reasons. --)
+message BatchUpdateBooksRequest {
+  string parent = 1;
+  repeated string books = 2; // should be "repeated UpdateBookRequest requests"
+}
+```
+
+If you need to violate this rule for an entire file, place the comment at the
+top of the file.
+
+[aip-234]: https://aip.dev/234
+[aip.dev/not-precedent]: https://aip.dev/not-precedent
diff --git a/docs/rules/0234/response-message-name.md b/docs/rules/0234/response-message-name.md
new file mode 100644
index 0000000..ec3e514
--- /dev/null
+++ b/docs/rules/0234/response-message-name.md
@@ -0,0 +1,75 @@
+---
+rule:
+  aip: 234
+  name: [core, '0234', response-message-name]
+  summary: Batch Update methods must have standardized response message names.
+permalink: /234/response-message-name
+redirect_from:
+  - /0234/response-message-name
+---
+
+# Batch Update methods: Response message
+
+This rule enforces that all `BatchUpdate` RPCs have a response message name of
+`BatchUpdate*Response`, as mandated in [AIP-234][].
+
+## Details
+
+This rule looks at any RPCs whose name starts with `BatchUpdate`, and
+complains if the name of the corresponding returned message does not match the
+name of the RPC with the suffix `Response` appended.
+
+It also permits a response of `google.longrunning.Operation`; in this case, it
+checks the `response_type` in the `google.longrunning.operation_info`
+annotation and ensures that _it_ corresponds to the name of the RPC with the
+suffix `Response` appended, as mandated in [AIP-151][].
+
+## Examples
+
+**Incorrect** code for this rule:
+
+```proto
+// Incorrect.
+// Should be `BatchUpdateBooksResponse`.
+rpc BatchUpdateBooks(BatchUpdateBooksRequest) returns (BooksResponse) {
+  option (google.api.http) = {
+    post: "/v1/{parent=publishers/*}/books:batchUpdate"
+    body: "*"
+  };
+}
+```
+
+**Correct** code for this rule:
+
+```proto
+// Correct.
+rpc BatchUpdateBooks(BatchUpdateBooksRequest) returns (BatchUpdateBooksResponse) {
+  option (google.api.http) = {
+    post: "/v1/{parent=publishers/*}/books:batchUpdate"
+    body: "*"
+  };
+}
+```
+
+## 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::0234::response-message-name=disabled
+//     aip.dev/not-precedent: We need to do this because reasons. --)
+rpc BatchUpdateBooks(BatchUpdateBooksRequest) returns (BooksResponse) {
+  option (google.api.http) = {
+    post: "/v1/{parent=publishers/*}/books:batchUpdate"
+    body: "*"
+  };
+}
+```
+
+If you need to violate this rule for an entire file, place the comment at the
+top of the file.
+
+[aip-234]: https://aip.dev/234
+[aip-151]: https://aip.dev/151
+[aip.dev/not-precedent]: https://aip.dev/not-precedent
diff --git a/docs/rules/0234/response-resource-field.md b/docs/rules/0234/response-resource-field.md
new file mode 100644
index 0000000..3588111
--- /dev/null
+++ b/docs/rules/0234/response-resource-field.md
@@ -0,0 +1,67 @@
+---
+rule:
+  aip: 234
+  name: [core, '0234', response-resource-field]
+  summary:
+    Batch Update RPCs must have a repeated field for the resource in the response.
+permalink: /234/response-resource-field
+redirect_from:
+  - /0234/response-resource-field
+---
+
+# Batch Update methods: Resource field
+
+This rule enforces that all `BatchUpdate` methods have a repeated field in the
+response message for the resource itself, as mandated in [AIP-234][].
+
+## Details
+
+This rule looks at any message matching `BatchUpdate*Response` and complains if
+there is no repeated field of the resource's type with the expected field name.
+
+## Examples
+
+**Incorrect** code for this rule:
+
+```proto
+// Incorrect.
+message BatchUpdateBooksResponse {
+  // `repeated Book books` is missing.
+}
+```
+
+```proto
+// Incorrect.
+message BatchUpdateBooksResponse {
+  Book books = 1;  // Field should be repeated.
+}
+```
+
+**Correct** code for this rule:
+
+```proto
+// Correct.
+message BatchUpdateBooksResponse {
+  repeated Book books = 1;
+}
+```
+
+## Disabling
+
+If you need to violate this rule, use a leading comment above the message (if
+the resource field is missing) or above the field (if it is improperly named).
+Remember to also include an [aip.dev/not-precedent][] comment explaining why.
+
+```proto
+// (-- api-linter: core::0234::response-resource-field=disabled
+//     aip.dev/not-precedent: We need to do this because reasons. --)
+message BatchUpdateBooksResponse {
+  // `repeated Book books` is missing.
+}
+```
+
+If you need to violate this rule for an entire file, place the comment at the
+top of the file.
+
+[aip-234]: https://aip.dev/234
+[aip.dev/not-precedent]: https://aip.dev/not-precedent
diff --git a/rules/aip0234/aip0234.go b/rules/aip0234/aip0234.go
index be3725b..06051fa 100644
--- a/rules/aip0234/aip0234.go
+++ b/rules/aip0234/aip0234.go
@@ -30,6 +30,12 @@ func AddRules(r lint.RuleRegistry) error {
 		httpBody,
 		httpMethod,
 		httpUriSuffix,
+		pluralMethodName,
+		requestMessageName,
+		requestParentField,
+		requestRequestsField,
+		responseMessageName,
+		responseResourceField,
 	)
 }
 
diff --git a/rules/aip0234/plural_method_name.go b/rules/aip0234/plural_method_name.go
new file mode 100644
index 0000000..c32b954
--- /dev/null
+++ b/rules/aip0234/plural_method_name.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 aip0234
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/gertd/go-pluralize"
+	"github.com/googleapis/api-linter/lint"
+	"github.com/googleapis/api-linter/locations"
+	"github.com/jhump/protoreflect/desc"
+)
+
+var pluralMethodName = &lint.MethodRule{
+	Name:   lint.NewRuleName(234, "plural-method-name"),
+	OnlyIf: isBatchUpdateMethod,
+	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
+		// Note: Retrieve the resource name from the method name. For example,
+		// "BatchUpdateFoos" -> "Foos"
+		pluralMethodResourceName := strings.TrimPrefix(m.GetName(), "BatchUpdate")
+
+		pluralize := pluralize.NewClient()
+
+		// Rule check: Establish that for methods such as `BatchUpdateFoos`
+		if !pluralize.IsPlural(pluralMethodResourceName) {
+			return []lint.Problem{{
+				Message: fmt.Sprintf(
+					`The resource part in method name %q shouldn't be %q, but should be its plural form %q`,
+					m.GetName(), pluralMethodResourceName, pluralize.Plural(pluralMethodResourceName),
+				),
+				Descriptor: m,
+				Location:   locations.DescriptorName(m),
+				Suggestion: "BatchUpdate" + pluralize.Plural(pluralMethodResourceName),
+			}}
+		}
+
+		return nil
+	},
+}
diff --git a/rules/aip0234/request_message_name.go b/rules/aip0234/request_message_name.go
new file mode 100644
index 0000000..3fd4346
--- /dev/null
+++ b/rules/aip0234/request_message_name.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 aip0234
+
+import (
+	"fmt"
+
+	"github.com/googleapis/api-linter/lint"
+	"github.com/googleapis/api-linter/locations"
+	"github.com/jhump/protoreflect/desc"
+)
+
+// Batch Update method should have a properly named Request message.
+var requestMessageName = &lint.MethodRule{
+	Name:   lint.NewRuleName(234, "request-message-name"),
+	OnlyIf: isBatchUpdateMethod,
+	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
+		// Rule check: Establish that for methods such as `BatchUpdateFoos`, the request
+		// message is named `BatchUpdateFoosRequest`.
+		if got, want := m.GetInputType().GetName(), m.GetName()+"Request"; got != want {
+			return []lint.Problem{{
+				Message: fmt.Sprintf(
+					"Batch Update RPCs should have a properly named request message %q, but not %q",
+					want, got,
+				),
+				Descriptor: m,
+				Location:   locations.MethodRequestType(m),
+				Suggestion: want,
+			}}
+		}
+
+		return nil
+	},
+}
diff --git a/rules/aip0234/request_parent_field.go b/rules/aip0234/request_parent_field.go
new file mode 100644
index 0000000..817cec8
--- /dev/null
+++ b/rules/aip0234/request_parent_field.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 aip0234
+
+import (
+	"fmt"
+
+	"github.com/googleapis/api-linter/lint"
+	"github.com/googleapis/api-linter/locations"
+	"github.com/jhump/protoreflect/desc"
+	"github.com/jhump/protoreflect/desc/builder"
+)
+
+// The Batch Update request message should have parent field.
+var requestParentField = &lint.MessageRule{
+	Name:   lint.NewRuleName(234, "request-parent-field"),
+	OnlyIf: isBatchUpdateRequestMessage,
+	LintMessage: func(m *desc.MessageDescriptor) []lint.Problem {
+		// Rule check: Establish that a `parent` field is present.
+		parentField := m.FindFieldByName("parent")
+		if parentField == nil {
+			return []lint.Problem{{
+				Message:    fmt.Sprintf(`Message %q has no "parent" field`, m.GetName()),
+				Descriptor: m,
+			}}
+		}
+
+		// Rule check: Establish that the parent field is a string.
+		if parentField.GetType() != builder.FieldTypeString().GetType() {
+			return []lint.Problem{{
+				Message:    `"parent" field on Update request message must be a string`,
+				Descriptor: parentField,
+				Location:   locations.FieldType(parentField),
+				Suggestion: "string",
+			}}
+		}
+
+		return nil
+	},
+}
diff --git a/rules/aip0234/request_requests_field.go b/rules/aip0234/request_requests_field.go
new file mode 100644
index 0000000..b16399d
--- /dev/null
+++ b/rules/aip0234/request_requests_field.go
@@ -0,0 +1,67 @@
+// 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 aip0234
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/gertd/go-pluralize"
+	"github.com/googleapis/api-linter/lint"
+	"github.com/googleapis/api-linter/locations"
+	"github.com/jhump/protoreflect/desc"
+)
+
+// The Batch Update standard method should have repeated standard Update request
+// message field.
+var requestRequestsField = &lint.MessageRule{
+	Name:   lint.NewRuleName(234, "request-requests-field"),
+	OnlyIf: isBatchUpdateRequestMessage,
+	LintMessage: func(m *desc.MessageDescriptor) (problems []lint.Problem) {
+		// Rule check: Establish that a "requests" field is present.
+		requests := m.FindFieldByName("requests")
+
+		// Rule check: Ensure that the "requests" field is existed.
+		if requests == nil {
+			return []lint.Problem{{
+				Message:    fmt.Sprintf(`Message %q has no "requests" field`, m.GetName()),
+				Descriptor: m,
+			}}
+		}
+
+		// Rule check: Ensure that the standard Update request message field "requests" is repeated.
+		if !requests.IsRepeated() {
+			problems = append(problems, lint.Problem{
+				Message:    `The "requests" field should be repeated`,
+				Descriptor: requests,
+			})
+		}
+
+		// Rule check: Ensure that the standard update request message field is the
+		// correct type. Note: Retrieve the resource name from the the batch update
+		// request, for example: "BatchUpdateBooksRequest" -> "Books"
+		rightTypeName := fmt.Sprintf("Update%sRequest",
+			pluralize.NewClient().Singular(strings.TrimPrefix(strings.TrimSuffix(m.GetName(), "Request"), "BatchUpdate")))
+		if requests.GetMessageType() == nil || requests.GetMessageType().GetName() != rightTypeName {
+			problems = append(problems, lint.Problem{
+				Message:    fmt.Sprintf(`The "requests" field on Batch Update Request should be a %q type`, rightTypeName),
+				Descriptor: requests,
+				Location:   locations.FieldType(requests),
+				Suggestion: rightTypeName,
+			})
+		}
+		return
+	},
+}
diff --git a/rules/aip0234/response_message_name.go b/rules/aip0234/response_message_name.go
new file mode 100644
index 0000000..f340ba2
--- /dev/null
+++ b/rules/aip0234/response_message_name.go
@@ -0,0 +1,62 @@
+// 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 aip0234
+
+import (
+	"fmt"
+
+	"github.com/googleapis/api-linter/lint"
+	"github.com/googleapis/api-linter/locations"
+	"github.com/googleapis/api-linter/rules/internal/utils"
+	"github.com/jhump/protoreflect/desc"
+)
+
+// Batch Update method should have a properly named Response message.
+var responseMessageName = &lint.MethodRule{
+	Name:   lint.NewRuleName(234, "response-message-name"),
+	OnlyIf: isBatchUpdateMethod,
+	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
+		// Proper response name should be the concatenation of the method name and
+		// "Response"
+		want := m.GetName() + "Response"
+
+		// If this is an LRO, then use the annotated response type instead of
+		// the actual RPC return type.
+		got := m.GetOutputType().GetName()
+		if m.GetOutputType().GetFullyQualifiedName() == "google.longrunning.Operation" {
+			got = utils.GetOperationInfo(m).GetResponseType()
+		}
+
+		// Rule check: Establish that for methods such as `BatchUpdateFoos`, the
+		// response message should be named `BatchUpdateFoosResponse`
+		//
+		// Note: If `got` is empty string, this is an unannotated LRO.
+		// The AIP-151 rule will whine about that, and this rule should not as it
+		// would be confusing.
+		if got != want && got != "" {
+			return []lint.Problem{{
+				Message: fmt.Sprintf(
+					"Batch Update RPCs should have a properly named response message %q, but not %q",
+					want, got,
+				),
+				Descriptor: m,
+				Location:   locations.MethodResponseType(m),
+				Suggestion: want,
+			}}
+		}
+
+		return nil
+	},
+}
diff --git a/rules/aip0234/response_resource_field.go b/rules/aip0234/response_resource_field.go
new file mode 100644
index 0000000..526e8af
--- /dev/null
+++ b/rules/aip0234/response_resource_field.go
@@ -0,0 +1,55 @@
+// 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 aip0234
+
+import (
+	"fmt"
+	"strings"
+
+	"github.com/gertd/go-pluralize"
+	"github.com/googleapis/api-linter/lint"
+	"github.com/jhump/protoreflect/desc"
+)
+
+// The Batch Update response message should have resource field.
+var responseResourceField = &lint.MessageRule{
+	Name:   lint.NewRuleName(234, "response-resource-field"),
+	OnlyIf: isBatchUpdateResponseMessage,
+	LintMessage: func(m *desc.MessageDescriptor) []lint.Problem {
+		// the singular form the resource name, the first letter is Capitalized.
+		// Note: Retrieve the resource name from the the batch update response,
+		// for example: "BatchUpdateBooksResponse" -> "Books"
+		resourceMsgName := pluralize.NewClient().Singular(strings.TrimPrefix(strings.TrimSuffix(m.GetName(), "Response"), "BatchUpdate"))
+
+		for _, fieldDesc := range m.GetFields() {
+			if msgDesc := fieldDesc.GetMessageType(); msgDesc != nil && msgDesc.GetName() == resourceMsgName {
+				if !fieldDesc.IsRepeated() {
+					return []lint.Problem{{
+						Message:    fmt.Sprintf("The %q type field on Batch Update Response message should be repeated", msgDesc.GetName()),
+						Descriptor: fieldDesc,
+					}}
+				}
+
+				return nil
+			}
+		}
+
+		// Rule check: Establish that a resource field must be included.
+		return []lint.Problem{{
+			Message:    fmt.Sprintf("Message %q has no %q type field", m.GetName(), resourceMsgName),
+			Descriptor: m,
+		}}
+	},
+}


Test patch:
diff --git a/rules/aip0234/plural_method_name_test.go b/rules/aip0234/plural_method_name_test.go
new file mode 100644
index 0000000..62a80ee
--- /dev/null
+++ b/rules/aip0234/plural_method_name_test.go
@@ -0,0 +1,95 @@
+// 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 aip0234
+
+import (
+	"testing"
+
+	"github.com/googleapis/api-linter/rules/internal/testutils"
+)
+
+func TestMethodPluralResourceName(t *testing.T) {
+	// Set up the testing permutations.
+	tests := []struct {
+		testName   string
+		MethodName string
+		UriSuffix  string
+		problems   testutils.Problems
+	}{
+		{
+			testName:   "Valid-BatchUpdateBooks",
+			MethodName: "BatchUpdateBooks",
+			UriSuffix:  "books:batchUpdate",
+			problems:   testutils.Problems{},
+		},
+		{
+			testName:   "Valid-BatchUpdateMen",
+			MethodName: "BatchUpdateMen",
+			UriSuffix:  "men:batchUpdate",
+			problems:   testutils.Problems{},
+		},
+		{
+			testName:   "Invalid-SingularBus",
+			MethodName: "BatchUpdateBus",
+			UriSuffix:  "bus:batchUpdate",
+			problems: testutils.Problems{{
+				Suggestion: "BatchUpdateBuses",
+			}},
+		},
+		{
+			testName:   "Invalid-SingularCorpPerson",
+			MethodName: "BatchUpdateCorpPerson",
+			UriSuffix:  "corpPerson:batchUpdate",
+			problems: testutils.Problems{{
+				Suggestion: "BatchUpdateCorpPeople",
+			}},
+		},
+		{
+			testName:   "Invalid-Irrelevant",
+			MethodName: "AcquireBook",
+			UriSuffix:  "book",
+			problems:   testutils.Problems{},
+		},
+	}
+
+	// Run each test individually.
+	for _, test := range tests {
+		t.Run(test.testName, func(t *testing.T) {
+			file := testutils.ParseProto3Tmpl(t, `
+				import "google/api/annotations.proto";
+				
+				service BookService {
+					rpc {{.MethodName}}({{.MethodName}}Request) returns ({{.MethodName}}Response) {
+						option (google.api.http) = {
+							post: "/v1/{parent=publishers/*}/{{.UriSuffix}}"
+							body: "*"
+						};
+					}
+				}
+				
+				message {{.MethodName}}Request {}
+				
+				message {{.MethodName}}Response{}
+				`, test)
+
+			m := file.GetServices()[0].GetMethods()[0]
+
+			problems := pluralMethodName.Lint(file)
+			if diff := test.problems.SetDescriptor(m).Diff(problems); diff != "" {
+				t.Errorf(diff)
+			}
+		})
+	}
+}
diff --git a/rules/aip0234/request_message_name_test.go b/rules/aip0234/request_message_name_test.go
new file mode 100644
index 0000000..0e66b16
--- /dev/null
+++ b/rules/aip0234/request_message_name_test.go
@@ -0,0 +1,79 @@
+// 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 aip0234
+
+import (
+	"testing"
+
+	"github.com/googleapis/api-linter/rules/internal/testutils"
+)
+
+func TestRequestMessageName(t *testing.T) {
+	// Set up the testing permutations.
+	tests := []struct {
+		testName   string
+		MethodName string
+		Request    string
+		problems   testutils.Problems
+	}{
+		{
+			testName:   "Valid-BatchUpdateBooksRequest",
+			MethodName: "BatchUpdateBooks",
+			Request:    "BatchUpdateBooksRequest",
+			problems:   testutils.Problems{},
+		},
+		{
+			testName:   "Invalid-MissMatchingMethodName",
+			MethodName: "BatchUpdateBooks",
+			Request:    "BatchUpdateBookRequest",
+			problems: testutils.Problems{{
+				Suggestion: "BatchUpdateBooksRequest",
+			}},
+		},
+		{
+			testName:   "Irrelevant",
+			MethodName: "AcquireBook",
+			Request:    "AcquireBookRequest",
+			problems:   testutils.Problems{},
+		},
+	}
+
+	// Run each test individually.
+	for _, test := range tests {
+		t.Run(test.testName, func(t *testing.T) {
+			file := testutils.ParseProto3Tmpl(t, `
+				import "google/api/annotations.proto";
+				
+				service BookService {
+					rpc {{.MethodName}}({{.Request}}) returns ({{.MethodName}}Response) {
+						option (google.api.http) = {
+							post: "/v1/{parent=publishers/*}/"
+							body: "*"
+						};
+					}
+				}
+				message {{.Request}}{}
+				message {{.MethodName}}Response{}
+				`, test)
+
+			m := file.GetServices()[0].GetMethods()[0]
+
+			problems := requestMessageName.Lint(file)
+			if diff := test.problems.SetDescriptor(m).Diff(problems); diff != "" {
+				t.Errorf(diff)
+			}
+		})
+	}
+}
diff --git a/rules/aip0234/request_parent_field_test.go b/rules/aip0234/request_parent_field_test.go
new file mode 100644
index 0000000..284c1d7
--- /dev/null
+++ b/rules/aip0234/request_parent_field_test.go
@@ -0,0 +1,87 @@
+// 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 aip0234
+
+import (
+	"testing"
+
+	"github.com/googleapis/api-linter/rules/internal/testutils"
+	"github.com/jhump/protoreflect/desc"
+)
+
+func TestRequestParentField(t *testing.T) {
+	// Set up the testing permutations.
+	tests := []struct {
+		testName    string
+		MessageName string
+		Field       string
+		problems    testutils.Problems
+	}{
+		{
+			"Valid",
+			"BatchUpdateBooksRequest",
+			"string parent",
+			testutils.Problems{},
+		},
+		{
+			"MissingField",
+			"BatchUpdateBooksRequest",
+			"string id",
+			testutils.Problems{{Message: "parent"}},
+		},
+		{
+			"InvalidType",
+			"BatchUpdateBooksRequest",
+			"int32 parent",
+			testutils.Problems{{
+				Message:    "string",
+				Suggestion: "string",
+			}},
+		},
+		{
+			"Irrelevant",
+			"EnumerateBooksRequest",
+			"string id",
+			testutils.Problems{},
+		},
+	}
+
+	// Run each test individually.
+	for _, test := range tests {
+		t.Run(test.testName, func(t *testing.T) {
+			file := testutils.ParseProto3Tmpl(t, `
+				message {{.MessageName}} {
+					{{.Field}} = 1;
+					repeated UpdateBookRequest requests = 2;
+				}
+				message UpdateBookRequest{}
+				`, test)
+
+			// Determine the descriptor that a failing test will attach to.
+			var problemDesc desc.Descriptor
+			if parent := file.GetMessageTypes()[0].FindFieldByName("parent"); parent != nil {
+				problemDesc = parent
+			} else {
+				problemDesc = file.GetMessageTypes()[0]
+			}
+
+			// Run the lint rule, and establish that it returns the correct problems.
+			problems := requestParentField.Lint(file)
+			if diff := test.problems.SetDescriptor(problemDesc).Diff(problems); diff != "" {
+				t.Errorf(diff)
+			}
+		})
+	}
+}
diff --git a/rules/aip0234/request_requests_field_test.go b/rules/aip0234/request_requests_field_test.go
new file mode 100644
index 0000000..4793ee9
--- /dev/null
+++ b/rules/aip0234/request_requests_field_test.go
@@ -0,0 +1,88 @@
+// 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 aip0234
+
+import (
+	"testing"
+
+	"github.com/googleapis/api-linter/rules/internal/testutils"
+	"github.com/jhump/protoreflect/desc"
+)
+
+func TestRequestRequestsField(t *testing.T) {
+	// Set up the testing permutations.
+	tests := []struct {
+		testName string
+		Field    string
+		problems testutils.Problems
+	}{
+		{
+			testName: "Valid",
+			Field:    "repeated UpdateBookRequest requests",
+			problems: testutils.Problems{},
+		},
+		{
+			testName: "Invalid-MissingRequestsField",
+			Field:    "string parent",
+			problems: testutils.Problems{{Message: `no "requests" field`}},
+		},
+		{
+			testName: "Invalid-RequestsFieldIsNotRepeated",
+			Field:    "UpdateBookRequest requests",
+			problems: testutils.Problems{{Message: "repeated"}},
+		},
+		{
+			testName: "Invalid-RequestsFieldWrongType",
+			Field:    "repeated int32 requests",
+			problems: testutils.Problems{{
+				Suggestion: "UpdateBookRequest",
+			}},
+		},
+		{
+			testName: "Invalid-RequestsNotRepeatedWrongType",
+			Field:    "int32 requests",
+			problems: testutils.Problems{
+				{Message: `The "requests" field should be repeated`},
+				{
+					Suggestion: "UpdateBookRequest",
+				}},
+		},
+	}
+
+	// Run each test individually.
+	for _, test := range tests {
+		t.Run(test.testName, func(t *testing.T) {
+			file := testutils.ParseProto3Tmpl(t, `
+				message BatchUpdateBooksRequest {
+					{{.Field}} = 1;
+				}
+				message UpdateBookRequest {}
+				`, test)
+
+			// Determine the descriptor that a failing test will attach to.
+			var problemDesc desc.Descriptor
+			if requests := file.GetMessageTypes()[0].FindFieldByName("requests"); requests != nil {
+				problemDesc = requests
+			} else {
+				problemDesc = file.GetMessageTypes()[0]
+			}
+
+			problems := requestRequestsField.Lint(file)
+			if diff := test.problems.SetDescriptor(problemDesc).Diff(problems); diff != "" {
+				t.Errorf(diff)
+			}
+		})
+	}
+}
diff --git a/rules/aip0234/response_message_name_test.go b/rules/aip0234/response_message_name_test.go
new file mode 100644
index 0000000..27babc8
--- /dev/null
+++ b/rules/aip0234/response_message_name_test.go
@@ -0,0 +1,130 @@
+// 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 aip0234
+
+import (
+	"testing"
+
+	"github.com/googleapis/api-linter/rules/internal/testutils"
+)
+
+func TestResponseMessageName(t *testing.T) {
+	// Set up the testing permutations.
+	tests := []struct {
+		testName   string
+		MethodName string
+		Response   string
+		problems   testutils.Problems
+	}{
+		{
+			testName:   "Valid-BatchUpdateBooksResponse",
+			MethodName: "BatchUpdateBooks",
+			Response:   "BatchUpdateBooksResponse",
+			problems:   testutils.Problems{},
+		},
+		{
+			testName:   "Invalid-MissMatchingMethodName",
+			MethodName: "BatchUpdateBooks",
+			Response:   "BatchUpdateBookResponse",
+			problems: testutils.Problems{{
+				Suggestion: "BatchUpdateBooksResponse",
+			}},
+		},
+		{
+			testName:   "Irrelevant",
+			MethodName: "UpdateBook",
+			Response:   "Book",
+			problems:   testutils.Problems{},
+		},
+	}
+
+	// Run each test individually.
+	for _, test := range tests {
+		t.Run(test.testName, func(t *testing.T) {
+			file := testutils.ParseProto3Tmpl(t, `
+				import "google/api/annotations.proto";
+				
+				service BookService {
+					rpc {{.MethodName}}({{.MethodName}}Request) returns ({{.Response}}) {
+						option (google.api.http) = {
+							post: "/v1/{parent=publishers/*}/"
+							body: "*"
+						};
+					}
+				}
+				message {{.MethodName}}Request {}
+				message {{.Response}}{}
+				`, test)
+
+			m := file.GetServices()[0].GetMethods()[0]
+
+			problems := responseMessageName.Lint(file)
+			if diff := test.problems.SetDescriptor(m).Diff(problems); diff != "" {
+				t.Errorf(diff)
+			}
+		})
+	}
+}
+
+// Other cases for long running response will be handled by AIP-0151
+func TestLongRunningResponse(t *testing.T) {
+	// Set up the testing permutations.
+	tests := []struct {
+		testName     string
+		ResponseType string
+		problems     testutils.Problems
+	}{
+		{
+			testName:     "Valid-LongRunning",
+			ResponseType: "BatchUpdateBooksResponse",
+			problems:     testutils.Problems{},
+		},
+		{
+			testName: "Valid-LongRunningEmptyResponseType",
+			problems: testutils.Problems{},
+		},
+	}
+
+	// Run each test individually.
+	for _, test := range tests {
+		t.Run(test.testName, func(t *testing.T) {
+			file := testutils.ParseProto3Tmpl(t, `
+				import "google/api/annotations.proto";
+				import "google/longrunning/operations.proto";
+				
+				service BookService {
+					rpc BatchUpdateBooks(BatchUpdateBooksRequest) returns (google.longrunning.Operation) {
+						option (google.api.http) = {
+							post: "/v1/{parent=publishers/*}/books:batchUpdate"
+							body: "*"
+						};
+						option (google.longrunning.operation_info) = {
+							response_type: "{{.ResponseType}}"
+						};
+					}
+				}
+				message BatchUpdateBooksRequest {}
+				message BatchUpdateBooksResponse{}
+				`, test)
+
+			m := file.GetServices()[0].GetMethods()[0]
+
+			problems := responseMessageName.Lint(file)
+			if diff := test.problems.SetDescriptor(m).Diff(problems); diff != "" {
+				t.Errorf(diff)
+			}
+		})
+	}
+}
diff --git a/rules/aip0234/response_resource_field_test.go b/rules/aip0234/response_resource_field_test.go
new file mode 100644
index 0000000..6794287
--- /dev/null
+++ b/rules/aip0234/response_resource_field_test.go
@@ -0,0 +1,76 @@
+// 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 aip0234
+
+import (
+	"testing"
+
+	"github.com/googleapis/api-linter/rules/internal/testutils"
+	"github.com/jhump/protoreflect/desc"
+)
+
+func TestResponseResourceField(t *testing.T) {
+	// Set up the testing permutations.
+	tests := []struct {
+		testName    string
+		Field       string
+		problems    testutils.Problems
+		problemDesc func(m *desc.MessageDescriptor) desc.Descriptor
+	}{
+		{
+			testName: "Valid",
+			Field:    "repeated Book books",
+			problems: testutils.Problems{},
+		},
+		{
+			testName: "FieldIsNotRepeated",
+			Field:    "Book book",
+			problems: testutils.Problems{{Message: "repeated"}},
+		},
+		{
+			testName: "MissingField",
+			Field:    "string response",
+			problems: testutils.Problems{{Message: "no \"Book\" type field"}},
+			problemDesc: func(m *desc.MessageDescriptor) desc.Descriptor {
+				return m
+			},
+		},
+	}
+
+	// Run each test individually.
+	for _, test := range tests {
+		t.Run(test.testName, func(t *testing.T) {
+			file := testutils.ParseProto3Tmpl(t, `
+				message BatchUpdateBooksResponse {
+					{{.Field}} = 1;
+				}
+				message Book {
+				}`, test)
+
+			m := file.GetMessageTypes()[0]
+
+			// Determine the descriptor that a failing test will attach to.
+			var problemDesc desc.Descriptor = m.GetFields()[0]
+			if test.problemDesc != nil {
+				problemDesc = test.problemDesc(m)
+			}
+
+			problems := responseResourceField.Lint(file)
+			if diff := test.problems.SetDescriptor(problemDesc).Diff(problems); diff != "" {
+				t.Errorf(diff)
+			}
+		})
+	}
+}