Repository: googleapis/api-linter
Pull request #687 (2020-10-14 20:51:47)

refactor: Extract common lints into the util package to eliminate redundancy


Patch:
diff --git a/rules/aip0131/http_body.go b/rules/aip0131/http_body.go
index 2eaa781..d1f3e17 100644
--- a/rules/aip0131/http_body.go
+++ b/rules/aip0131/http_body.go
@@ -17,24 +17,11 @@ package aip0131
 import (
 	"github.com/googleapis/api-linter/lint"
 	"github.com/googleapis/api-linter/rules/internal/utils"
-	"github.com/jhump/protoreflect/desc"
 )
 
 // Get methods should not have an HTTP body.
 var httpBody = &lint.MethodRule{
-	Name:   lint.NewRuleName(131, "http-body"),
-	OnlyIf: isGetMethod,
-	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
-		// Establish that the RPC has no HTTP body.
-		for _, httpRule := range utils.GetHTTPRules(m) {
-			if httpRule.Body != "" {
-				return []lint.Problem{{
-					Message:    "Get methods should not have an HTTP body.",
-					Descriptor: m,
-				}}
-			}
-		}
-
-		return nil
-	},
+	Name:       lint.NewRuleName(131, "http-body"),
+	OnlyIf:     isGetMethod,
+	LintMethod: utils.LintNoHTTPBody,
 }
diff --git a/rules/aip0131/http_method.go b/rules/aip0131/http_method.go
index 5da9fdc..83684ca 100644
--- a/rules/aip0131/http_method.go
+++ b/rules/aip0131/http_method.go
@@ -17,24 +17,11 @@ package aip0131
 import (
 	"github.com/googleapis/api-linter/lint"
 	"github.com/googleapis/api-linter/rules/internal/utils"
-	"github.com/jhump/protoreflect/desc"
 )
 
 // Get methods should use the HTTP GET verb.
 var httpMethod = &lint.MethodRule{
-	Name:   lint.NewRuleName(131, "http-method"),
-	OnlyIf: isGetMethod,
-	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
-		// Rule check: Establish that the RPC uses HTTP GET.
-		for _, httpRule := range utils.GetHTTPRules(m) {
-			if httpRule.Method != "GET" {
-				return []lint.Problem{{
-					Message:    "Get methods must use the HTTP GET verb.",
-					Descriptor: m,
-				}}
-			}
-		}
-
-		return nil
-	},
+	Name:       lint.NewRuleName(131, "http-method"),
+	OnlyIf:     isGetMethod,
+	LintMethod: utils.LintHTTPMethod("GET"),
 }
diff --git a/rules/aip0131/request_name_behavior.go b/rules/aip0131/request_name_behavior.go
index 392ad8f..160b136 100644
--- a/rules/aip0131/request_name_behavior.go
+++ b/rules/aip0131/request_name_behavior.go
@@ -25,13 +25,5 @@ var requestNameBehavior = &lint.FieldRule{
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
 		return isGetRequestMessage(f.GetOwner()) && f.GetName() == "name"
 	},
-	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
-		if !utils.GetFieldBehavior(f).Contains("REQUIRED") {
-			return []lint.Problem{{
-				Message:    "Get requests: The `name` field should include `(google.api.field_behavior) = REQUIRED`.",
-				Descriptor: f,
-			}}
-		}
-		return nil
-	},
+	LintField: utils.LintRequiredField,
 }
diff --git a/rules/aip0131/request_name_reference.go b/rules/aip0131/request_name_reference.go
index 20add0c..4826dbd 100644
--- a/rules/aip0131/request_name_reference.go
+++ b/rules/aip0131/request_name_reference.go
@@ -25,13 +25,5 @@ var requestNameReference = &lint.FieldRule{
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
 		return isGetRequestMessage(f.GetOwner()) && f.GetName() == "name"
 	},
-	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
-		if ref := utils.GetResourceReference(f); ref == nil {
-			return []lint.Problem{{
-				Message:    "Get methods: The `name` field should include a `google.api.resource_reference` annotation.",
-				Descriptor: f,
-			}}
-		}
-		return nil
-	},
+	LintField: utils.LintFieldResourceReference,
 }
diff --git a/rules/aip0132/request_parent_behavior.go b/rules/aip0132/request_parent_behavior.go
index d1fe3b9..9a4731d 100644
--- a/rules/aip0132/request_parent_behavior.go
+++ b/rules/aip0132/request_parent_behavior.go
@@ -25,13 +25,5 @@ var requestParentBehavior = &lint.FieldRule{
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
 		return isListRequestMessage(f.GetOwner()) && f.GetName() == "parent"
 	},
-	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
-		if !utils.GetFieldBehavior(f).Contains("REQUIRED") {
-			return []lint.Problem{{
-				Message:    "List requests: The `parent` field should include `(google.api.field_behavior) = REQUIRED`.",
-				Descriptor: f,
-			}}
-		}
-		return nil
-	},
+	LintField: utils.LintRequiredField,
 }
diff --git a/rules/aip0132/request_parent_field.go b/rules/aip0132/request_parent_field.go
index 358b55e..0b2131f 100644
--- a/rules/aip0132/request_parent_field.go
+++ b/rules/aip0132/request_parent_field.go
@@ -16,9 +16,8 @@ package aip0132
 
 import (
 	"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"
-	"github.com/jhump/protoreflect/desc/builder"
 )
 
 // The type of the parent field in the List request message should
@@ -28,16 +27,5 @@ var requestParentField = &lint.FieldRule{
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
 		return isListRequestMessage(f.GetOwner()) && f.GetName() == "parent"
 	},
-	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
-		if f.GetType() != builder.FieldTypeString().GetType() {
-			return []lint.Problem{{
-				Message:    "`parent` field on List RPCs must be a string",
-				Suggestion: "string",
-				Descriptor: f,
-				Location:   locations.FieldType(f),
-			}}
-		}
-
-		return nil
-	},
+	LintField: utils.LintStringField,
 }
diff --git a/rules/aip0132/request_parent_reference.go b/rules/aip0132/request_parent_reference.go
index f73f5c9..ec9c511 100644
--- a/rules/aip0132/request_parent_reference.go
+++ b/rules/aip0132/request_parent_reference.go
@@ -25,13 +25,5 @@ var requestParentReference = &lint.FieldRule{
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
 		return isListRequestMessage(f.GetOwner()) && f.GetName() == "parent"
 	},
