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 }