diff --git a/docs/rules/0162/commit-http-uri-suffix.md b/docs/rules/0162/commit-http-uri-suffix.md new file mode 100644 index 000000000..f4cce3d41 --- /dev/null +++ b/docs/rules/0162/commit-http-uri-suffix.md @@ -0,0 +1,67 @@ +--- +rule: + aip: 162 + name: [core, '0162', commit-http-uri-suffix] + summary: Commit methods must have the correct URI suffix +permalink: /162/commit-http-uri-suffix +redirect_from: + - /0162/commit-http-uri-suffix +--- + +# Commit methods: URI suffix + +This rule enforces that `Commit` methods include the `:commit` suffix +in the REST URI, as mandated in [AIP-162][]. + +## Details + +This rule looks at any method beginning with `Commit`, and +complains if the HTTP URI does not end with `:commit`. + +## Examples + +**Incorrect** code for this rule: + +```proto +// Incorrect. +rpc CommitBook(CommitBookRequest) returns (Book) { + option (google.api.http) = { + post: "/v1/{name=publishers/*/books/*}:save" // Should end with `:commit` + body: "*" + }; +} +``` + +**Correct** code for this rule: + +```proto +// Correct. +rpc CommitBook(CommitBookRequest) returns (Book) { + option (google.api.http) = { + post: "/v1/{name=publishers/*/books/*}:commit" + 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::commit-http-uri-suffix=disabled +// aip.dev/not-precedent: We need to do this because reasons. --) +rpc Commit(CommitBookRequest) returns (Book) { + option (google.api.http) = { + post: "/v1/{name=publishers/*/books/*}:save" + 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 index aa755cb8d..45c6fe389 100644 --- a/rules/aip0162/aip0162.go +++ b/rules/aip0162/aip0162.go @@ -27,6 +27,7 @@ import ( func AddRules(r lint.RuleRegistry) error { return r.Register( 162, + commitHTTPURISuffix, tagRevisionHTTPBody, tagRevisionHTTPMethod, tagRevisionHTTPURISuffix, @@ -53,3 +54,11 @@ func isTagRevisionMethod(m *desc.MethodDescriptor) bool { func isTagRevisionRequestMessage(m *desc.MessageDescriptor) bool { return tagRevisionReqMessageRegexp.MatchString(m.GetName()) } + +var commitMethodRegexp = regexp.MustCompile(`^Commit(?:[A-Za-z0-9]+)$`) +var commitURINameRegexp = regexp.MustCompile(`:commit$`) + +// Returns true if this is an AIP-162 Commit method, false otherwise. +func isCommitMethod(m *desc.MethodDescriptor) bool { + return commitMethodRegexp.MatchString(m.GetName()) +} diff --git a/rules/aip0162/commit_http_uri_suffix.go b/rules/aip0162/commit_http_uri_suffix.go new file mode 100644 index 000000000..7bba9437c --- /dev/null +++ b/rules/aip0162/commit_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" +) + +// Commit methods should have a proper HTTP pattern. +var commitHTTPURISuffix = &lint.MethodRule{ + Name: lint.NewRuleName(162, "commit-http-uri-suffix"), + OnlyIf: isCommitMethod, + LintMethod: func(m *desc.MethodDescriptor) []lint.Problem { + for _, httpRule := range utils.GetHTTPRules(m) { + if !commitURINameRegexp.MatchString(httpRule.URI) { + return []lint.Problem{{ + Message: `Commit URI should end with ":commit".`, + Descriptor: m, + }} + } + } + + return nil + }, +} diff --git a/rules/aip0162/commit_http_uri_suffix_test.go b/rules/aip0162/commit_http_uri_suffix_test.go new file mode 100644 index 000000000..2d222e8a7 --- /dev/null +++ b/rules/aip0162/commit_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 TestCommitHTTPURISuffix(t *testing.T) { + tests := []struct { + testName string + URI string + MethodName string + problems testutils.Problems + }{ + {"Valid", "/v1/{name=publishers/*/books/*}:commit", "CommitBook", nil}, + {"InvalidSuffix", "/v1/{name=publishers/*/books/*}:save", "CommitBook", testutils.Problems{{Message: ":commit"}}}, + {"Irrelevant", "/v1/{name=publishers/*/books/*}:save", "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 := commitHTTPURISuffix.Lint(file) + if diff := test.problems.SetDescriptor(file.GetServices()[0].GetMethods()[0]).Diff(problems); diff != "" { + t.Errorf(diff) + } + }) + } +}