-	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
-		if ref := utils.GetResourceReference(f); ref == nil {
-			return []lint.Problem{{
-				Message:    "List methods: The `parent` field should include a `google.api.resource_reference` annotation.",
-				Descriptor: f,
-			}}
-		}
-		return nil
-	},
+	LintField: utils.LintFieldResourceReference,
 }
diff --git a/rules/aip0133/http_method.go b/rules/aip0133/http_method.go
index 28e4de3..e11bbe4 100644
--- a/rules/aip0133/http_method.go
+++ b/rules/aip0133/http_method.go
@@ -17,24 +17,11 @@ package aip0133
 import (
 	"github.com/googleapis/api-linter/lint"
 	"github.com/googleapis/api-linter/rules/internal/utils"
-	"github.com/jhump/protoreflect/desc"
 )
 
 // Create methods should use the HTTP POST verb.
 var httpMethod = &lint.MethodRule{
-	Name:   lint.NewRuleName(133, "http-method"),
-	OnlyIf: isCreateMethod,
-	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
-		// Rule check: Establish that the RPC uses HTTP POST.
-		for _, httpRule := range utils.GetHTTPRules(m) {
-			if httpRule.Method != "POST" {
-				return []lint.Problem{{
-					Message:    "Create methods must use the HTTP POST verb.",
-					Descriptor: m,
-				}}
-			}
-		}
-
-		return nil
-	},
+	Name:       lint.NewRuleName(133, "http-method"),
+	OnlyIf:     isCreateMethod,
+	LintMethod: utils.LintHTTPMethod("POST"),
 }
diff --git a/rules/aip0133/request_parent_behavior.go b/rules/aip0133/request_parent_behavior.go
index 4319fd8..6368cd5 100644
--- a/rules/aip0133/request_parent_behavior.go
+++ b/rules/aip0133/request_parent_behavior.go
@@ -25,13 +25,5 @@ var requestParentBehavior = &lint.FieldRule{
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
 		return isCreateRequestMessage(f.GetOwner()) && f.GetName() == "parent"
 	},
-	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
-		if !utils.GetFieldBehavior(f).Contains("REQUIRED") {
-			return []lint.Problem{{
-				Message:    "Create requests: The `parent` field should include `(google.api.field_behavior) = REQUIRED`.",
-				Descriptor: f,
-			}}
-		}
-		return nil
-	},
+	LintField: utils.LintRequiredField,
 }
diff --git a/rules/aip0133/request_parent_field.go b/rules/aip0133/request_parent_field.go
index dc87818..b2fe0be 100644
--- a/rules/aip0133/request_parent_field.go
+++ b/rules/aip0133/request_parent_field.go
@@ -16,9 +16,8 @@ package aip0133
 
 import (
 	"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"
-	"github.com/jhump/protoreflect/desc/builder"
 )
 
 // The type of the parent field in a creat request should be string.
@@ -27,16 +26,5 @@ var requestParentField = &lint.FieldRule{
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
 		return isCreateRequestMessage(f.GetOwner()) && f.GetName() == "parent"
 	},
-	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
-		if f.GetType() != builder.FieldTypeString().GetType() {
-			return []lint.Problem{{
-				Message:    "`parent` field on create request message must be a string",
-				Suggestion: "string",
-				Descriptor: f,
-				Location:   locations.FieldType(f),
-			}}
-		}
-
-		return nil
-	},
+	LintField: utils.LintStringField,
 }
diff --git a/rules/aip0133/request_parent_reference.go b/rules/aip0133/request_parent_reference.go
index 41d436f..c966baa 100644
--- a/rules/aip0133/request_parent_reference.go
+++ b/rules/aip0133/request_parent_reference.go
@@ -25,13 +25,5 @@ var requestParentReference = &lint.FieldRule{
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
 		return isCreateRequestMessage(f.GetOwner()) && f.GetName() == "parent"
 	},
-	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
-		if ref := utils.GetResourceReference(f); ref == nil {
-			return []lint.Problem{{
-				Message:    "Create methods: The `parent` field should include a `google.api.resource_reference` annotation.",
-				Descriptor: f,
-			}}
-		}
-		return nil
-	},
+	LintField: utils.LintFieldResourceReference,
 }
diff --git a/rules/aip0133/request_resource_behavior.go b/rules/aip0133/request_resource_behavior.go
index 9691a32..b49db30 100644
--- a/rules/aip0133/request_resource_behavior.go
+++ b/rules/aip0133/request_resource_behavior.go
@@ -15,8 +15,6 @@
 package aip0133
 
 import (
-	"fmt"
-
 	"github.com/googleapis/api-linter/lint"
 	"github.com/googleapis/api-linter/rules/internal/utils"
 	"github.com/jhump/protoreflect/desc"
@@ -35,16 +33,5 @@ var requestResourceBehavior = &lint.FieldRule{
 		}
 		return false
 	},
-	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
-		if !utils.GetFieldBehavior(f).Contains("REQUIRED") {
-			return []lint.Problem{{
-				Message: fmt.Sprintf(
-					"Create requests: The `%s` field should include `(google.api.field_behavior) = REQUIRED`.",
-					f.GetName(),
-				),
-				Descriptor: f,
-			}}
-		}
-		return nil
-	},
+	LintField: utils.LintRequiredField,
 }
diff --git a/rules/aip0134/http_method.go b/rules/aip0134/http_method.go
index de74f35..cb68dde 100644
--- a/rules/aip0134/http_method.go
+++ b/rules/aip0134/http_method.go
@@ -17,24 +17,11 @@ package aip0134
 import (
 	"github.com/googleapis/api-linter/lint"
 	"github.com/googleapis/api-linter/rules/internal/utils"
-	"github.com/jhump/protoreflect/desc"
 )
 
 // Update methods should use the HTTP PATCH verb.
 var httpMethod = &lint.MethodRule{
-	Name:   lint.NewRuleName(134, "http-method"),
-	OnlyIf: isUpdateMethod,
-	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
-		// Rule check: Establish that the RPC uses HTTP PATCH.
-		for _, httpRule := range utils.GetHTTPRules(m) {
-			if httpRule.Method != "PATCH" {
-				return []lint.Problem{{
-					Message:    "Update methods must use the HTTP PATCH verb.",
-					Descriptor: m,
-				}}
-			}
-		}
-
-		return nil
-	},
+	Name:       lint.NewRuleName(134, "http-method"),
+	OnlyIf:     isUpdateMethod,
+	LintMethod: utils.LintHTTPMethod("PATCH"),
 }
