Repository: googleapis/api-linter
Pull request #445 (2019-12-11 19:43:35)

[fix] Allow the resource to be the HTTP body on custom methods.


Patch:
diff --git a/docs/rules/0136/http-body.md b/docs/rules/0136/http-body.md
index 73ca019..eef8c4a 100644
--- a/docs/rules/0136/http-body.md
+++ b/docs/rules/0136/http-body.md
@@ -11,12 +11,13 @@ redirect_from:
 # Custom methods: HTTP body
 
 This rule enforces that all custom methods set the HTTP `body` to `*`, as
-mandated in [AIP-136][].
+advised in [AIP-136][].
 
 ## Details
 
-This rule looks at any method that is not a standard method, and complains 
-if the HTTP `body` field is not set to the resource being created.
+This rule looks at any method that is not a standard method, and complains if
+the HTTP `body` field is not set to `"*"`. It also permits the name of the
+resource.
 
 ## Examples
 
@@ -27,7 +28,7 @@ if the HTTP `body` field is not set to the resource being created.
 rpc CheckoutBook(CheckoutBookRequest) returns (CheckoutBookResponse) {
   option (google.api.http) = {
     post: "/v1/{name=publishers/*/books}:checkout"
-    body: "book"  // This should be "*".
+    // `body: "*"` should be included.
   };
 }
 ```
@@ -55,7 +56,6 @@ Remember to also include an [aip.dev/not-precedent][] comment explaining why.
 rpc CheckoutBook(CheckoutBookRequest) returns (CheckoutBookResponse) {
   option (google.api.http) = {
     post: "/v1/{name=publishers/*/books}:checkout"
-    body: "book"
   };
 }
 ```
diff --git a/rules/aip0136/http_body.go b/rules/aip0136/http_body.go
index 945235a..816ea3f 100644
--- a/rules/aip0136/http_body.go
+++ b/rules/aip0136/http_body.go
@@ -15,10 +15,13 @@
 package aip0136
 
 import (
+	"strings"
+
 	"bitbucket.org/creachadair/stringset"
 	"github.com/googleapis/api-linter/lint"
 	"github.com/googleapis/api-linter/rules/internal/utils"
 	"github.com/jhump/protoreflect/desc"
+	"github.com/stoewer/go-strcase"
 )
 
 var httpBody = &lint.MethodRule{
@@ -27,14 +30,20 @@ var httpBody = &lint.MethodRule{
 	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
 		for _, httpRule := range utils.GetHTTPRules(m) {
 			noBody := stringset.New("GET", "DELETE")
-			if !noBody.Contains(httpRule.Method) && httpRule.Body != "*" {
-				// FIXME: We intentionally only return one Problem here.
-				// When we can attach problems to the particular annotation, update this
-				// to return multiples.
-				return []lint.Problem{{
-					Message:    "Custom POST methods should set `body: \"*\"`.",
-					Descriptor: m,
-				}}
+			if !noBody.Contains(httpRule.Method) {
+				// Determine the name of the resource.
+				// This entails some guessing; we assume that the verb is a single
+				// word and that the resource is everything else.
+				resource := strings.Join(strings.Split(strcase.SnakeCase(m.GetName()), "_")[1:], "_")
+				if !stringset.New(resource, "*").Contains(httpRule.Body) {
+					// FIXME: We intentionally only return one Problem here.
+					// When we can attach problems to the particular annotation, update this
+					// to return multiples.
+					return []lint.Problem{{
+						Message:    "Custom POST methods should set `body: \"*\"`.",
+						Descriptor: m,
+					}}
+				}
 			} else if noBody.Contains(httpRule.Method) && httpRule.Body != "" {
 				// FIXME: We intentionally only return one Problem here.
 				// When we can attach problems to the particular annotation, update this


Test patch:
diff --git a/rules/aip0136/http_body_test.go b/rules/aip0136/http_body_test.go
index 0253ce5..b146528 100644
--- a/rules/aip0136/http_body_test.go
+++ b/rules/aip0136/http_body_test.go
@@ -29,7 +29,8 @@ func TestHttpBody(t *testing.T) {
 		problems   testutils.Problems
 	}{
 		{"ValidPostBody", "ArchiveBook", "post", "*", testutils.Problems{}},
-		{"InvalidPostBody", "ArchiveBook", "post", "book", testutils.Problems{{Message: `body: "*"`}}},
+		{"ValidPostBodyBook", "ArchiveBook", "post", "book", testutils.Problems{}},
+		{"InvalidPostBody", "ArchiveBook", "post", "random", testutils.Problems{{Message: `body: "*"`}}},
 		{"InvalidPostNoBody", "ArchiveBook", "post", "", testutils.Problems{{Message: `body: "*"`}}},
 		{"ValidGetNoBody", "ReadBook", "get", "", testutils.Problems{}},
 		{"InvalidGetBody", "ReadBook", "get", "*", testutils.Problems{{Message: "should not set"}}},