github.com/googleapis/api-linter@v1.65.2/rules/internal/utils/common_lints.go (about)

     1  // Copyright 2020 Google LLC
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     https://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package utils
    16  
    17  import (
    18  	"fmt"
    19  
    20  	"github.com/googleapis/api-linter/lint"
    21  	"github.com/googleapis/api-linter/locations"
    22  	"github.com/jhump/protoreflect/desc"
    23  	"github.com/jhump/protoreflect/desc/builder"
    24  )
    25  
    26  // LintFieldPresent returns a problem if the given message does not have the given field.
    27  func LintFieldPresent(m *desc.MessageDescriptor, field string) (*desc.FieldDescriptor, []lint.Problem) {
    28  	f := m.FindFieldByName(field)
    29  	if f == nil {
    30  		return nil, []lint.Problem{{
    31  			Message:    fmt.Sprintf("Message `%s` has no `%s` field.", m.GetName(), field),
    32  			Descriptor: m,
    33  		}}
    34  	}
    35  	return f, nil
    36  }
    37  
    38  // LintSingularStringField returns a problem if the field is not a singular string.
    39  func LintSingularStringField(f *desc.FieldDescriptor) []lint.Problem {
    40  	return LintSingularField(f, builder.FieldTypeString(), "string")
    41  }
    42  
    43  // LintSingularField returns a problem if the field is not singular i.e. it is repeated.
    44  func LintSingularField(f *desc.FieldDescriptor, t *builder.FieldType, want string) []lint.Problem {
    45  	if f.GetType() != t.GetType() || f.IsRepeated() {
    46  		return []lint.Problem{{
    47  			Message:    fmt.Sprintf("The `%s` field must be a singular %s.", f.GetName(), want),
    48  			Suggestion: want,
    49  			Descriptor: f,
    50  			Location:   locations.FieldType(f),
    51  		}}
    52  	}
    53  	return nil
    54  }
    55  
    56  // LintSingularBoolField returns a problem if the field is not a singular bool.
    57  func LintSingularBoolField(f *desc.FieldDescriptor) []lint.Problem {
    58  	return LintSingularField(f, builder.FieldTypeBool(), "bool")
    59  }
    60  
    61  // LintFieldMask returns a problem if the field is not a singular google.protobuf.FieldMask.
    62  func LintFieldMask(f *desc.FieldDescriptor) []lint.Problem {
    63  	const want = "google.protobuf.FieldMask"
    64  	if t := f.GetMessageType(); t == nil || t.GetFullyQualifiedName() != want || f.IsRepeated() {
    65  		return []lint.Problem{{
    66  			Message:    fmt.Sprintf("The `%s` field should be a singular %s.", f.GetName(), want),
    67  			Suggestion: want,
    68  			Descriptor: f,
    69  			Location:   locations.FieldType(f),
    70  		}}
    71  	}
    72  	return nil
    73  }
    74  
    75  // LintNotOneof returns a problem if the field is a oneof.
    76  func LintNotOneof(f *desc.FieldDescriptor) []lint.Problem {
    77  	if f.GetOneOf() != nil && !f.IsProto3Optional() {
    78  		return []lint.Problem{{
    79  			Message:    fmt.Sprintf("The `%s` field should not be a oneof field.", f.GetName()),
    80  			Descriptor: f,
    81  		}}
    82  	}
    83  	return nil
    84  }
    85  
    86  // LintFieldPresentAndSingularString returns a problem if a message does not have the given singular-string field.
    87  func LintFieldPresentAndSingularString(field string) func(*desc.MessageDescriptor) []lint.Problem {
    88  	return func(m *desc.MessageDescriptor) []lint.Problem {
    89  		f, problems := LintFieldPresent(m, field)
    90  		if f == nil {
    91  			return problems
    92  		}
    93  		return LintSingularStringField(f)
    94  	}
    95  }
    96  
    97  func lintFieldBehavior(f *desc.FieldDescriptor, want string) []lint.Problem {
    98  	if !GetFieldBehavior(f).Contains(want) {
    99  		return []lint.Problem{{
   100  			Message:    fmt.Sprintf("The `%s` field should include `(google.api.field_behavior) = %s`.", f.GetName(), want),
   101  			Descriptor: f,
   102  		}}
   103  	}
   104  	return nil
   105  }
   106  
   107  // LintRequiredField returns a problem if the field's behavior is not REQUIRED.
   108  func LintRequiredField(f *desc.FieldDescriptor) []lint.Problem {
   109  	return lintFieldBehavior(f, "REQUIRED")
   110  }
   111  
   112  // LintOutputOnlyField returns a problem if the field's behavior is not OUTPUT_ONLY.
   113  func LintOutputOnlyField(f *desc.FieldDescriptor) []lint.Problem {
   114  	return lintFieldBehavior(f, "OUTPUT_ONLY")
   115  }
   116  
   117  // LintFieldResourceReference returns a problem if the field does not have a resource reference annotation.
   118  func LintFieldResourceReference(f *desc.FieldDescriptor) []lint.Problem {
   119  	if ref := GetResourceReference(f); ref == nil {
   120  		return []lint.Problem{{
   121  			Message:    fmt.Sprintf("The `%s` field should include a `google.api.resource_reference` annotation.", f.GetName()),
   122  			Descriptor: f,
   123  		}}
   124  	}
   125  	return nil
   126  }
   127  
   128  func lintHTTPBody(m *desc.MethodDescriptor, want, msg string) []lint.Problem {
   129  	for _, httpRule := range GetHTTPRules(m) {
   130  		if httpRule.Body != want {
   131  			return []lint.Problem{{
   132  				Message:    fmt.Sprintf("The `%s` method should %s HTTP body.", m.GetName(), msg),
   133  				Descriptor: m,
   134  				Location:   locations.MethodHTTPRule(m),
   135  			}}
   136  		}
   137  	}
   138  	return nil
   139  }
   140  
   141  // LintNoHTTPBody returns a problem for each HTTP rule whose body is not "".
   142  func LintNoHTTPBody(m *desc.MethodDescriptor) []lint.Problem {
   143  	return lintHTTPBody(m, "", "not have an")
   144  }
   145  
   146  // LintWildcardHTTPBody returns a problem for each HTTP rule whose body is not "*".
   147  func LintWildcardHTTPBody(m *desc.MethodDescriptor) []lint.Problem {
   148  	return lintHTTPBody(m, "*", `use "*" as the`)
   149  }
   150  
   151  // LintHTTPMethod returns a problem for each HTTP rule whose HTTP method is not the given one.
   152  func LintHTTPMethod(verb string) func(*desc.MethodDescriptor) []lint.Problem {
   153  	return func(m *desc.MethodDescriptor) []lint.Problem {
   154  		for _, httpRule := range GetHTTPRules(m) {
   155  			if httpRule.Method != verb {
   156  				return []lint.Problem{{
   157  					Message:    fmt.Sprintf("The `%s` method should use the HTTP %s verb.", m.GetName(), verb),
   158  					Descriptor: m,
   159  					Location:   locations.MethodHTTPRule(m),
   160  				}}
   161  			}
   162  		}
   163  		return nil
   164  	}
   165  }
   166  
   167  // LintMethodHasMatchingRequestName returns a problem if the given method's request type does not
   168  // have a name matching the method's, with a "Request" suffix.
   169  func LintMethodHasMatchingRequestName(m *desc.MethodDescriptor) []lint.Problem {
   170  	if got, want := m.GetInputType().GetName(), m.GetName()+"Request"; got != want {
   171  		return []lint.Problem{{
   172  			Message:    fmt.Sprintf("Request message should be named after the RPC, i.e. %q.", want),
   173  			Suggestion: want,
   174  			Descriptor: m,
   175  			Location:   locations.MethodRequestType(m),
   176  		}}
   177  	}
   178  	return nil
   179  }
   180  
   181  // LintMethodHasMatchingResponseName returns a problem if the given method's response type does not
   182  // have a name matching the method's, with a "Response" suffix.
   183  func LintMethodHasMatchingResponseName(m *desc.MethodDescriptor) []lint.Problem {
   184  	if got, want := m.GetOutputType().GetName(), m.GetName()+"Response"; got != want {
   185  		return []lint.Problem{{
   186  			Message:    fmt.Sprintf("Response message should be named after the RPC, i.e. %q.", want),
   187  			Suggestion: want,
   188  			Descriptor: m,
   189  			Location:   locations.MethodResponseType(m),
   190  		}}
   191  	}
   192  	return nil
   193  }
   194  
   195  // LintHTTPURIHasParentVariable returns a problem if any of the given method's HTTP rules do not
   196  // have a parent variable in the URI.
   197  func LintHTTPURIHasParentVariable(m *desc.MethodDescriptor) []lint.Problem {
   198  	return LintHTTPURIHasVariable(m, "parent")
   199  }
   200  
   201  // LintHTTPURIHasVariable returns a problem if any of the given method's HTTP rules do not
   202  // have the given variable in the URI.
   203  func LintHTTPURIHasVariable(m *desc.MethodDescriptor, v string) []lint.Problem {
   204  	for _, httpRule := range GetHTTPRules(m) {
   205  		if _, ok := httpRule.GetVariables()[v]; !ok {
   206  			return []lint.Problem{{
   207  				Message:    fmt.Sprintf("HTTP URI should include a `%s` variable.", v),
   208  				Descriptor: m,
   209  				Location:   locations.MethodHTTPRule(m),
   210  			}}
   211  		}
   212  	}
   213  	return nil
   214  }
   215  
   216  // LintHTTPURIVariableCount returns a problem if the given method's HTTP rules
   217  // do not contain the given number of variables in the URI.
   218  func LintHTTPURIVariableCount(m *desc.MethodDescriptor, n int) []lint.Problem {
   219  	varsText := "variables"
   220  	if n == 1 {
   221  		varsText = "variable"
   222  	}
   223  
   224  	varsCount := 0
   225  	for _, httpRule := range GetHTTPRules(m) {
   226  		varsCount = max(varsCount, len(httpRule.GetVariables()))
   227  	}
   228  	if varsCount != n {
   229  		return []lint.Problem{{
   230  			Message:    fmt.Sprintf("HTTP URI should contain %d %s.", n, varsText),
   231  			Descriptor: m,
   232  			Location:   locations.MethodHTTPRule(m),
   233  		}}
   234  	}
   235  	return nil
   236  }
   237  
   238  // LintHTTPURIHasNameVariable returns a problem if any of the given method's HTTP rules do not
   239  // have a name variable in the URI.
   240  func LintHTTPURIHasNameVariable(m *desc.MethodDescriptor) []lint.Problem {
   241  	return LintHTTPURIHasVariable(m, "name")
   242  }
   243  
   244  func max(x, y int) int {
   245  	if x > y {
   246  		return x
   247  	}
   248  	return y
   249  }