diff --git a/rules/aip0135/http_body.go b/rules/aip0135/http_body.go
index 1541c33..eaba7bb 100644
--- a/rules/aip0135/http_body.go
+++ b/rules/aip0135/http_body.go
@@ -17,24 +17,11 @@ package aip0135
 import (
 	"github.com/googleapis/api-linter/lint"
 	"github.com/googleapis/api-linter/rules/internal/utils"
-	"github.com/jhump/protoreflect/desc"
 )
 
 // Delete methods should not have an HTTP body.
 var httpBody = &lint.MethodRule{
-	Name:   lint.NewRuleName(135, "http-body"),
-	OnlyIf: isDeleteMethod,
-	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
-		// Establish that the RPC has no HTTP body.
-		for _, httpRule := range utils.GetHTTPRules(m) {
-			if httpRule.Body != "" {
-				return []lint.Problem{{
-					Message:    "Delete methods should not have an HTTP body.",
-					Descriptor: m,
-				}}
-			}
-		}
-
-		return nil
-	},
+	Name:       lint.NewRuleName(135, "http-body"),
+	OnlyIf:     isDeleteMethod,
+	LintMethod: utils.LintNoHTTPBody,
 }
diff --git a/rules/aip0135/http_method.go b/rules/aip0135/http_method.go
index d5a07aa..36f41f9 100644
--- a/rules/aip0135/http_method.go
+++ b/rules/aip0135/http_method.go
@@ -17,24 +17,11 @@ package aip0135
 import (
 	"github.com/googleapis/api-linter/lint"
 	"github.com/googleapis/api-linter/rules/internal/utils"
-	"github.com/jhump/protoreflect/desc"
 )
 
 // Delete methods should use the HTTP DELETE method.
 var httpMethod = &lint.MethodRule{
-	Name:   lint.NewRuleName(135, "http-method"),
-	OnlyIf: isDeleteMethod,
-	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
-		// Rule check: Establish that the RPC uses HTTP DELETE.
-		for _, httpRule := range utils.GetHTTPRules(m) {
-			if httpRule.Method != "DELETE" {
-				return []lint.Problem{{
-					Message:    "Delete methods must use the HTTP DELETE verb.",
-					Descriptor: m,
-				}}
-			}
-		}
-
-		return nil
-	},
+	Name:       lint.NewRuleName(135, "http-method"),
+	OnlyIf:     isDeleteMethod,
+	LintMethod: utils.LintHTTPMethod("DELETE"),
 }
diff --git a/rules/aip0135/request_name_behavior.go b/rules/aip0135/request_name_behavior.go
index cff9ff6..c642695 100644
--- a/rules/aip0135/request_name_behavior.go
+++ b/rules/aip0135/request_name_behavior.go
@@ -25,13 +25,5 @@ var requestNameBehavior = &lint.FieldRule{
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
 		return isDeleteRequestMessage(f.GetOwner()) && f.GetName() == "name"
 	},
-	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
-		if !utils.GetFieldBehavior(f).Contains("REQUIRED") {
-			return []lint.Problem{{
-				Message:    "Delete requests: The `name` field should include `(google.api.field_behavior) = REQUIRED`.",
-				Descriptor: f,
-			}}
-		}
-		return nil
-	},
+	LintField: utils.LintRequiredField,
 }
diff --git a/rules/aip0135/request_name_reference.go b/rules/aip0135/request_name_reference.go
index a1a83e1..9b7cc8c 100644
--- a/rules/aip0135/request_name_reference.go
+++ b/rules/aip0135/request_name_reference.go
@@ -25,13 +25,5 @@ var requestNameReference = &lint.FieldRule{
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
 		return isDeleteRequestMessage(f.GetOwner()) && f.GetName() == "name"
 	},
-	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
-		if ref := utils.GetResourceReference(f); ref == nil {
-			return []lint.Problem{{
-				Message:    "Delete methods: The `name` field should include a `google.api.resource_reference` annotation.",
-				Descriptor: f,
-			}}
-		}
-		return nil
-	},
+	LintField: utils.LintFieldResourceReference,
 }
diff --git a/rules/aip0164/http_body.go b/rules/aip0164/http_body.go
index b7313c8..d4b1724 100644
--- a/rules/aip0164/http_body.go
+++ b/rules/aip0164/http_body.go
@@ -17,24 +17,11 @@ package aip0164
 import (
 	"github.com/googleapis/api-linter/lint"
 	"github.com/googleapis/api-linter/rules/internal/utils"
-	"github.com/jhump/protoreflect/desc"
 )
 
-// Undelete methods should not have an HTTP body.
+// Undelete methods should have "*" as the HTTP body.
 var httpBody = &lint.MethodRule{
-	Name:   lint.NewRuleName(164, "http-body"),
-	OnlyIf: isUndeleteMethod,
-	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
-		// Establish that the RPC has correct HTTP body.
-		for _, httpRule := range utils.GetHTTPRules(m) {
-			if httpRule.Body != "*" {
-				return []lint.Problem{{
-					Message:    `Undelete methods should use "*" as the HTTP body.`,
-					Descriptor: m,
-				}}
-			}
-		}
-
-		return nil
-	},
+	Name:       lint.NewRuleName(164, "http-body"),
+	OnlyIf:     isUndeleteMethod,
+	LintMethod: utils.LintWildcardHTTPBody,
 }
diff --git a/rules/aip0164/http_method.go b/rules/aip0164/http_method.go
index 362da1b..ed1c50c 100644
--- a/rules/aip0164/http_method.go
+++ b/rules/aip0164/http_method.go
@@ -17,24 +17,11 @@ package aip0164
 import (
 	"github.com/googleapis/api-linter/lint"
 	"github.com/googleapis/api-linter/rules/internal/utils"
-	"github.com/jhump/protoreflect/desc"
 )
 
 // Undelete methods should use the HTTP POST method.
 var httpMethod = &lint.MethodRule{
-	Name:   lint.NewRuleName(164, "http-method"),
-	OnlyIf: isUndeleteMethod,
-	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
-		// Rule check: Establish that the RPC uses HTTP POST.
-		for _, httpRule := range utils.GetHTTPRules(m) {
-			if httpRule.Method != "POST" {
-				return []lint.Problem{{
-					Message:    "Undelete methods must use the HTTP POST verb.",
-					Descriptor: m,
-				}}
-			}
-		}
-
-		return nil
-	},
+	Name:       lint.NewRuleName(164, "http-method"),
+	OnlyIf:     isUndeleteMethod,
+	LintMethod: utils.LintHTTPMethod("POST"),
 }
