Repository: googleapis/api-linter
Pull request #725 (2021-01-06 18:47:31)

feat: Add rules for Tag Revision methods
Part of #721 


Patch:
diff --git a/docs/rules/0162/index.md b/docs/rules/0162/index.md
new file mode 100644
index 0000000..0253b96
--- /dev/null
+++ b/docs/rules/0162/index.md
@@ -0,0 +1,10 @@
+---
+aip_listing: 162
+permalink: /162/
+redirect_from:
+  - /0162/
+---
+
+# Resource revisions
+
+{% include linter-aip-listing.md aip=162 %}
diff --git a/docs/rules/0162/tag-revision-http-body.md b/docs/rules/0162/tag-revision-http-body.md
new file mode 100644
index 0000000..26eda00
--- /dev/null
+++ b/docs/rules/0162/tag-revision-http-body.md
@@ -0,0 +1,66 @@
+---
+rule:
+  aip: 162
+  name: [core, '0162', tag-revision-http-body]
+  summary: Tag Revision methods should use `*` as the HTTP body.
+permalink: /162/tag-revision-http-body
+redirect_from:
+  - /0162/tag-revision-http-body
+---
+
+# Tag Revision methods: HTTP body
+
+This rule enforces that all Tag Revision RPCs use `*` as the HTTP `body`, as mandated in
+[AIP-162][].
+
+## Details
+
+This rule looks at any method matching `Tag*Revision`, and complains
+if the HTTP `body` field is anything other than `*`.
+
+## Examples
+
+**Incorrect** code for this rule:
+
+```proto
+// Incorrect.
+rpc TagBookRevision(TagBookRevisionRequest) returns (Book) {
+  option (google.api.http) = {
+    post: "/v1/{name=publishers/*/books/*}:tagRevision"
+    // body: "*" should be set.
+  };
+}
+```
+
+**Correct** code for this rule:
+
+```proto
+// Correct.
+rpc TagBookRevision(TagBookRevisionRequest) returns (Book) {
+  option (google.api.http) = {
+    post: "/v1/{name=publishers/*/books/*}:tagRevision"
+    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::0162::tag-revision-http-body=disabled
+//     aip.dev/not-precedent: We need to do this because reasons. --)
+rpc TagBookRevision(TagBookRevisionRequest) returns (Book) {
+  option (google.api.http) = {
+    post: "/v1/{name=publishers/*/books/*}:tagRevision"
+  };
+}
+```
+
+If you need to violate this rule for an entire file, place the comment at the
+top of the file.
+
+[aip-162]: https://aip.dev/162
+[aip.dev/not-precedent]: https://aip.dev/not-precedent
diff --git a/docs/rules/0162/tag-revision-http-method.md b/docs/rules/0162/tag-revision-http-method.md
new file mode 100644
index 0000000..eba8559
--- /dev/null
+++ b/docs/rules/0162/tag-revision-http-method.md
@@ -0,0 +1,66 @@
+---
+rule:
+  aip: 162
+  name: [core, '0162', tag-revision-http-method]
+  summary: Tag Revision methods must use the POST HTTP verb.
+permalink: /162/tag-revision-http-method
+redirect_from:
+  - /0162/tag-revision-http-method
+---
+
+# Tag Revision methods: POST HTTP verb
+
+This rule enforces that all Tag Revision RPCs use the `POST` HTTP verb, as
+mandated in [AIP-162][].
+
+## Details
+
+This rule looks at any method matching `Tag*Revision`, and complains
+if the HTTP verb is anything other than `POST`. It _does_ check additional
+bindings if they are present.
+
+## Examples
+
+**Incorrect** code for this rule:
+
+```proto
+// Incorrect.
+rpc TagBookRevision(TagBookRevisionRequest) returns (Book) {
+  option (google.api.http) = {
+    get: "/v1/{name=publishers/*/books/*}:tagRevision"  // Should be `post:`.
+  };
+}
+```
+
+**Correct** code for this rule:
+
+```proto
+// Correct.
+rpc TagBookRevision(TagBookRevisionRequest) returns (Book) {
+  option (google.api.http) = {
+    post: "/v1/{name=publishers/*/books/*}:tagRevision"
+    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::0162::tag-revision-http-method=disabled
+//     aip.dev/not-precedent: We need to do this because reasons. --)
+rpc TagBookRevision(TagBookRevisionRequest) returns (Book) {
+  option (google.api.http) = {
+    get: "/v1/{name=publishers/*/books/*}:tagRevision"
+  };
+}
+```
+
+If you need to violate this rule for an entire file, place the comment at the
+top of the file.
+
+[aip-162]: https://aip.dev/162
+[aip.dev/not-precedent]: https://aip.dev/not-precedent
diff --git a/docs/rules/0162/tag-revision-http-uri-suffix.md b/docs/rules/0162/tag-revision-http-uri-suffix.md
new file mode 100644
index 0000000..1398473
--- /dev/null
+++ b/docs/rules/0162/tag-revision-http-uri-suffix.md
@@ -0,0 +1,67 @@
+---
+rule:
+  aip: 162
+  name: [core, '0162', tag-revision-http-uri-suffix]
+  summary: Tag Revision methods must have the correct URI suffix
+permalink: /162/tag-revision-http-uri-suffix
+redirect_from:
+  - /0162/tag-revision-http-uri-suffix
+---
+
+# Tag Revision methods: URI suffix
+
+This rule enforces that Tag Revision methods include the `:tagRevision` suffix
+in the REST URI, as mandated in [AIP-162][].
+
+## Details
+
+This rule looks at any method matching `Tag*Revision`, and
+complains if the HTTP URI does not end with `:tagRevision`.
+
+## Examples
+
+**Incorrect** code for this rule:
+
+```proto
+// Incorrect.
+rpc TagBookRevision(TagBookRevisionRequest) returns (Book) {
+  option (google.api.http) = {
+    post: "/v1/{name=publishers/*/books/*}:tag"  // Should end with `:tagRevision`
+    body: "*"
+  };
+}
+```
+
+**Correct** code for this rule:
+
+```proto
+// Correct.
+rpc TagBookRevision(TagBookRevisionRequest) returns (Book) {
+  option (google.api.http) = {
+    post: "/v1/{name=publishers/*/books/*}:tagRevision"
+    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::0162::tag-revision-http-uri-suffix=disabled
+//     aip.dev/not-precedent: We need to do this because reasons. --)
+rpc TagBookRevision(TagBookRevisionRequest) returns (Book) {
+  option (google.api.http) = {
+    post: "/v1/{name=publishers/*/books/*}:tag"
+    body: "*"
+  };
+}
+```
+
+If you need to violate this rule for an entire file, place the comment at the
+top of the file.
+
+[aip-162]: https://aip.dev/162
+[aip.dev/not-precedent]: https://aip.dev/not-precedent
diff --git a/docs/rules/0162/tag-revision-request-message-name.md b/docs/rules/0162/tag-revision-request-message-name.md
new file mode 100644
index 0000000..272bc91
--- /dev/null
+++ b/docs/rules/0162/tag-revision-request-message-name.md
@@ -0,0 +1,69 @@
+---
+rule:
+  aip: 162
+  name: [core, '0162', tag-revision-request-message-name]
+  summary: Tag Revision methods must have standardized request message names.
+permalink: /162/tag-revision-request-message-name
+redirect_from:
+  - /0162/tag-revision-request-message-name
+---
+
+# Tag Revision methods: Request message
+
+This rule enforces that all Tag Revision RPCs have a request message name of
+`Tag*RevisionRequest`, as mandated in [AIP-162][].
+
+## Details
+
+This rule looks at any method matching `Tag*Revision`, 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.
+// Should be `TagBookRevisionRequest`.
+rpc TagBookRevision(TagBookRequest) returns (Book) {
+  option (google.api.http) = {
+    post: "/v1/{name=publishers/*/books/*}:tagRevision"
+    body: "*"
+  };
+}
+```
+
+**Correct** code for this rule:
+
+```proto
+// Correct.
+rpc TagBookRevision(TagBookRevisionRequest) returns (Book) {
+  option (google.api.http) = {
+    post: "/v1/{name=publishers/*/books/*}:tagRevision"
+    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::0162::tag-revision-request-message-name=disabled
+//     aip.dev/not-precedent: We need to do this because reasons. --)
+rpc TagBookRevision(TagBookRequest) returns (Book) {
+  option (google.api.http) = {
+    post: "/v1/{name=publishers/*/books/*}:tagRevision"
+    body: "*"
+  };
+}
+```
+
+If you need to violate this rule for an entire file, place the comment at the
+top of the file.
+
+[aip-162]: https://aip.dev/162
+[aip.dev/not-precedent]: https://aip.dev/not-precedent
diff --git a/docs/rules/0162/tag-revision-request-name-behavior.md b/docs/rules/0162/tag-revision-request-name-behavior.md
new file mode 100644
index 0000000..6a2dbba
--- /dev/null
+++ b/docs/rules/0162/tag-revision-request-name-behavior.md
@@ -0,0 +1,75 @@
+---
+rule:
+  aip: 162
+  name: [core, '0162', tag-revision-request-name-behavior]
+  summary: |
+    Tag Revision requests should annotate the `name` field with `google.api.field_behavior`.
+permalink: /162/tag-revision-request-name-behavior
+redirect_from:
+  - /0162/tag-revision-request-name-behavior
+---
+
+# Tag Revision requests: Name field behavior
+
+This rule enforces that all Tag Revision requests have
+`google.api.field_behavior` set to `REQUIRED` on their `string name` field, as
+mandated in [AIP-162][].
+
+## Details
+
+This rule looks at any message matching `Tag*RevisionRequest` and complains if the
+`name` field does not have a `google.api.field_behavior` annotation with a
+value of `REQUIRED`.
+
+## Examples
+
+**Incorrect** code for this rule:
+
+```proto
+// Incorrect.
+message TagBookRevisionRequest {
+  // The `google.api.field_behavior` annotation should also be included.
+  string name = 1 [
+    (google.api.resource_reference).type = "library.googleapis.com/Book"
+  ];
+
+  string tag = 2 [(google.api.field_behavior) = REQUIRED];
+}
+```
+
+**Correct** code for this rule:
+
+```proto
+// Correct.
+message TagBookRevisionRequest {
+  string name = 1 [
+    (google.api.field_behavior) = REQUIRED,
+    (google.api.resource_reference).type = "library.googleapis.com/Book"
+  ];
+
+  string tag = 2 [(google.api.field_behavior) = REQUIRED];
+}
+```
+
+## Disabling
+
+If you need to violate this rule, use a leading comment above the field.
+Remember to also include an [aip.dev/not-precedent][] comment explaining why.
+
+```proto
+message TagBookRevisionRequest {
+  // (-- api-linter: core::0162::tag-revision-request-name-behavior=disabled
+  //     aip.dev/not-precedent: We need to do this because reasons. --)
+  string name = 1 [
+    (google.api.resource_reference).type = "library.googleapis.com/Book"
+  ];
+
+  string tag = 2 [(google.api.field_behavior) = REQUIRED];
+}
+```
+
+If you need to violate this rule for an entire file, place the comment at the
+top of the file.
+
+[aip-162]: https://aip.dev/162
+[aip.dev/not-precedent]: https://aip.dev/not-precedent
diff --git a/docs/rules/0162/tag-revision-request-name-field.md b/docs/rules/0162/tag-revision-request-name-field.md
new file mode 100644
index 0000000..7e0a135
--- /dev/null
+++ b/docs/rules/0162/tag-revision-request-name-field.md
@@ -0,0 +1,83 @@
+---
+rule:
+  aip: 162
+  name: [core, '0162', tag-revision-request-name-field]
+  summary: Tag Revision RPCs must have a `name` field in the request.
+permalink: /162/tag-revision-request-name-field
+redirect_from:
+  - /0162/tag-revision-request-name-field
+---
+
+# Tag Revision requests: Name field
+
+This rule enforces that all Tag Revision methods have a `string name`
+field in the request message, as mandated in [AIP-162][].
+
+## Details
+
+This rule looks at any message matching `Tag*RevisionRequest` and complains if
+either the `name` field is missing or it has any type other than `string`.
+
+## Examples
+
+**Incorrect** code for this rule:
+
+```proto
+// Incorrect.
+// Should include a `string name` field.
+message TagBookRevisionRequest {
+  string tag = 2 [(google.api.field_behavior) = REQUIRED];
+}
+```
+
+```proto
+// Incorrect.
+message TagBookRevisionRequest {
+  // Field type should be `string`.
+  bytes name = 1 [
+    (google.api.field_behavior) = REQUIRED,
+    (google.api.resource_reference).type = "library.googleapis.com/Book"
+  ];
+
+  string tag = 2 [(google.api.field_behavior) = REQUIRED];
+}
+```
+
+**Correct** code for this rule:
+
+```proto
+// Correct.
+message TagBookRevisionRequest {
+  string name = 1 [
+    (google.api.field_behavior) = REQUIRED,
+    (google.api.resource_reference).type = "library.googleapis.com/Book"
+  ];
+
+  string tag = 2 [(google.api.field_behavior) = REQUIRED];
+}
+```
+
+## Disabling
+
+If you need to violate this rule, use a leading comment above the message (if
+the `name` 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
+message TagBookRevisionRequest {
+  // (-- api-linter: core::0162::tag-revision-request-name-field=disabled
+  //     aip.dev/not-precedent: We need to do this because reasons. --)
+  bytes name = 1 [
+    (google.api.field_behavior) = REQUIRED,
+    (google.api.resource_reference).type = "library.googleapis.com/Book"
+  ];
+
+  string tag = 2 [(google.api.field_behavior) = REQUIRED];
+}
+```
+
+If you need to violate this rule for an entire file, place the comment at the
+top of the file.
+
+[aip-162]: https://aip.dev/162
+[aip.dev/not-precedent]: https://aip.dev/not-precedent
diff --git a/docs/rules/0162/tag-revision-request-name-reference.md b/docs/rules/0162/tag-revision-request-name-reference.md
new file mode 100644
index 0000000..c719293
--- /dev/null
+++ b/docs/rules/0162/tag-revision-request-name-reference.md
@@ -0,0 +1,70 @@
+---
+rule:
+  aip: 162
+  name: [core, '0162', tag-revision-request-name-reference]
+  summary: |
+    Tag Revision requests should annotate the `name` field with `google.api.resource_reference`.
+permalink: /162/tag-revision-request-name-reference
+redirect_from:
+  - /0162/tag-revision-request-name-reference
+---
+
+# Tag Revision requests: Name resource reference
+
+This rule enforces that all Tag Revision requests have
+`google.api.resource_reference` on their `string name` field, as mandated in
+[AIP-162][].
+
+## Details
+
+This rule looks at the `name` field of any message matching `Tag*RevisionRequest`
+and complains if it does not have a `google.api.resource_reference` annotation.
+
+## Examples
+
+**Incorrect** code for this rule:
+
+```proto
+// Incorrect.
+message TagBookRevisionRequest {
+  // The `google.api.resource_reference` annotation should also be included.
+  string name = 1 [(google.api.field_behavior) = REQUIRED];
+
+  string tag = 2 [(google.api.field_behavior) = REQUIRED];
+}
+```
+
+**Correct** code for this rule:
+
+```proto
+// Correct.
+message TagBookRevisionRequest {
+  string name = 1 [
+    (google.api.field_behavior) = REQUIRED,
+    (google.api.resource_reference).type = "library.googleapis.com/Book"
+  ];
+
+  string tag = 2 [(google.api.field_behavior) = REQUIRED];
+}
+```
+
+## Disabling
+
+If you need to violate this rule, use a leading comment above the field.
+Remember to also include an [aip.dev/not-precedent][] comment explaining why.
+
+```proto
+message TagBookRevisionRequest {
+  // (-- api-linter: core::0162::tag-revision-request-name-reference=disabled
+  //     aip.dev/not-precedent: We need to do this because reasons. --)
+  string name = 1 [(google.api.field_behavior) = REQUIRED];
+
+  string tag = 2 [(google.api.field_behavior) = REQUIRED];
+}
+```
+
+If you need to violate this rule for an entire file, place the comment at the
+top of the file.
+
+[aip-162]: https://aip.dev/162
+[aip.dev/not-precedent]: https://aip.dev/not-precedent
diff --git a/docs/rules/0162/tag-revision-request-tag-behavior.md b/docs/rules/0162/tag-revision-request-tag-behavior.md
new file mode 100644
index 0000000..fab6aa4
--- /dev/null
+++ b/docs/rules/0162/tag-revision-request-tag-behavior.md
@@ -0,0 +1,77 @@
+---
+rule:
+  aip: 162
+  name: [core, '0162', tag-revision-request-tag-behavior]
+  summary: |
+    Tag Revision requests should annotate the `tag` field with `google.api.field_behavior`.
+permalink: /162/tag-revision-request-tag-behavior
+redirect_from:
+  - /0162/tag-revision-request-tag-behavior
+---
+
+# Tag Revision requests: Tag field behavior
+
+This rule enforces that all Tag Revision requests have
+`google.api.field_behavior` set to `REQUIRED` on their `string tag` field, as
+mandated in [AIP-162][].
+
+## Details
+
+This rule looks at any message matching `Tag*RevisionRequest` and complains if the
+`tag` field does not have a `google.api.field_behavior` annotation with a
+value of `REQUIRED`.
+
+## Examples
+
+**Incorrect** code for this rule:
+
+```proto
+// Incorrect.
+message TagBookRevisionRequest {
+  string name = 1 [
+    (google.api.field_behavior) = REQUIRED,
+    (google.api.resource_reference).type = "library.googleapis.com/Book"
+  ];
+
+  // The `google.api.field_behavior` annotation should be included.
+  string tag = 2;
+}
+```
+
+**Correct** code for this rule:
+
+```proto
+// Correct.
+message TagBookRevisionRequest {
+  string name = 1 [
+    (google.api.field_behavior) = REQUIRED,
+    (google.api.resource_reference).type = "library.googleapis.com/Book"
+  ];
+
+  string tag = 2 [(google.api.field_behavior) = REQUIRED];
+}
+```
+
+## Disabling
+
+If you need to violate this rule, use a leading comment above the field.
+Remember to also include an [aip.dev/not-precedent][] comment explaining why.
+
+```proto
+message TagBookRevisionRequest {
+  string name = 1 [
+    (google.api.field_behavior) = REQUIRED,
+    (google.api.resource_reference).type = "library.googleapis.com/Book"
+  ];
+
+  // (-- api-linter: core::0162::tag-revision-request-tag-behavior=disabled
+  //     aip.dev/not-precedent: We need to do this because reasons. --)
+  string tag = 2;
+}
+```
+
+If you need to violate this rule for an entire file, place the comment at the
+top of the file.
+
+[aip-162]: https://aip.dev/162
+[aip.dev/not-precedent]: https://aip.dev/not-precedent
diff --git a/docs/rules/0162/tag-revision-request-tag-field.md b/docs/rules/0162/tag-revision-request-tag-field.md
new file mode 100644
index 0000000..74e980a
--- /dev/null
+++ b/docs/rules/0162/tag-revision-request-tag-field.md
@@ -0,0 +1,86 @@
+---
+rule:
+  aip: 162
+  name: [core, '0162', tag-revision-request-tag-field]
+  summary: Tag Revision RPCs must have a `tag` field in the request.
+permalink: /162/tag-revision-request-tag-field
+redirect_from:
+  - /0162/tag-revision-request-tag-field
+---
+
+# Tag Revision requests: Tag field
+
+This rule enforces that all Tag Revision methods have a `string tag`
+field in the request message, as mandated in [AIP-162][].
+
+## Details
+
+This rule looks at any message matching `Tag*RevisionRequest` and complains if
+either the `tag` field is missing or it has any type other than `string`.
+
+## Examples
+
+**Incorrect** code for this rule:
+
+```proto
+// Incorrect.
+// Should include a `string tag` field.
+message TagBookRevisionRequest {
+  string name = 1 [
+    (google.api.field_behavior) = REQUIRED,
+    (google.api.resource_reference).type = "library.googleapis.com/Book"
+  ];
+}
+```
+
+```proto
+// Incorrect.
+message TagBookRevisionRequest {
+  string name = 1 [
+    (google.api.field_behavior) = REQUIRED,
+    (google.api.resource_reference).type = "library.googleapis.com/Book"
+  ];
+
+  // Field type should be `string`.
+  bytes tag = 2 [(google.api.field_behavior) = REQUIRED];
+}
+```
+
+**Correct** code for this rule:
+
+```proto
+// Correct.
+message TagBookRevisionRequest {
+  string name = 1 [
+    (google.api.field_behavior) = REQUIRED,
+    (google.api.resource_reference).type = "library.googleapis.com/Book"
+  ];
+
+  string tag = 2 [(google.api.field_behavior) = REQUIRED];
+}
+```
+
+## Disabling
+
+If you need to violate this rule, use a leading comment above the message (if
+the `tag` 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
+message TagBookRevisionRequest {
+  string name = 1 [
+    (google.api.field_behavior) = REQUIRED,
+    (google.api.resource_reference).type = "library.googleapis.com/Book"
+  ];
+
+  // (-- api-linter: core::0162::tag-revision-request-tag-field=disabled
+  //     aip.dev/not-precedent: We need to do this because reasons. --)
+  bytes tag = 2 [(google.api.field_behavior) = REQUIRED];
+}
+```
+
+If you need to violate this rule for an entire file, place the comment at the
+top of the file.
+
+[aip-162]: https://aip.dev/162
+[aip.dev/not-precedent]: https://aip.dev/not-precedent
diff --git a/docs/rules/0162/tag-revision-response-message-name.md b/docs/rules/0162/tag-revision-response-message-name.md
new file mode 100644
index 0000000..bb024ae
--- /dev/null
+++ b/docs/rules/0162/tag-revision-response-message-name.md
@@ -0,0 +1,69 @@
+---
+rule:
+  aip: 162
+  name: [core, '0162', tag-revision-response-message-name]
+  summary: Tag Revision methods must return a long-running operation.
+permalink: /162/tag-revision-response-message-name
+redirect_from:
+  - /0162/tag-revision-response-message-name
+---
+
+# Tag Revision methods: Response message
+
+This rule enforces that all Tag Revision RPCs have a response message of the
+resource, as mandated in [AIP-162][].
+
+## Details
+
+This rule looks at any method matching `Tag*Revision`, and complains
+if the name of the corresponding output message does not match the name of the
+RPC with the prefix `Tag` and suffix `Revision` removed.
+
+## Examples
+
+**Incorrect** code for this rule:
+
+```proto
+// Incorrect.
+// Should return `Book`.
+rpc TagBookRevision(TagBookRevisionRequest) returns (TagBookRevisionResponse) {
+  option (google.api.http) = {
+    post: "/v1/{name=publishers/*/books/*}:tagRevision"
+    body: "*"
+  };
+}
+```
+
+**Correct** code for this rule:
+
+```proto
+// Correct.
+rpc TagBookRevision(TagBookRevisionRequest) returns (Book) {
+  option (google.api.http) = {
+    post: "/v1/{name=publishers/*/books/*}:tagRevision"
+    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::0162::tag-revision-response-message-name=disabled
+//     aip.dev/not-precedent: We need to do this because reasons. --)
+rpc TagBookRevision(TagBookRevisionRequest) returns (TagBookRevisionResponse) {
+  option (google.api.http) = {
+    post: "/v1/{name=publishers/*/books/*}:tagRevision"
+    body: "*"
+  };
+}
+```
+
+If you need to violate this rule for an entire file, place the comment at the
+top of the file.
+
+[aip-162]: https://aip.dev/162
+[aip.dev/not-precedent]: https://aip.dev/not-precedent
diff --git a/rules/aip0162/aip0162.go b/rules/aip0162/aip0162.go
new file mode 100644
index 0000000..aa755cb
--- /dev/null
+++ b/rules/aip0162/aip0162.go
@@ -0,0 +1,55 @@
+// Copyright 2021 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 aip0162 contains rules defined in https://aip.dev/162.
+package aip0162
+
+import (
+	"regexp"
+
+	"github.com/googleapis/api-linter/lint"
+	"github.com/jhump/protoreflect/desc"
+)
+
+// AddRules accepts a register function and registers each of
+// this AIP's rules to it.
+func AddRules(r lint.RuleRegistry) error {
+	return r.Register(
+		162,
+		tagRevisionHTTPBody,
+		tagRevisionHTTPMethod,
+		tagRevisionHTTPURISuffix,
+		tagRevisionRequestMessageName,
+		tagRevisionRequestNameBehavior,
+		tagRevisionRequestNameField,
+		tagRevisionRequestNameReference,
+		tagRevisionRequestTagBehavior,
+		tagRevisionRequestTagField,
+		tagRevisionResponseMessageName,
+	)
+}
+
+var tagRevisionMethodRegexp = regexp.MustCompile(`^Tag([A-Za-z0-9]+)Revision$`)
+var tagRevisionReqMessageRegexp = regexp.MustCompile(`^Tag(?:[A-Za-z0-9]+)RevisionRequest$`)
+var tagRevisionURINameRegexp = regexp.MustCompile(`:tagRevision$`)
+
+// Returns true if this is an AIP-162 Tag Revision method, false otherwise.
+func isTagRevisionMethod(m *desc.MethodDescriptor) bool {
+	return tagRevisionMethodRegexp.MatchString(m.GetName())
+}
+
+// Returns true if this is an AIP-162 Tag Revision request message, false otherwise.
+func isTagRevisionRequestMessage(m *desc.MessageDescriptor) bool {
+	return tagRevisionReqMessageRegexp.MatchString(m.GetName())
+}
diff --git a/rules/aip0162/tag_revision_http_body.go b/rules/aip0162/tag_revision_http_body.go
new file mode 100644
index 0000000..daa054e
--- /dev/null
+++ b/rules/aip0162/tag_revision_http_body.go
@@ -0,0 +1,27 @@
+// Copyright 2021 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 aip0162
+
+import (
+	"github.com/googleapis/api-linter/lint"
+	"github.com/googleapis/api-linter/rules/internal/utils"
+)
+
+// Tag Revision methods should have "*" as the HTTP body.
+var tagRevisionHTTPBody = &lint.MethodRule{
+	Name:       lint.NewRuleName(162, "tag-revision-http-body"),
+	OnlyIf:     isTagRevisionMethod,
+	LintMethod: utils.LintWildcardHTTPBody,
+}
diff --git a/rules/aip0162/tag_revision_http_method.go b/rules/aip0162/tag_revision_http_method.go
new file mode 100644
index 0000000..b1c0a2b
--- /dev/null
+++ b/rules/aip0162/tag_revision_http_method.go
@@ -0,0 +1,27 @@
+// Copyright 2021 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 aip0162
+
+import (
+	"github.com/googleapis/api-linter/lint"
+	"github.com/googleapis/api-linter/rules/internal/utils"
+)
+
+// Tag Revision methods should use the HTTP POST method.
+var tagRevisionHTTPMethod = &lint.MethodRule{
+	Name:       lint.NewRuleName(162, "tag-revision-http-method"),
+	OnlyIf:     isTagRevisionMethod,
+	LintMethod: utils.LintHTTPMethod("POST"),
+}
diff --git a/rules/aip0162/tag_revision_http_uri_suffix.go b/rules/aip0162/tag_revision_http_uri_suffix.go
new file mode 100644
index 0000000..46a9580
--- /dev/null
+++ b/rules/aip0162/tag_revision_http_uri_suffix.go
@@ -0,0 +1,39 @@
+// Copyright 2021 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 aip0162
+
+import (
+	"github.com/googleapis/api-linter/lint"
+	"github.com/googleapis/api-linter/rules/internal/utils"
+	"github.com/jhump/protoreflect/desc"
+)
+
+// Tag Revision methods should have a proper HTTP pattern.
+var tagRevisionHTTPURISuffix = &lint.MethodRule{
+	Name:   lint.NewRuleName(162, "tag-revision-http-uri-suffix"),
+	OnlyIf: isTagRevisionMethod,
+	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
+		for _, httpRule := range utils.GetHTTPRules(m) {
+			if !tagRevisionURINameRegexp.MatchString(httpRule.URI) {
+				return []lint.Problem{{
+					Message:    `Tag Revision URI should end with ":tagRevision".`,
+					Descriptor: m,
+				}}
+			}
+		}
+
+		return nil
+	},
+}
diff --git a/rules/aip0162/tag_revision_request_message_name.go b/rules/aip0162/tag_revision_request_message_name.go
new file mode 100644
index 0000000..aac28ce
--- /dev/null
+++ b/rules/aip0162/tag_revision_request_message_name.go
@@ -0,0 +1,27 @@
+// Copyright 2021 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 aip0162
+
+import (
+	"github.com/googleapis/api-linter/lint"
+	"github.com/googleapis/api-linter/rules/internal/utils"
+)
+
+// Tag Revision messages should have a properly named request message.
+var tagRevisionRequestMessageName = &lint.MethodRule{
+	Name:       lint.NewRuleName(162, "tag-revision-request-message-name"),
+	OnlyIf:     isTagRevisionMethod,
+	LintMethod: utils.LintMethodHasMatchingRequestName,
+}
diff --git a/rules/aip0162/tag_revision_request_name_behavior.go b/rules/aip0162/tag_revision_request_name_behavior.go
new file mode 100644
index 0000000..fc6adde
--- /dev/null
+++ b/rules/aip0162/tag_revision_request_name_behavior.go
@@ -0,0 +1,29 @@
+// Copyright 2021 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 aip0162
+
+import (
+	"github.com/googleapis/api-linter/lint"
+	"github.com/googleapis/api-linter/rules/internal/utils"
+	"github.com/jhump/protoreflect/desc"
+)
+
+var tagRevisionRequestNameBehavior = &lint.FieldRule{
+	Name: lint.NewRuleName(162, "tag-revision-request-name-behavior"),
+	OnlyIf: func(f *desc.FieldDescriptor) bool {
+		return isTagRevisionRequestMessage(f.GetOwner()) && f.GetName() == "name"
+	},
+	LintField: utils.LintRequiredField,
+}
diff --git a/rules/aip0162/tag_revision_request_name_field.go b/rules/aip0162/tag_revision_request_name_field.go
new file mode 100644
index 0000000..ada4276
--- /dev/null
+++ b/rules/aip0162/tag_revision_request_name_field.go
@@ -0,0 +1,52 @@
+// Copyright 2021 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 aip0162
+
+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 Tag Revision request message should have a name field.
+var tagRevisionRequestNameField = &lint.MessageRule{
+	Name:   lint.NewRuleName(162, "tag-revision-request-name-field"),
+	OnlyIf: isTagRevisionRequestMessage,
+	LintMessage: func(m *desc.MessageDescriptor) []lint.Problem {
+		// Rule check: Establish that a `name` field is present.
+		nameField := m.FindFieldByName("name")
+		if nameField == nil {
+			return []lint.Problem{{
+				Message:    fmt.Sprintf("Message %q has no `name` field.", m.GetName()),
+				Descriptor: m,
+			}}
+		}
+
+		// Rule check: Establish that the name field is a string.
+		if nameField.GetType() != builder.FieldTypeString().GetType() || nameField.IsRepeated() {
+			return []lint.Problem{{
+				Message:    "`name` field on Tag Revision request message must be a singular string.",
+				Descriptor: nameField,
+				Location:   locations.FieldType(nameField),
+				Suggestion: "string",
+			}}
+		}
+
+		return nil
+	},
+}
diff --git a/rules/aip0162/tag_revision_request_name_reference.go b/rules/aip0162/tag_revision_request_name_reference.go
new file mode 100644
index 0000000..f4d2dd8
--- /dev/null
+++ b/rules/aip0162/tag_revision_request_name_reference.go
@@ -0,0 +1,29 @@
+// Copyright 2021 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 aip0162
+
+import (
+	"github.com/googleapis/api-linter/lint"
+	"github.com/googleapis/api-linter/rules/internal/utils"
+	"github.com/jhump/protoreflect/desc"
+)
+
+var tagRevisionRequestNameReference = &lint.FieldRule{
+	Name: lint.NewRuleName(162, "tag-revision-request-name-reference"),
+	OnlyIf: func(f *desc.FieldDescriptor) bool {
+		return isTagRevisionRequestMessage(f.GetOwner()) && f.GetName() == "name"
+	},
+	LintField: utils.LintFieldResourceReference,
+}
diff --git a/rules/aip0162/tag_revision_request_tag_behavior.go b/rules/aip0162/tag_revision_request_tag_behavior.go
new file mode 100644
index 0000000..12e6425
--- /dev/null
+++ b/rules/aip0162/tag_revision_request_tag_behavior.go
@@ -0,0 +1,29 @@
+// Copyright 2021 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 aip0162
+
+import (
+	"github.com/googleapis/api-linter/lint"
+	"github.com/googleapis/api-linter/rules/internal/utils"
+	"github.com/jhump/protoreflect/desc"
+)
+
+var tagRevisionRequestTagBehavior = &lint.FieldRule{
+	Name: lint.NewRuleName(162, "tag-revision-request-tag-behavior"),
+	OnlyIf: func(f *desc.FieldDescriptor) bool {
+		return isTagRevisionRequestMessage(f.GetOwner()) && f.GetName() == "tag"
+	},
+	LintField: utils.LintRequiredField,
+}
diff --git a/rules/aip0162/tag_revision_request_tag_field.go b/rules/aip0162/tag_revision_request_tag_field.go
new file mode 100644
index 0000000..0591278
--- /dev/null
+++ b/rules/aip0162/tag_revision_request_tag_field.go
@@ -0,0 +1,52 @@
+// Copyright 2021 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 aip0162
+
+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 Tag Revision request message should have a tag field.
+var tagRevisionRequestTagField = &lint.MessageRule{
+	Name:   lint.NewRuleName(162, "tag-revision-request-tag-field"),
+	OnlyIf: isTagRevisionRequestMessage,
+	LintMessage: func(m *desc.MessageDescriptor) []lint.Problem {
+		// Rule check: Establish that a `tag` field is present.
+		tagField := m.FindFieldByName("tag")
+		if tagField == nil {
+			return []lint.Problem{{
+				Message:    fmt.Sprintf("Message %q has no `tag` field.", m.GetName()),
+				Descriptor: m,
+			}}
+		}
+
+		// Rule check: Establish that the tag field is a string.
+		if tagField.GetType() != builder.FieldTypeString().GetType() || tagField.IsRepeated() {
+			return []lint.Problem{{
+				Message:    "`tag` field on Tag Revision request message must be a singular string.",
+				Descriptor: tagField,
+				Location:   locations.FieldType(tagField),
+				Suggestion: "string",
+			}}
+		}
+
+		return nil
+	},
+}
diff --git a/rules/aip0162/tag_revision_response_message_name.go b/rules/aip0162/tag_revision_response_message_name.go
new file mode 100644
index 0000000..b3a930e
--- /dev/null
+++ b/rules/aip0162/tag_revision_response_message_name.go
@@ -0,0 +1,50 @@
+// Copyright 2021 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 aip0162
+
+import (
+	"fmt"
+
+	"github.com/googleapis/api-linter/lint"
+	"github.com/googleapis/api-linter/locations"
+	"github.com/jhump/protoreflect/desc"
+)
+
+var tagRevisionResponseMessageName = &lint.MethodRule{
+	Name:   lint.NewRuleName(162, "tag-revision-response-message-name"),
+	OnlyIf: isTagRevisionMethod,
+	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
+		// Rule check: Establish that for methods such as `TagBookRevision`, the response
+		// message is `Book`.
+		want := tagRevisionMethodRegexp.FindStringSubmatch(m.GetName())[1]
+		got := m.GetOutputType().GetName()
+
+		// Return a problem if we did not get the expected return name.
+		// would be confusing.
+		if got != want {
+			return []lint.Problem{{
+				Message: fmt.Sprintf(
+					"Tag Revision RPCs should have response message type %q, not %q.",
+					want,
+					got,
+				),
+				Suggestion: want,
+				Descriptor: m,
+				Location:   locations.MethodResponseType(m),
+			}}
+		}
+		return nil
+	},
+}
diff --git a/rules/rules.go b/rules/rules.go
index 14c03bf..2a43719 100644
--- a/rules/rules.go
+++ b/rules/rules.go
@@ -76,6 +76,7 @@ import (
 	"github.com/googleapis/api-linter/rules/aip0156"
 	"github.com/googleapis/api-linter/rules/aip0158"
 	"github.com/googleapis/api-linter/rules/aip0159"
+	"github.com/googleapis/api-linter/rules/aip0162"
 	"github.com/googleapis/api-linter/rules/aip0163"
 	"github.com/googleapis/api-linter/rules/aip0164"
 	"github.com/googleapis/api-linter/rules/aip0165"
@@ -120,6 +121,7 @@ var aipAddRulesFuncs = []addRulesFuncType{
 	aip0156.AddRules,
 	aip0158.AddRules,
 	aip0159.AddRules,
+	aip0162.AddRules,
 	aip0163.AddRules,
 	aip0164.AddRules,
 	aip0165.AddRules,


Test patch:
diff --git a/rules/aip0162/tag_revision_http_body_test.go b/rules/aip0162/tag_revision_http_body_test.go
new file mode 100644
index 0000000..a1f23b1
--- /dev/null
+++ b/rules/aip0162/tag_revision_http_body_test.go
@@ -0,0 +1,57 @@
+// Copyright 2021 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 aip0162
+
+import (
+	"testing"
+
+	"github.com/googleapis/api-linter/rules/internal/testutils"
+)
+
+func TestTagRevisionHTTPBody(t *testing.T) {
+	tests := []struct {
+		testName   string
+		Body       string
+		MethodName string
+		problems   testutils.Problems
+	}{
+		{"Valid", "*", "TagBookRevision", nil},
+		{"Invalid", "", "TagBookRevision", testutils.Problems{{Message: "HTTP body"}}},
+		{"Irrelevant", "", "AcquireBook", nil},
+	}
+
+	for _, test := range tests {
+		t.Run(test.testName, func(t *testing.T) {
+			file := testutils.ParseProto3Tmpl(t, `
+				import "google/api/annotations.proto";
+				service Library {
+					rpc {{.MethodName}}({{.MethodName}}Request) returns (Book) {
+						option (google.api.http) = {
+							post: "/v1/{name=publishers/*/books/*}:tagRevision"
+							body: "{{.Body}}"
+						};
+					}
+				}
+				message Book {}
+				message {{.MethodName}}Request {}
+			`, test)
+			method := file.GetServices()[0].GetMethods()[0]
+			problems := tagRevisionHTTPBody.Lint(file)
+			if diff := test.problems.SetDescriptor(method).Diff(problems); diff != "" {
+				t.Error(diff)
+			}
+		})
+	}
+}
diff --git a/rules/aip0162/tag_revision_http_method_test.go b/rules/aip0162/tag_revision_http_method_test.go
new file mode 100644
index 0000000..1717a54
--- /dev/null
+++ b/rules/aip0162/tag_revision_http_method_test.go
@@ -0,0 +1,58 @@
+// Copyright 2021 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 aip0162
+
+import (
+	"testing"
+
+	"github.com/googleapis/api-linter/rules/internal/testutils"
+)
+
+func TestTagRevisionHTTPMethod(t *testing.T) {
+	// Set up testing permutations.
+	tests := []struct {
+		testName   string
+		Method     string
+		MethodName string
+		problems   testutils.Problems
+	}{
+		{"Valid", "post", "TagBookRevision", nil},
+		{"Invalid", "delete", "TagBookRevision", testutils.Problems{{Message: "HTTP POST"}}},
+		{"Irrelevant", "delete", "AcquireBook", nil},
+	}
+
+	// Run each test.
+	for _, test := range tests {
+		t.Run(test.testName, func(t *testing.T) {
+			file := testutils.ParseProto3Tmpl(t, `
+				import "google/api/annotations.proto";
+				service Library {
+					rpc {{.MethodName}}({{.MethodName}}Request) returns (Book) {
+						option (google.api.http) = {
+							{{.Method}}: "/v1/{name=publishers/*/books/*}:tagRevision"
+						};
+					}
+				}
+				message Book {}
+				message {{.MethodName}}Request {}
+			`, test)
+			method := file.GetServices()[0].GetMethods()[0]
+			problems := tagRevisionHTTPMethod.Lint(file)
+			if diff := test.problems.SetDescriptor(method).Diff(problems); diff != "" {
+				t.Error(diff)
+			}
+		})
+	}
+}
diff --git a/rules/aip0162/tag_revision_http_uri_suffix_test.go b/rules/aip0162/tag_revision_http_uri_suffix_test.go
new file mode 100644
index 0000000..1830c0b
--- /dev/null
+++ b/rules/aip0162/tag_revision_http_uri_suffix_test.go
@@ -0,0 +1,57 @@
+// Copyright 2021 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 aip0162
+
+import (
+	"testing"
+
+	"github.com/googleapis/api-linter/rules/internal/testutils"
+)
+
+func TestTagRevisionHTTPURISuffix(t *testing.T) {
+	tests := []struct {
+		testName   string
+		URI        string
+		MethodName string
+		problems   testutils.Problems
+	}{
+		{"Valid", "/v1/{name=publishers/*/books/*}:tagRevision", "TagBookRevision", nil},
+		{"InvalidSuffix", "/v1/{name=publishers/*/books/*}:tag", "TagBookRevision", testutils.Problems{{Message: ":tagRevision"}}},
+		{"Irrelevant", "/v1/{name=publishers/*/books/*}:tag", "AcquireBook", nil},
+	}
+
+	for _, test := range tests {
+		t.Run(test.testName, func(t *testing.T) {
+			file := testutils.ParseProto3Tmpl(t, `
+				import "google/api/annotations.proto";
+				service Library {
+					rpc {{.MethodName}}({{.MethodName}}Request) returns (Book) {
+						option (google.api.http) = {
+							post: "{{.URI}}"
+							body: "*"
+						};
+					}
+				}
+				message Book {}
+				message {{.MethodName}}Request {}
+				`, test)
+
+			problems := tagRevisionHTTPURISuffix.Lint(file)
+			if diff := test.problems.SetDescriptor(file.GetServices()[0].GetMethods()[0]).Diff(problems); diff != "" {
+				t.Errorf(diff)
+			}
+		})
+	}
+}
diff --git a/rules/aip0162/tag_revision_request_message_name_test.go b/rules/aip0162/tag_revision_request_message_name_test.go
new file mode 100644
index 0000000..fb17d4c
--- /dev/null
+++ b/rules/aip0162/tag_revision_request_message_name_test.go
@@ -0,0 +1,51 @@
+// Copyright 2021 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 aip0162
+
+import (
+	"testing"
+
+	"github.com/googleapis/api-linter/rules/internal/testutils"
+)
+
+func TestTagRevisionRequestMessageName(t *testing.T) {
+	// Set up the testing permutations.
+	tests := []struct {
+		testName       string
+		MethodName     string
+		ReqMessageName string
+		problems       testutils.Problems
+	}{
+		{"Valid", "TagBookRevision", "TagBookRevisionRequest", nil},
+		{"Invalid", "TagBookRevision", "TagBookRequest", testutils.Problems{{Suggestion: "TagBookRevisionRequest"}}},
+		{"Irrelevant", "AcquireBook", "GetBookRequest", nil},
+	}
+
+	for _, test := range tests {
+		t.Run(test.testName, func(t *testing.T) {
+			f := testutils.ParseProto3Tmpl(t, `
+				service Library {
+					rpc {{.MethodName}}({{.ReqMessageName}}) returns (Book) {}
+				}
+				message Book {}
+				message {{.ReqMessageName}} {}
+			`, test)
+			m := f.GetServices()[0].GetMethods()[0]
+			if diff := test.problems.SetDescriptor(m).Diff(tagRevisionRequestMessageName.Lint(f)); diff != "" {
+				t.Errorf(diff)
+			}
+		})
+	}
+}
diff --git a/rules/aip0162/tag_revision_request_name_behavior_test.go b/rules/aip0162/tag_revision_request_name_behavior_test.go
new file mode 100644
index 0000000..a8fec27
--- /dev/null
+++ b/rules/aip0162/tag_revision_request_name_behavior_test.go
@@ -0,0 +1,49 @@
+// Copyright 2021 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 aip0162
+
+import (
+	"testing"
+
+	"github.com/googleapis/api-linter/rules/internal/testutils"
+)
+
+func TestTagRevisionRequestNameBehavior(t *testing.T) {
+	for _, test := range []struct {
+		name      string
+		RPC       string
+		Field     string
+		FieldOpts string
+		problems  testutils.Problems
+	}{
+		{"Valid", "TagBookRevision", "name", " [(google.api.field_behavior) = REQUIRED]", nil},
+		{"Missing", "TagBookRevision", "name", "", testutils.Problems{{Message: "(google.api.field_behavior) = REQUIRED"}}},
+		{"IrrelevantMessage", "PurgeBooks", "name", "", nil},
+		{"IrrelevantField", "TagBookRevision", "something_else", "", nil},
+	} {
+		t.Run(test.name, func(t *testing.T) {
+			f := testutils.ParseProto3Tmpl(t, `
+				import "google/api/field_behavior.proto";
+				message {{.RPC}}Request {
+					string {{.Field}} = 1{{.FieldOpts}};
+				}
+			`, test)
+			field := f.GetMessageTypes()[0].GetFields()[0]
+			if diff := test.problems.SetDescriptor(field).Diff(tagRevisionRequestNameBehavior.Lint(f)); diff != "" {
+				t.Errorf(diff)
+			}
+		})
+	}
+}
diff --git a/rules/aip0162/tag_revision_request_name_field_test.go b/rules/aip0162/tag_revision_request_name_field_test.go
new file mode 100644
index 0000000..03c54eb
--- /dev/null
+++ b/rules/aip0162/tag_revision_request_name_field_test.go
@@ -0,0 +1,51 @@
+// Copyright 2021 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 aip0162
+
+import (
+	"testing"
+
+	"github.com/googleapis/api-linter/rules/internal/testutils"
+	"github.com/jhump/protoreflect/desc"
+)
+
+func TestTagRevisionRequestNameField(t *testing.T) {
+	for _, test := range []struct {
+		name     string
+		RPC      string
+		Field    string
+		problems testutils.Problems
+	}{
+		{"Valid", "TagBookRevision", "string name = 1;", nil},
+		{"Missing", "TagBookRevision", "", testutils.Problems{{Message: "no `name`"}}},
+		{"InvalidType", "TagBookRevision", "int32 name = 1;", testutils.Problems{{Suggestion: "string"}}},
+		{"IrrelevantRPCName", "EnumerateBooks", "", nil},
+	} {
+		t.Run(test.name, func(t *testing.T) {
+			f := testutils.ParseProto3Tmpl(t, `
+				message {{.RPC}}Request {
+					{{.Field}}
+				}
+			`, test)
+			var d desc.Descriptor = f.GetMessageTypes()[0]
+			if test.name == "InvalidType" {
+				d = f.GetMessageTypes()[0].GetFields()[0]
+			}
+			if diff := test.problems.SetDescriptor(d).Diff(tagRevisionRequestNameField.Lint(f)); diff != "" {
+				t.Errorf(diff)
+			}
+		})
+	}
+}
diff --git a/rules/aip0162/tag_revision_request_name_reference_test.go b/rules/aip0162/tag_revision_request_name_reference_test.go
new file mode 100644
index 0000000..ebd03a7
--- /dev/null
+++ b/rules/aip0162/tag_revision_request_name_reference_test.go
@@ -0,0 +1,49 @@
+// Copyright 2021 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 aip0162
+
+import (
+	"testing"
+
+	"github.com/googleapis/api-linter/rules/internal/testutils"
+)
+
+func TestTagRevisionRequestNameReference(t *testing.T) {
+	for _, test := range []struct {
+		name      string
+		RPC       string
+		Field     string
+		FieldOpts string
+		problems  testutils.Problems
+	}{
+		{"Valid", "TagBookRevision", "name", ` [(google.api.resource_reference) = { type: "foo" }]`, nil},
+		{"Missing", "TagBookRevision", "name", "", testutils.Problems{{Message: "google.api.resource_reference"}}},
+		{"IrrelevantMessage", "PurgeBooks", "name", "", nil},
+		{"IrrelevantField", "TagBookRevision", "names", "", nil},
+	} {
+		t.Run(test.name, func(t *testing.T) {
+			f := testutils.ParseProto3Tmpl(t, `
+				import "google/api/resource.proto";
+				message {{.RPC}}Request {
+					repeated string {{.Field}} = 1{{.FieldOpts}};
+				}
+			`, test)
+			field := f.GetMessageTypes()[0].GetFields()[0]
+			if diff := test.problems.SetDescriptor(field).Diff(tagRevisionRequestNameReference.Lint(f)); diff != "" {
+				t.Errorf(diff)
+			}
+		})
+	}
+}
diff --git a/rules/aip0162/tag_revision_request_tag_behavior_test.go b/rules/aip0162/tag_revision_request_tag_behavior_test.go
new file mode 100644
index 0000000..8eea5ae
--- /dev/null
+++ b/rules/aip0162/tag_revision_request_tag_behavior_test.go
@@ -0,0 +1,49 @@
+// Copyright 2021 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 aip0162
+
+import (
+	"testing"
+
+	"github.com/googleapis/api-linter/rules/internal/testutils"
+)
+
+func TestTagRevisionRequestTagBehavior(t *testing.T) {
+	for _, test := range []struct {
+		name      string
+		RPC       string
+		Field     string
+		FieldOpts string
+		problems  testutils.Problems
+	}{
+		{"Valid", "TagBookRevision", "tag", " [(google.api.field_behavior) = REQUIRED]", nil},
+		{"Missing", "TagBookRevision", "tag", "", testutils.Problems{{Message: "(google.api.field_behavior) = REQUIRED"}}},
+		{"IrrelevantMessage", "PurgeBooks", "tag", "", nil},
+		{"IrrelevantField", "TagBookRevision", "something_else", "", nil},
+	} {
+		t.Run(test.name, func(t *testing.T) {
+			f := testutils.ParseProto3Tmpl(t, `
+				import "google/api/field_behavior.proto";
+				message {{.RPC}}Request {
+					string {{.Field}} = 1{{.FieldOpts}};
+				}
+			`, test)
+			field := f.GetMessageTypes()[0].GetFields()[0]
+			if diff := test.problems.SetDescriptor(field).Diff(tagRevisionRequestTagBehavior.Lint(f)); diff != "" {
+				t.Errorf(diff)
+			}
+		})
+	}
+}
diff --git a/rules/aip0162/tag_revision_request_tag_field_test.go b/rules/aip0162/tag_revision_request_tag_field_test.go
new file mode 100644
index 0000000..fa69cee
--- /dev/null
+++ b/rules/aip0162/tag_revision_request_tag_field_test.go
@@ -0,0 +1,51 @@
+// Copyright 2021 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 aip0162
+
+import (
+	"testing"
+
+	"github.com/googleapis/api-linter/rules/internal/testutils"
+	"github.com/jhump/protoreflect/desc"
+)
+
+func TestTagRevisionRequestTagField(t *testing.T) {
+	for _, test := range []struct {
+		name     string
+		RPC      string
+		Field    string
+		problems testutils.Problems
+	}{
+		{"Valid", "TagBookRevision", "string tag = 1;", nil},
+		{"Missing", "TagBookRevision", "", testutils.Problems{{Message: "no `tag`"}}},
+		{"InvalidType", "TagBookRevision", "int32 tag = 1;", testutils.Problems{{Suggestion: "string"}}},
+		{"IrrelevantRPCName", "EnumerateBooks", "", nil},
+	} {
+		t.Run(test.name, func(t *testing.T) {
+			f := testutils.ParseProto3Tmpl(t, `
+				message {{.RPC}}Request {
+					{{.Field}}
+				}
+			`, test)
+			var d desc.Descriptor = f.GetMessageTypes()[0]
+			if test.name == "InvalidType" {
+				d = f.GetMessageTypes()[0].GetFields()[0]
+			}
+			if diff := test.problems.SetDescriptor(d).Diff(tagRevisionRequestTagField.Lint(f)); diff != "" {
+				t.Errorf(diff)
+			}
+		})
+	}
+}
diff --git a/rules/aip0162/tag_revision_response_message_name_test.go b/rules/aip0162/tag_revision_response_message_name_test.go
new file mode 100644
index 0000000..25bbd2f
--- /dev/null
+++ b/rules/aip0162/tag_revision_response_message_name_test.go
@@ -0,0 +1,49 @@
+// Copyright 2021 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 aip0162
+
+import (
+	"testing"
+
+	"github.com/googleapis/api-linter/rules/internal/testutils"
+)
+
+func TestTagRevisionResponseMessageName(t *testing.T) {
+	for _, test := range []struct {
+		name         string
+		Method       string
+		ResponseType string
+		problems     testutils.Problems
+	}{
+		{"Valid", "TagBookRevision", "Book", nil},
+		{"Invalid", "TagBookRevision", "TagBookRevisionResponse", testutils.Problems{{Suggestion: "Book"}}},
+		{"Irrelevant", "AcquireBook", "PurgeBooksResponse", nil},
+	} {
+		t.Run(test.name, func(t *testing.T) {
+			f := testutils.ParseProto3Tmpl(t, `
+				import "google/longrunning/operations.proto";
+				service Library {
+					rpc {{.Method}}(TagBookRevisionRequest) returns ({{.ResponseType}});
+				}
+				message TagBookRevisionRequest {}
+				message {{.ResponseType}} {}
+			`, test)
+			m := f.GetServices()[0].GetMethods()[0]
+			if diff := test.problems.SetDescriptor(m).Diff(tagRevisionResponseMessageName.Lint(f)); diff != "" {
+				t.Errorf(diff)
+			}
+		})
+	}
+}