github.com/googleapis/api-linter@v1.65.2/rules/aip0203/field_behavior_required.go (about) 1 // Copyright 2023 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 aip0203 16 17 import ( 18 "fmt" 19 20 "bitbucket.org/creachadair/stringset" 21 "github.com/googleapis/api-linter/lint" 22 "github.com/googleapis/api-linter/rules/internal/utils" 23 "github.com/jhump/protoreflect/desc" 24 ) 25 26 var minimumRequiredFieldBehavior = stringset.New( 27 "OPTIONAL", "REQUIRED", "OUTPUT_ONLY", "IMMUTABLE", 28 ) 29 30 var excusedResourceFields = stringset.New( 31 "name", // Uses https://google.aip.dev/203#identifier 32 "etag", // Prohibited by https://google.aip.dev/154 33 ) 34 35 var fieldBehaviorRequired = &lint.MethodRule{ 36 Name: lint.NewRuleName(203, "field-behavior-required"), 37 LintMethod: func(m *desc.MethodDescriptor) []lint.Problem { 38 req := m.GetInputType() 39 p := m.GetFile().GetPackage() 40 ps := problems(req, p, map[desc.Descriptor]bool{}) 41 if len(ps) == 0 { 42 return nil 43 } 44 45 return ps 46 }, 47 } 48 49 func problems(m *desc.MessageDescriptor, pkg string, visited map[desc.Descriptor]bool) []lint.Problem { 50 var ps []lint.Problem 51 52 for _, f := range m.GetFields() { 53 // ignore the field if it was already visited 54 if ok := visited[f]; ok { 55 continue 56 } 57 visited[f] = true 58 59 if utils.IsResource(m) && excusedResourceFields.Contains(f.GetName()) { 60 continue 61 } 62 63 // Ignore a field if it is a OneOf (do not ignore children) 64 if f.AsFieldDescriptorProto().OneofIndex == nil { 65 p := checkFieldBehavior(f) 66 if p != nil { 67 ps = append(ps, *p) 68 } 69 } 70 71 if mt := f.GetMessageType(); mt != nil && !mt.IsMapEntry() && mt.GetFile().GetPackage() == pkg { 72 ps = append(ps, problems(mt, pkg, visited)...) 73 } 74 } 75 76 return ps 77 } 78 79 func checkFieldBehavior(f *desc.FieldDescriptor) *lint.Problem { 80 fb := utils.GetFieldBehavior(f) 81 82 if len(fb) == 0 { 83 return &lint.Problem{ 84 Message: fmt.Sprintf("google.api.field_behavior annotation must be set on %q and contain one of, \"%v\"", f.GetName(), minimumRequiredFieldBehavior), 85 Descriptor: f, 86 } 87 } 88 89 if !minimumRequiredFieldBehavior.Intersects(fb) { 90 // check for at least one valid annotation 91 return &lint.Problem{ 92 Message: fmt.Sprintf( 93 "google.api.field_behavior on field %q must contain at least one, \"%v\"", f.GetName(), minimumRequiredFieldBehavior), 94 Descriptor: f, 95 } 96 } 97 98 return nil 99 }