diff --git a/rules/aip0164/request_name_behavior.go b/rules/aip0164/request_name_behavior.go
index e07b3e3..69bfaba 100644
--- a/rules/aip0164/request_name_behavior.go
+++ b/rules/aip0164/request_name_behavior.go
@@ -25,13 +25,5 @@ var requestNameBehavior = &lint.FieldRule{
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
 		return isUndeleteRequestMessage(f.GetOwner()) && f.GetName() == "name"
 	},
-	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
-		if !utils.GetFieldBehavior(f).Contains("REQUIRED") {
-			return []lint.Problem{{
-				Message:    "Undelete requests: The `name` field should include `(google.api.field_behavior) = REQUIRED`.",
-				Descriptor: f,
-			}}
-		}
-		return nil
-	},
+	LintField: utils.LintRequiredField,
 }
diff --git a/rules/aip0164/request_name_reference.go b/rules/aip0164/request_name_reference.go
index df51ad9..bdfe438 100644
--- a/rules/aip0164/request_name_reference.go
+++ b/rules/aip0164/request_name_reference.go
@@ -25,13 +25,5 @@ var requestNameReference = &lint.FieldRule{
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
 		return isUndeleteRequestMessage(f.GetOwner()) && f.GetName() == "name"
 	},
-	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
-		if ref := utils.GetResourceReference(f); ref == nil {
-			return []lint.Problem{{
-				Message:    "Undelete methods: The `name` field should include a `google.api.resource_reference` annotation.",
-				Descriptor: f,
-			}}
-		}
-		return nil
-	},
+	LintField: utils.LintFieldResourceReference,
 }
diff --git a/rules/aip0231/http_body.go b/rules/aip0231/http_body.go
index fe1cb2f..6083b34 100644
--- a/rules/aip0231/http_body.go
+++ b/rules/aip0231/http_body.go
@@ -17,24 +17,11 @@ package aip0231
 import (
 	"github.com/googleapis/api-linter/lint"
 	"github.com/googleapis/api-linter/rules/internal/utils"
-	"github.com/jhump/protoreflect/desc"
 )
 
-// Get methods should not have an HTTP body.
+// Batch Get methods should not have an HTTP body.
 var httpBody = &lint.MethodRule{
-	Name:   lint.NewRuleName(231, "http-body"),
-	OnlyIf: isBatchGetMethod,
-	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
-		// Establish that the RPC has no HTTP body.
-		for _, httpRule := range utils.GetHTTPRules(m) {
-			if httpRule.Body != "" {
-				return []lint.Problem{{
-					Message:    "Batch Get methods should not have an HTTP body.",
-					Descriptor: m,
-				}}
-			}
-		}
-
-		return nil
-	},
+	Name:       lint.NewRuleName(231, "http-body"),
+	OnlyIf:     isBatchGetMethod,
+	LintMethod: utils.LintNoHTTPBody,
 }
diff --git a/rules/aip0231/http_method.go b/rules/aip0231/http_method.go
index 3c327c9..bb837dd 100644
--- a/rules/aip0231/http_method.go
+++ b/rules/aip0231/http_method.go
@@ -17,24 +17,11 @@ package aip0231
 import (
 	"github.com/googleapis/api-linter/lint"
 	"github.com/googleapis/api-linter/rules/internal/utils"
-	"github.com/jhump/protoreflect/desc"
 )
 
 // Batch Get methods should use the HTTP GET verb.
 var httpVerb = &lint.MethodRule{
-	Name:   lint.NewRuleName(231, "http-method"),
-	OnlyIf: isBatchGetMethod,
-	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
-		// Rule check: Establish that the RPC uses HTTP GET.
-		for _, httpRule := range utils.GetHTTPRules(m) {
-			if httpRule.Method != "GET" {
-				return []lint.Problem{{
-					Message:    "Batch Get methods must use the HTTP GET verb.",
-					Descriptor: m,
-				}}
-			}
-		}
-
-		return nil
-	},
+	Name:       lint.NewRuleName(231, "http-method"),
+	OnlyIf:     isBatchGetMethod,
+	LintMethod: utils.LintHTTPMethod("GET"),
 }
diff --git a/rules/aip0231/request_names_behavior.go b/rules/aip0231/request_names_behavior.go
index f73d7d5..6210af6 100644
--- a/rules/aip0231/request_names_behavior.go
+++ b/rules/aip0231/request_names_behavior.go
@@ -25,13 +25,5 @@ var requestNamesBehavior = &lint.FieldRule{
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
 		return isBatchGetRequestMessage(f.GetOwner()) && f.GetName() == "names"
 	},
-	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
-		if !utils.GetFieldBehavior(f).Contains("REQUIRED") {
-			return []lint.Problem{{
-				Message:    "Batch Get requests: The `names` field should include `(google.api.field_behavior) = REQUIRED`.",
-				Descriptor: f,
-			}}
-		}
-		return nil
-	},
+	LintField: utils.LintRequiredField,
 }
diff --git a/rules/aip0231/request_names_reference.go b/rules/aip0231/request_names_reference.go
index 5f197d2..da0c426 100644
--- a/rules/aip0231/request_names_reference.go
+++ b/rules/aip0231/request_names_reference.go
@@ -25,13 +25,5 @@ var requestNamesReference = &lint.FieldRule{
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
 		return isBatchGetRequestMessage(f.GetOwner()) && f.GetName() == "names"
 	},
-	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
-		if ref := utils.GetResourceReference(f); ref == nil {
-			return []lint.Problem{{
-				Message:    "Batch Get methods: The `names` field should include a `google.api.resource_reference` annotation.",
-				Descriptor: f,
-			}}
-		}
-		return nil
-	},
+	LintField: utils.LintFieldResourceReference,
 }
diff --git a/rules/aip0231/request_parent_reference.go b/rules/aip0231/request_parent_reference.go
index 79101a6..d6db396 100644
--- a/rules/aip0231/request_parent_reference.go
+++ b/rules/aip0231/request_parent_reference.go
@@ -25,13 +25,5 @@ var requestParentReference = &lint.FieldRule{
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
 		return isBatchGetRequestMessage(f.GetOwner()) && f.GetName() == "parent"
 	},
