github.com/googleapis/api-linter@v1.65.2/rules/internal/utils/find.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  	"sort"
    19  	"strings"
    20  
    21  	"github.com/jhump/protoreflect/desc"
    22  	"google.golang.org/protobuf/types/descriptorpb"
    23  )
    24  
    25  // FindMessage looks for a message in a file and all imports within the
    26  // same package.
    27  func FindMessage(f *desc.FileDescriptor, name string) *desc.MessageDescriptor {
    28  	// FileDescriptor.FindMessage requires fully-qualified message names;
    29  	// attempt to infer that.
    30  	if !strings.Contains(name, ".") && f.GetPackage() != "" {
    31  		name = f.GetPackage() + "." + name
    32  	}
    33  
    34  	// Attempt to find the message in the file provided.
    35  	if m := f.FindMessage(name); m != nil {
    36  		return m
    37  	}
    38  
    39  	// Attempt to find the message in any dependency files if they are in the
    40  	// same package.
    41  	for _, dep := range f.GetDependencies() {
    42  		if f.GetPackage() == dep.GetPackage() {
    43  			if m := FindMessage(dep, name); m != nil {
    44  				return m
    45  			}
    46  		}
    47  	}
    48  
    49  	// Whelp, no luck. Too bad.
    50  	return nil
    51  }
    52  
    53  // FindMethod searches a file and all imports within the same package, and
    54  // returns the method with a given name, or nil if none is found.
    55  func FindMethod(f *desc.FileDescriptor, name string) *desc.MethodDescriptor {
    56  	for _, s := range getServices(f) {
    57  		for _, m := range s.GetMethods() {
    58  			if m.GetName() == name {
    59  				return m
    60  			}
    61  		}
    62  	}
    63  	return nil
    64  }
    65  
    66  // getServices finds all services in a file and all imports within the
    67  // same package.
    68  func getServices(f *desc.FileDescriptor) []*desc.ServiceDescriptor {
    69  	answer := f.GetServices()
    70  	for _, dep := range f.GetDependencies() {
    71  		if f.GetPackage() == dep.GetPackage() {
    72  			answer = append(answer, getServices(dep)...)
    73  		}
    74  	}
    75  	return answer
    76  }
    77  
    78  // GetAllDependencies returns all dependencies.
    79  func GetAllDependencies(file *desc.FileDescriptor) map[string]*desc.FileDescriptor {
    80  	answer := map[string]*desc.FileDescriptor{file.GetName(): file}
    81  	for _, f := range file.GetDependencies() {
    82  		if _, found := answer[f.GetName()]; !found {
    83  			answer[f.GetName()] = f
    84  			for name, f2 := range GetAllDependencies(f) {
    85  				answer[name] = f2
    86  			}
    87  		}
    88  	}
    89  	return answer
    90  }
    91  
    92  type fieldSorter []*desc.FieldDescriptor
    93  
    94  // Len is part of sort.Interface.
    95  func (f fieldSorter) Len() int {
    96  	return len(f)
    97  }
    98  
    99  // Swap is part of sort.Interface.
   100  func (f fieldSorter) Swap(i, j int) {
   101  	f[i], f[j] = f[j], f[i]
   102  }
   103  
   104  // Less is part of sort.Interface. Compare field number.
   105  func (f fieldSorter) Less(i, j int) bool {
   106  	return f[i].GetNumber() < f[j].GetNumber()
   107  }
   108  
   109  // GetRepeatedMessageFields returns all fields labeled `repeated` of type
   110  // Message in the given message, sorted in field number order.
   111  func GetRepeatedMessageFields(m *desc.MessageDescriptor) []*desc.FieldDescriptor {
   112  	var fields fieldSorter
   113  
   114  	// If an unresolable message is fed into this helper, return empty slice.
   115  	if m == nil {
   116  		return fields
   117  	}
   118  
   119  	for _, f := range m.GetFields() {
   120  		if f.IsRepeated() && f.GetType() == descriptorpb.FieldDescriptorProto_TYPE_MESSAGE {
   121  			fields = append(fields, f)
   122  		}
   123  	}
   124  
   125  	sort.Sort(fields)
   126  
   127  	return fields
   128  }
   129  
   130  // FindFieldDotNotation returns a field descriptor from a given message that
   131  // corresponds to the dot separated path e.g. "book.name". If the path is
   132  // unresolable the method returns nil. This is especially useful for resolving
   133  // path variables in google.api.http and nested fields in
   134  // google.api.method_signature annotations.
   135  func FindFieldDotNotation(msg *desc.MessageDescriptor, ref string) *desc.FieldDescriptor {
   136  	path := strings.Split(ref, ".")
   137  	end := len(path) - 1
   138  	for i, seg := range path {
   139  		field := msg.FindFieldByName(seg)
   140  		if field == nil {
   141  			return nil
   142  		}
   143  
   144  		if m := field.GetMessageType(); m != nil && i != end {
   145  			msg = m
   146  			continue
   147  		}
   148  
   149  		return field
   150  	}
   151  
   152  	return nil
   153  }