github.com/googleapis/api-linter@v1.65.2/rules/aip0123/duplicate_resource.go (about) 1 // Copyright 2021 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 aip0123 16 17 import ( 18 "fmt" 19 "sort" 20 "strings" 21 22 "github.com/googleapis/api-linter/lint" 23 "github.com/googleapis/api-linter/locations" 24 "github.com/googleapis/api-linter/rules/internal/utils" 25 "github.com/jhump/protoreflect/desc" 26 dpb "google.golang.org/protobuf/types/descriptorpb" 27 ) 28 29 type resourceDef struct { 30 desc desc.Descriptor 31 idx int 32 } 33 34 func (d *resourceDef) String() string { 35 switch d.desc.(type) { 36 case *desc.FileDescriptor: 37 return fmt.Sprintf("`google.api.resource_definition` %d in file `%s`", d.idx, d.desc.GetFullyQualifiedName()) 38 case *desc.MessageDescriptor: 39 return fmt.Sprintf("message `%s`", d.desc.GetFullyQualifiedName()) 40 default: 41 return fmt.Sprintf("unexpected descriptor type %T", d.desc) 42 } 43 } 44 45 func (d *resourceDef) location() *dpb.SourceCodeInfo_Location { 46 switch desc := d.desc.(type) { 47 case *desc.FileDescriptor: 48 return locations.FileResourceDefinition(desc, d.idx) 49 case *desc.MessageDescriptor: 50 return locations.MessageResource(desc) 51 default: 52 return nil 53 } 54 } 55 56 func resourceDefsInFile(f *desc.FileDescriptor, defs map[string][]resourceDef) map[string][]resourceDef { 57 for i, rd := range utils.GetResourceDefinitions(f) { 58 if t := rd.GetType(); t != "" { 59 defs[t] = append(defs[t], resourceDef{f, i}) 60 } 61 } 62 for _, m := range f.GetMessageTypes() { 63 resourceDefsInMsg(m, defs) 64 } 65 return defs 66 } 67 68 func resourceDefsInMsg(m *desc.MessageDescriptor, defs map[string][]resourceDef) { 69 if t := utils.GetResource(m).GetType(); t != "" { 70 defs[t] = append(defs[t], resourceDef{m, -1}) 71 } 72 for _, m := range m.GetNestedMessageTypes() { 73 resourceDefsInMsg(m, defs) 74 } 75 } 76 77 func allDeps(f *desc.FileDescriptor, deps map[string]*desc.FileDescriptor) map[string]*desc.FileDescriptor { 78 for _, f := range f.GetDependencies() { 79 name := f.GetName() 80 if _, ok := deps[name]; !ok { 81 deps[name] = f 82 allDeps(f, deps) 83 } 84 } 85 return deps 86 } 87 88 var duplicateResource = &lint.FileRule{ 89 Name: lint.NewRuleName(123, "duplicate-resource"), 90 LintFile: func(f *desc.FileDescriptor) []lint.Problem { 91 defsInFile := resourceDefsInFile(f, map[string][]resourceDef{}) 92 if len(defsInFile) == 0 { 93 return nil 94 } 95 96 defsInDeps := map[string][]resourceDef{} 97 for _, f := range allDeps(f, map[string]*desc.FileDescriptor{}) { 98 resourceDefsInFile(f, defsInDeps) 99 } 100 101 var resourceTypes []string 102 for t := range defsInFile { 103 resourceTypes = append(resourceTypes, t) 104 } 105 sort.Strings(resourceTypes) 106 107 ps := []lint.Problem{} 108 for _, t := range resourceTypes { 109 ds := defsInFile[t] 110 locs := []string{} 111 // Duplicates in this file. 112 if len(ds) > 1 { 113 for _, d := range ds { 114 locs = append(locs, d.String()) 115 } 116 } 117 // Duplicates in dependencies. 118 for _, d := range defsInDeps[t] { 119 locs = append(locs, d.String()) 120 } 121 if len(locs) == 0 { 122 continue 123 } 124 sort.Strings(locs) 125 msg := fmt.Sprintf("Multiple definitions for resource %q: %s.", t, strings.Join(locs, ", ")) 126 for _, d := range ds { 127 ps = append(ps, lint.Problem{ 128 Message: msg, 129 Descriptor: d.desc, 130 Location: d.location(), 131 }) 132 } 133 } 134 return ps 135 }, 136 }