-	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
-		if ref := utils.GetResourceReference(f); ref == nil {
-			return []lint.Problem{{
-				Message:    "Batch Get methods: The `parent` field should include a `google.api.resource_reference` annotation.",
-				Descriptor: f,
-			}}
-		}
-		return nil
-	},
+	LintField: utils.LintFieldResourceReference,
 }
diff --git a/rules/aip0233/http_body.go b/rules/aip0233/http_body.go
index 69e1f5b..e4ddc59 100644
--- a/rules/aip0233/http_body.go
+++ b/rules/aip0233/http_body.go
@@ -17,24 +17,11 @@ package aip0233
 import (
 	"github.com/googleapis/api-linter/lint"
 	"github.com/googleapis/api-linter/rules/internal/utils"
-	"github.com/jhump/protoreflect/desc"
 )
 
-// Batch create methods should use "*" as the HTTP body.
+// Batch Create methods should use "*" as the HTTP body.
 var httpBody = &lint.MethodRule{
-	Name:   lint.NewRuleName(233, "http-body"),
-	OnlyIf: isBatchCreateMethod,
-	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
-		// Establish that the RPC has correct HTTP body.
-		for _, httpRule := range utils.GetHTTPRules(m) {
-			if httpRule.Body != "*" {
-				return []lint.Problem{{
-					Message:    `Batch Create methods should use "*" as the HTTP body.`,
-					Descriptor: m,
-				}}
-			}
-		}
-
-		return nil
-	},
+	Name:       lint.NewRuleName(233, "http-body"),
+	OnlyIf:     isBatchCreateMethod,
+	LintMethod: utils.LintWildcardHTTPBody,
 }
diff --git a/rules/aip0233/http_method.go b/rules/aip0233/http_method.go
index cd80a98..00b1328 100644
--- a/rules/aip0233/http_method.go
+++ b/rules/aip0233/http_method.go
@@ -17,24 +17,11 @@ package aip0233
 import (
 	"github.com/googleapis/api-linter/lint"
 	"github.com/googleapis/api-linter/rules/internal/utils"
-	"github.com/jhump/protoreflect/desc"
 )
 
 // Batch Create methods should use the HTTP POST verb.
 var httpVerb = &lint.MethodRule{
-	Name:   lint.NewRuleName(233, "http-method"),
-	OnlyIf: isBatchCreateMethod,
-	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
-		// Rule check: Establish that the RPC uses HTTP POST.
-		for _, httpRule := range utils.GetHTTPRules(m) {
-			if httpRule.Method != "POST" {
-				return []lint.Problem{{
-					Message:    "Batch Create methods must use the HTTP POST verb.",
-					Descriptor: m,
-				}}
-			}
-		}
-
-		return nil
-	},
+	Name:       lint.NewRuleName(233, "http-method"),
+	OnlyIf:     isBatchCreateMethod,
+	LintMethod: utils.LintHTTPMethod("POST"),
 }
diff --git a/rules/aip0233/request_parent_reference.go b/rules/aip0233/request_parent_reference.go
index d3a016a..eb0468a 100644
--- a/rules/aip0233/request_parent_reference.go
+++ b/rules/aip0233/request_parent_reference.go
@@ -25,13 +25,5 @@ var requestParentReference = &lint.FieldRule{
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
 		return isBatchCreateRequestMessage(f.GetOwner()) && f.GetName() == "parent"
 	},
-	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
-		if ref := utils.GetResourceReference(f); ref == nil {
-			return []lint.Problem{{
-				Message:    "Batch Create methods: The `parent` field should include a `google.api.resource_reference` annotation.",
-				Descriptor: f,
-			}}
-		}
-		return nil
-	},
+	LintField: utils.LintFieldResourceReference,
 }
diff --git a/rules/aip0233/request_requests_behavior.go b/rules/aip0233/request_requests_behavior.go
index 639e4ed..8061b2f 100644
--- a/rules/aip0233/request_requests_behavior.go
+++ b/rules/aip0233/request_requests_behavior.go
@@ -25,13 +25,5 @@ var requestRequestsBehavior = &lint.FieldRule{
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
 		return isBatchCreateRequestMessage(f.GetOwner()) && f.GetName() == "requests"
 	},
-	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
-		if !utils.GetFieldBehavior(f).Contains("REQUIRED") {
-			return []lint.Problem{{
-				Message:    "Batch Create requests: The `requests` field should include `(google.api.field_behavior) = REQUIRED`.",
-				Descriptor: f,
-			}}
-		}
-		return nil
-	},
+	LintField: utils.LintRequiredField,
 }
diff --git a/rules/aip0234/http_body.go b/rules/aip0234/http_body.go
index 6bffe13..cc79f6f 100644
--- a/rules/aip0234/http_body.go
+++ b/rules/aip0234/http_body.go
@@ -17,24 +17,11 @@ package aip0234
 import (
 	"github.com/googleapis/api-linter/lint"
 	"github.com/googleapis/api-linter/rules/internal/utils"
-	"github.com/jhump/protoreflect/desc"
 )
 
 // Batch Update methods should use "*" as the HTTP body.
 var httpBody = &lint.MethodRule{
-	Name:   lint.NewRuleName(234, "http-body"),
-	OnlyIf: isBatchUpdateMethod,
-	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
-		// Establish that the RPC has correct HTTP body.
-		for _, httpRule := range utils.GetHTTPRules(m) {
-			if httpRule.Body != "*" {
-				return []lint.Problem{{
-					Message:    `Batch Update methods should use "*" as the HTTP body.`,
-					Descriptor: m,
-				}}
-			}
-		}
-
-		return nil
-	},
+	Name:       lint.NewRuleName(234, "http-body"),
+	OnlyIf:     isBatchUpdateMethod,
+	LintMethod: utils.LintWildcardHTTPBody,
 }
