diff --git a/docs/rules/0162/list-revisions-request-name-field.md b/docs/rules/0162/list-revisions-request-name-field.md new file mode 100644 index 000000000..77f79af6e --- /dev/null +++ b/docs/rules/0162/list-revisions-request-name-field.md @@ -0,0 +1,91 @@ +--- +rule: + aip: 162 + name: [core, '0162', list-revisions-request-name-field] + summary: List Revisions RPCs must have a `name` field in the request. +permalink: /162/list-revisions-request-name-field +redirect_from: + - /0162/list-revisions-request-name-field +--- + +# List Revisions requests: Name field + +This rule enforces that all List Revisions methods have a `string name` +field in the request message, as mandated in [AIP-162][]. + +## Details + +This rule looks at any message matching `List*RevisionsRequest` 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 ListBookRevisionsRequest { + int32 page_size = 1; + + string page_token = 2; +} +``` + +```proto +// Incorrect. +message ListBookRevisionsRequest { + // Field type should be `string`. + bytes name = 1 [ + (google.api.field_behavior) = REQUIRED, + (google.api.resource_reference).type = "library.googleapis.com/Book" + ]; + + int32 page_size = 2; + + string page_token = 3; +} +``` + +**Correct** code for this rule: + +```proto +// Correct. +message ListBookRevisionsRequest { + string name = 1 [ + (google.api.field_behavior) = REQUIRED, + (google.api.resource_reference).type = "library.googleapis.com/Book" + ]; + + int32 page_size = 2; + + string page_token = 3; +} +``` + +## 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 ListBookRevisionsRequest { + // (-- api-linter: core::0162::list-revisions-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" + ]; + + int32 page_size = 2; + + string page_token = 3; +} +``` + +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 index dac46cc8a..ee39e0cfb 100644 --- a/rules/aip0162/aip0162.go +++ b/rules/aip0162/aip0162.go @@ -46,6 +46,7 @@ func AddRules(r lint.RuleRegistry) error { listRevisionsHTTPMethod, listRevisionsHTTPURISuffix, listRevisionsRequestMessageName, + listRevisionsRequestNameField, listRevisionsResponseMessageName, rollbackHTTPBody, rollbackHTTPMethod, diff --git a/rules/aip0162/list_revisions_request_name_field.go b/rules/aip0162/list_revisions_request_name_field.go new file mode 100644 index 000000000..5b0cd594b --- /dev/null +++ b/rules/aip0162/list_revisions_request_name_field.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" +) + +// The List Revisions request message should have a name field. +var listRevisionsRequestNameField = &lint.MessageRule{ + Name: lint.NewRuleName(162, "list-revisions-request-name-field"), + OnlyIf: IsListRevisionsRequestMessage, + LintMessage: utils.LintFieldPresentAndSingularString("name"), +} diff --git a/rules/aip0162/list_revisions_request_name_field_test.go b/rules/aip0162/list_revisions_request_name_field_test.go new file mode 100644 index 000000000..f15710ac9 --- /dev/null +++ b/rules/aip0162/list_revisions_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 TestListRevisionsRequestNameField(t *testing.T) { + for _, test := range []struct { + name string + RPC string + Field string + problems testutils.Problems + }{ + {"Valid", "ListBookRevisions", "string name = 1;", nil}, + {"Missing", "ListBookRevisions", "", testutils.Problems{{Message: "no `name`"}}}, + {"InvalidType", "ListBookRevisions", "int32 name = 1;", testutils.Problems{{Suggestion: "string"}}}, + {"IrrelevantRPCName", "ListBooks", "", 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(listRevisionsRequestNameField.Lint(f)); diff != "" { + t.Error(diff) + } + }) + } +}