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  }