diff --git a/rules/aip0234/http_method.go b/rules/aip0234/http_method.go
index 1428bb9..1e0acc2 100644
--- a/rules/aip0234/http_method.go
+++ b/rules/aip0234/http_method.go
@@ -17,24 +17,11 @@ package aip0234
 import (
 	"github.com/googleapis/api-linter/lint"
 	"github.com/googleapis/api-linter/rules/internal/utils"
-	"github.com/jhump/protoreflect/desc"
 )
 
 // Batch Create methods should use the HTTP POST verb.
 var httpMethod = &lint.MethodRule{
-	Name:   lint.NewRuleName(234, "http-method"),
-	OnlyIf: isBatchUpdateMethod,
-	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
-		// Rule check: Establish that the RPC uses HTTP POST.
-		for _, httpRule := range utils.GetHTTPRules(m) {
-			if httpRule.Method != "POST" {
-				return []lint.Problem{{
-					Message:    "Batch Update methods must use the HTTP POST verb.",
-					Descriptor: m,
-				}}
-			}
-		}
-
-		return nil
-	},
+	Name:       lint.NewRuleName(234, "http-method"),
+	OnlyIf:     isBatchUpdateMethod,
+	LintMethod: utils.LintHTTPMethod("POST"),
 }
diff --git a/rules/aip0234/request_parent_reference.go b/rules/aip0234/request_parent_reference.go
index 9438d1f..26a5434 100644
--- a/rules/aip0234/request_parent_reference.go
+++ b/rules/aip0234/request_parent_reference.go
@@ -25,13 +25,5 @@ var requestParentReference = &lint.FieldRule{
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
 		return isBatchUpdateRequestMessage(f.GetOwner()) && f.GetName() == "parent"
 	},
-	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
-		if ref := utils.GetResourceReference(f); ref == nil {
-			return []lint.Problem{{
-				Message:    "Batch Update methods: The `parent` field should include a `google.api.resource_reference` annotation.",
-				Descriptor: f,
-			}}
-		}
-		return nil
-	},
+	LintField: utils.LintFieldResourceReference,
 }
diff --git a/rules/aip0234/request_requests_behavior.go b/rules/aip0234/request_requests_behavior.go
index dde5f49..3bd85b8 100644
--- a/rules/aip0234/request_requests_behavior.go
+++ b/rules/aip0234/request_requests_behavior.go
@@ -25,13 +25,5 @@ var requestRequestsBehavior = &lint.FieldRule{
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
 		return isBatchUpdateRequestMessage(f.GetOwner()) && f.GetName() == "requests"
 	},
-	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
-		if !utils.GetFieldBehavior(f).Contains("REQUIRED") {
-			return []lint.Problem{{
-				Message:    "Batch Update requests: The `requests` field should include `(google.api.field_behavior) = REQUIRED`.",
-				Descriptor: f,
-			}}
-		}
-		return nil
-	},
+	LintField: utils.LintRequiredField,
 }
diff --git a/rules/aip0235/http_body.go b/rules/aip0235/http_body.go
index f6fd164..9f5a86d 100644
--- a/rules/aip0235/http_body.go
+++ b/rules/aip0235/http_body.go
@@ -17,24 +17,11 @@ package aip0235
 import (
 	"github.com/googleapis/api-linter/lint"
 	"github.com/googleapis/api-linter/rules/internal/utils"
-	"github.com/jhump/protoreflect/desc"
 )
 
 // Batch Delete methods should use "*" as the HTTP body.
 var httpBody = &lint.MethodRule{
-	Name:   lint.NewRuleName(235, "http-body"),
-	OnlyIf: isBatchDeleteMethod,
-	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
-		// Establish that the RPC has correct HTTP body.
-		for _, httpRule := range utils.GetHTTPRules(m) {
-			if httpRule.Body != "*" {
-				return []lint.Problem{{
-					Message:    `Batch Delete methods should use "*" as the HTTP body.`,
-					Descriptor: m,
-				}}
-			}
-		}
-
-		return nil
-	},
+	Name:       lint.NewRuleName(235, "http-body"),
+	OnlyIf:     isBatchDeleteMethod,
+	LintMethod: utils.LintWildcardHTTPBody,
 }
diff --git a/rules/aip0235/http_method.go b/rules/aip0235/http_method.go
index 2d841dd..e1686d7 100644
--- a/rules/aip0235/http_method.go
+++ b/rules/aip0235/http_method.go
@@ -17,24 +17,11 @@ package aip0235
 import (
 	"github.com/googleapis/api-linter/lint"
 	"github.com/googleapis/api-linter/rules/internal/utils"
-	"github.com/jhump/protoreflect/desc"
 )
 
 // Batch Delete methods should use the HTTP POST method.
 var httpMethod = &lint.MethodRule{
-	Name:   lint.NewRuleName(235, "http-method"),
-	OnlyIf: isBatchDeleteMethod,
-	LintMethod: func(m *desc.MethodDescriptor) []lint.Problem {
-		// Rule check: Establish that the RPC uses HTTP POST.
-		for _, httpRule := range utils.GetHTTPRules(m) {
-			if httpRule.Method != "POST" {
-				return []lint.Problem{{
-					Message:    "Batch Delete methods must use the HTTP POST verb.",
-					Descriptor: m,
-				}}
-			}
-		}
-
-		return nil
-	},
+	Name:       lint.NewRuleName(235, "http-method"),
+	OnlyIf:     isBatchDeleteMethod,
+	LintMethod: utils.LintHTTPMethod("POST"),
 }
diff --git a/rules/aip0235/request_names_behavior.go b/rules/aip0235/request_names_behavior.go
index 6318c60..faa58fe 100644
--- a/rules/aip0235/request_names_behavior.go
+++ b/rules/aip0235/request_names_behavior.go
@@ -25,13 +25,5 @@ var requestNamesBehavior = &lint.FieldRule{
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
 		return isBatchDeleteRequestMessage(f.GetOwner()) && f.GetName() == "names"
 	},
-	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
-		if !utils.GetFieldBehavior(f).Contains("REQUIRED") {
-			return []lint.Problem{{
-				Message:    "Batch Delete requests: The `names` field should include `(google.api.field_behavior) = REQUIRED`.",
-				Descriptor: f,
-			}}
-		}
-		return nil
-	},
+	LintField: utils.LintRequiredField,
 }
diff --git a/rules/aip0235/request_names_reference.go b/rules/aip0235/request_names_reference.go
index da87cbb..810289e 100644
--- a/rules/aip0235/request_names_reference.go
+++ b/rules/aip0235/request_names_reference.go
@@ -25,13 +25,5 @@ var requestNamesReference = &lint.FieldRule{
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
 		return isBatchDeleteRequestMessage(f.GetOwner()) && f.GetName() == "names"
 	},
-	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
-		if ref := utils.GetResourceReference(f); ref == nil {
-			return []lint.Problem{{
-				Message:    "Batch Delete methods: The `names` field should include a `google.api.resource_reference` annotation.",
-				Descriptor: f,
-			}}
-		}
-		return nil
-	},
+	LintField: utils.LintFieldResourceReference,
 }
diff --git a/rules/aip0235/request_parent_reference.go b/rules/aip0235/request_parent_reference.go
index 105464c..93fcb14 100644
--- a/rules/aip0235/request_parent_reference.go
+++ b/rules/aip0235/request_parent_reference.go
@@ -25,13 +25,5 @@ var requestParentReference = &lint.FieldRule{
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
 		return isBatchDeleteRequestMessage(f.GetOwner()) && f.GetName() == "parent"
 	},
-	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
-		if ref := utils.GetResourceReference(f); ref == nil {
-			return []lint.Problem{{
-				Message:    "Batch Delete methods: The `parent` field should include a `google.api.resource_reference` annotation.",
-				Descriptor: f,
-			}}
-		}
-		return nil
-	},
+	LintField: utils.LintFieldResourceReference,
 }
diff --git a/rules/aip0235/request_requests_behavior.go b/rules/aip0235/request_requests_behavior.go
index 6fd312c..b5526b8 100644
--- a/rules/aip0235/request_requests_behavior.go
+++ b/rules/aip0235/request_requests_behavior.go
@@ -25,13 +25,5 @@ var requestRequestsBehavior = &lint.FieldRule{
 	OnlyIf: func(f *desc.FieldDescriptor) bool {
 		return isBatchDeleteRequestMessage(f.GetOwner()) && f.GetName() == "requests"
 	},
-	LintField: func(f *desc.FieldDescriptor) []lint.Problem {
-		if !utils.GetFieldBehavior(f).Contains("REQUIRED") {
-			return []lint.Problem{{
-				Message:    "Batch Delete requests: The `requests` field should include `(google.api.field_behavior) = REQUIRED`.",
-				Descriptor: f,
-			}}
-		}
-		return nil
-	},
+	LintField: utils.LintRequiredField,
 }
diff --git a/rules/internal/utils/common_lints.go b/rules/internal/utils/common_lints.go
new file mode 100644
index 0000000..b9cf0af
--- /dev/null
+++ b/rules/internal/utils/common_lints.go
@@ -0,0 +1,96 @@
+// Copyright 2020 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 utils
+
+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"
+)
+
+// LintStringField returns a problem if the field is not a string.
+func LintStringField(f *desc.FieldDescriptor) []lint.Problem {
+	if f.GetType() != builder.FieldTypeString().GetType() {
+		return []lint.Problem{{
+			Message:    fmt.Sprintf("The `%s` field must be a string.", f.GetType()),
+			Suggestion: "string",
+			Descriptor: f,
+			Location:   locations.FieldType(f),
+		}}
+	}
+	return nil
+}
+
+// LintRequiredField returns a problem if the field's behavior is not REQUIRED.
+func LintRequiredField(f *desc.FieldDescriptor) []lint.Problem {
+	if !GetFieldBehavior(f).Contains("REQUIRED") {
+		return []lint.Problem{{
+			Message:    fmt.Sprintf("The `%s` field should include `(google.api.field_behavior) = REQUIRED`.", f.GetName()),
+			Descriptor: f,
+		}}
+	}
+	return nil
+}
+
+// LintFieldResourceReference returns a problem if the field does not have a resource reference annotation.
+func LintFieldResourceReference(f *desc.FieldDescriptor) []lint.Problem {
+	if ref := GetResourceReference(f); ref == nil {
+		return []lint.Problem{{
+			Message:    fmt.Sprintf("The `%s` field should include a `google.api.resource_reference` annotation.", f.GetName()),
+			Descriptor: f,
+		}}
+	}
+	return nil
+}
+
+func lintHTTPBody(m *desc.MethodDescriptor, want, msg string) []lint.Problem {
+	for _, httpRule := range GetHTTPRules(m) {
+		if httpRule.Body != want {
+			return []lint.Problem{{
+				Message:    fmt.Sprintf("The `%s` method should %s HTTP body.", m.GetName(), msg),
+				Descriptor: m,
+			}}
+		}
+	}
+	return nil
+}
+
+// LintNoHTTPBody returns a problem for each HTTP rule whose body is not "".
+func LintNoHTTPBody(m *desc.MethodDescriptor) []lint.Problem {
+	return lintHTTPBody(m, "", "not have an")
+}
+
+// LintWildcardHTTPBody returns a problem for each HTTP rule whose body is not "*".
+func LintWildcardHTTPBody(m *desc.MethodDescriptor) []lint.Problem {
+	return lintHTTPBody(m, "*", `use "*" as the`)
+}
+
+// LintHTTPMethod returns a problem for each HTTP rule whose HTTP method is not the given one.
+func LintHTTPMethod(verb string) func(*desc.MethodDescriptor) []lint.Problem {
+	return func(m *desc.MethodDescriptor) []lint.Problem {
+		for _, httpRule := range GetHTTPRules(m) {
+			if httpRule.Method != verb {
+				return []lint.Problem{{
+					Message:    fmt.Sprintf("The `%s` method should use the HTTP %s verb.", m.GetName(), verb),
+					Descriptor: m,
+				}}
+			}
+		}
+		return nil
+	}
+}


Test patch:
diff --git a/rules/aip0231/http_body_test.go b/rules/aip0231/http_body_test.go
index deaa560..0fe793c 100644
--- a/rules/aip0231/http_body_test.go
+++ b/rules/aip0231/http_body_test.go
@@ -28,7 +28,7 @@ func TestHttpBody(t *testing.T) {
 		problems   testutils.Problems
 	}{
 		{"Valid", "", "BatchGetBooks", nil},
-		{"Invalid", "*", "BatchGetBooks", testutils.Problems{{Message: "Batch Get methods should not have an HTTP body."}}},
+		{"Invalid", "*", "BatchGetBooks", testutils.Problems{{Message: "HTTP body"}}},
 		{"Irrelevant", "*", "AcquireBook", nil},
 	}
 
diff --git a/rules/aip0231/http_method_test.go b/rules/aip0231/http_method_test.go
index 0aec9e9..27420aa 100644
--- a/rules/aip0231/http_method_test.go
+++ b/rules/aip0231/http_method_test.go
@@ -29,7 +29,7 @@ func TestHttpVerb(t *testing.T) {
 		problems   testutils.Problems
 	}{
 		{"Valid", "get", "BatchGetBooks", nil},
-		{"Invalid", "post", "BatchGetBooks", testutils.Problems{{Message: "Batch Get methods must use the HTTP GET verb."}}},
+		{"Invalid", "post", "BatchGetBooks", testutils.Problems{{Message: "HTTP GET"}}},
 		{"Irrelevant", "post", "AcquireBook", nil},
 	}
 
diff --git a/rules/internal/utils/common_lints_test.go b/rules/internal/utils/common_lints_test.go
new file mode 100644
index 0000000..b14230d
--- /dev/null
+++ b/rules/internal/utils/common_lints_test.go
@@ -0,0 +1,176 @@
+package utils
+
+import (
+	"testing"
+
+	"github.com/googleapis/api-linter/rules/internal/testutils"
+)
+
+func TestLintStringField(t *testing.T) {
+	for _, test := range []struct {
+		testName  string
+		FieldType string
+		problems  testutils.Problems
+	}{
+		{"Valid", `string`, nil},
+		{"Invalid", `int32`, testutils.Problems{{Suggestion: "string"}}},
+	} {
+		t.Run(test.testName, func(t *testing.T) {
+			f := testutils.ParseProto3Tmpl(t, `
+				message Message {
+					{{.FieldType}} foo = 1;
+				}
+			`, test)
+			field := f.GetMessageTypes()[0].GetFields()[0]
+			problems := LintStringField(field)
+			if diff := test.problems.SetDescriptor(field).Diff(problems); diff != "" {
+				t.Error(diff)
+			}
+		})
+	}
+}
+
+func TestLintRequiredField(t *testing.T) {
+	for _, test := range []struct {
+		testName   string
+		Annotation string
+		problems   testutils.Problems
+	}{
+		{"Valid", `[(google.api.field_behavior) = REQUIRED]`, nil},
+		{"Invalid", ``, testutils.Problems{{Message: "REQUIRED"}}},
+	} {
+		t.Run(test.testName, func(t *testing.T) {
+			f := testutils.ParseProto3Tmpl(t, `
+				import "google/api/field_behavior.proto";
+				message Message {
+					string foo = 1 {{.Annotation}};
+				}
+			`, test)
+			field := f.GetMessageTypes()[0].GetFields()[0]
+			problems := LintRequiredField(field)
+			if diff := test.problems.SetDescriptor(field).Diff(problems); diff != "" {
+				t.Error(diff)
+			}
+		})
+	}
+}
+
+func TestLintFieldResourceReference(t *testing.T) {
+	for _, test := range []struct {
+		testName   string
+		Annotation string
+		problems   testutils.Problems
+	}{
+		{"Valid", `[(google.api.resource_reference).type = "bar"]`, nil},
+		{"Invalid", ``, testutils.Problems{{Message: "resource_reference"}}},
+	} {
+		t.Run(test.testName, func(t *testing.T) {
+			f := testutils.ParseProto3Tmpl(t, `
+				import "google/api/resource.proto";
+				message Message {
+					string foo = 1 {{.Annotation}};
+				}
+			`, test)
+			field := f.GetMessageTypes()[0].GetFields()[0]
+			problems := LintFieldResourceReference(field)
+			if diff := test.problems.SetDescriptor(field).Diff(problems); diff != "" {
+				t.Error(diff)
+			}
+		})
+	}
+}
+
+func TestLintNoHTTPBody(t *testing.T) {
+	for _, test := range []struct {
+		testName string
+		Body     string
+		problems testutils.Problems
+	}{
+		{"Valid", ``, nil},
+		{"Invalid", `*`, testutils.Problems{{Message: "not have an HTTP body"}}},
+	} {
+		t.Run(test.testName, func(t *testing.T) {
+			f := testutils.ParseProto3Tmpl(t, `
+				import "google/api/annotations.proto";
+				service Library {
+					rpc GetBook(GetBookRequest) returns (Book) {
+						option (google.api.http) = {
+							get: "/v1/{name=publishers/*/books/*}"
+							body: "{{.Body}}"
+						};
+					}
+				}
+				message Book {}
+				message GetBookRequest {}
+			`, test)
+			method := f.GetServices()[0].GetMethods()[0]
+			problems := LintNoHTTPBody(method)
+			if diff := test.problems.SetDescriptor(method).Diff(problems); diff != "" {
+				t.Error(diff)
+			}
+		})
+	}
+}
+
+func TestLintWildcardHTTPBody(t *testing.T) {
+	for _, test := range []struct {
+		testName string
+		Body     string
+		problems testutils.Problems
+	}{
+		{"Valid", `*`, nil},
+		{"Invalid", ``, testutils.Problems{{Message: `use "*" as the HTTP body`}}},
+	} {
+		t.Run(test.testName, func(t *testing.T) {
+			f := testutils.ParseProto3Tmpl(t, `
+				import "google/api/annotations.proto";
+				service Library {
+					rpc ArchiveBook(ArchiveBookRequest) returns (Book) {
+						option (google.api.http) = {
+							post: "/v1/{name=publishers/*/books/*}:archive"
+							body: "{{.Body}}"
+						};
+					}
+				}
+				message Book {}
+				message ArchiveBookRequest {}
+			`, test)
+			method := f.GetServices()[0].GetMethods()[0]
+			problems := LintWildcardHTTPBody(method)
+			if diff := test.problems.SetDescriptor(method).Diff(problems); diff != "" {
+				t.Error(diff)
+			}
+		})
+	}
+}
+
+func TestLintHTTPMethod(t *testing.T) {
+	for _, test := range []struct {
+		testName string
+		Method   string
+		problems testutils.Problems
+	}{
+		{"Valid", `get`, nil},
+		{"Invalid", `delete`, testutils.Problems{{Message: `HTTP GET`}}},
+	} {
+		t.Run(test.testName, func(t *testing.T) {
+			f := testutils.ParseProto3Tmpl(t, `
+				import "google/api/annotations.proto";
+				service Library {
+					rpc GetBook(GetBookRequest) returns (Book) {
+						option (google.api.http) = {
+							{{.Method}}: "/v1/{name=publishers/*/books/*}"
+						};
+					}
+				}
+				message Book {}
+				message GetBookRequest {}
+			`, test)
+			method := f.GetServices()[0].GetMethods()[0]
+			problems := LintHTTPMethod("GET")(method)
+			if diff := test.problems.SetDescriptor(method).Diff(problems); diff != "" {
+				t.Error(diff)
+			}
+		})
+	}
+}