github.com/googleapis/api-linter@v1.65.2/rules/internal/utils/extension.go (about)

     1  // Copyright 2019 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  	"strings"
    19  
    20  	"bitbucket.org/creachadair/stringset"
    21  	lrpb "cloud.google.com/go/longrunning/autogen/longrunningpb"
    22  	"github.com/jhump/protoreflect/desc"
    23  	apb "google.golang.org/genproto/googleapis/api/annotations"
    24  	"google.golang.org/protobuf/proto"
    25  )
    26  
    27  // GetFieldBehavior returns a stringset.Set of FieldBehavior annotations for
    28  // the given field.
    29  func GetFieldBehavior(f *desc.FieldDescriptor) stringset.Set {
    30  	opts := f.GetFieldOptions()
    31  	if x := proto.GetExtension(opts, apb.E_FieldBehavior); x != nil {
    32  		answer := stringset.New()
    33  		for _, fb := range x.([]apb.FieldBehavior) {
    34  			answer.Add(fb.String())
    35  		}
    36  		return answer
    37  	}
    38  	return nil
    39  }
    40  
    41  // GetOperationInfo returns the google.longrunning.operation_info annotation.
    42  func GetOperationInfo(m *desc.MethodDescriptor) *lrpb.OperationInfo {
    43  	if m == nil {
    44  		return nil
    45  	}
    46  	opts := m.GetMethodOptions()
    47  	if x := proto.GetExtension(opts, lrpb.E_OperationInfo); x != nil {
    48  		return x.(*lrpb.OperationInfo)
    49  	}
    50  	return nil
    51  }
    52  
    53  // GetOperationResponseType returns the message referred to by the
    54  // (google.longrunning.operation_info).response_type annotation.
    55  func GetOperationResponseType(m *desc.MethodDescriptor) *desc.MessageDescriptor {
    56  	if m == nil {
    57  		return nil
    58  	}
    59  	info := GetOperationInfo(m)
    60  	if info == nil {
    61  		return nil
    62  	}
    63  	typ := FindMessage(m.GetFile(), info.GetResponseType())
    64  
    65  	return typ
    66  }
    67  
    68  // GetResponseType returns the OutputType if the response is
    69  // not an LRO, or the ResponseType otherwise.
    70  func GetResponseType(m *desc.MethodDescriptor) *desc.MessageDescriptor {
    71  	if m == nil {
    72  		return nil
    73  	}
    74  
    75  	ot := m.GetOutputType()
    76  	if !isLongRunningOperation(ot) {
    77  		return ot
    78  	}
    79  
    80  	return GetOperationResponseType(m)
    81  }
    82  
    83  func isLongRunningOperation(m *desc.MessageDescriptor) bool {
    84  	return m.GetFile().GetPackage() == "google.longrunning" && m.GetName() == "Operation"
    85  }
    86  
    87  // GetMetadataType returns the message referred to by the
    88  // (google.longrunning.operation_info).metadata_type annotation.
    89  func GetMetadataType(m *desc.MethodDescriptor) *desc.MessageDescriptor {
    90  	if m == nil {
    91  		return nil
    92  	}
    93  	info := GetOperationInfo(m)
    94  	if info == nil {
    95  		return nil
    96  	}
    97  	typ := FindMessage(m.GetFile(), info.GetMetadataType())
    98  
    99  	return typ
   100  }
   101  
   102  // GetMethodSignatures returns the `google.api.method_signature` annotations.
   103  func GetMethodSignatures(m *desc.MethodDescriptor) [][]string {
   104  	answer := [][]string{}
   105  	opts := m.GetMethodOptions()
   106  	if x := proto.GetExtension(opts, apb.E_MethodSignature); x != nil {
   107  		for _, sig := range x.([]string) {
   108  			answer = append(answer, strings.Split(sig, ","))
   109  		}
   110  	}
   111  	return answer
   112  }
   113  
   114  // GetResource returns the google.api.resource annotation.
   115  func GetResource(m *desc.MessageDescriptor) *apb.ResourceDescriptor {
   116  	if m == nil {
   117  		return nil
   118  	}
   119  	opts := m.GetMessageOptions()
   120  	if x := proto.GetExtension(opts, apb.E_Resource); x != nil {
   121  		return x.(*apb.ResourceDescriptor)
   122  	}
   123  	return nil
   124  }
   125  
   126  // IsResource returns true if the message has a populated google.api.resource
   127  // annotation with a non-empty "type" field.
   128  func IsResource(m *desc.MessageDescriptor) bool {
   129  	if res := GetResource(m); res != nil {
   130  		return res.GetType() != ""
   131  	}
   132  	return false
   133  }
   134  
   135  // IsSingletonResource returns true if the given message is a singleton
   136  // resource according to its pattern.
   137  func IsSingletonResource(m *desc.MessageDescriptor) bool {
   138  	// If the pattern ends in something other than "}", that indicates that this is a singleton.
   139  	//
   140  	// For example:
   141  	//   publishers/{publisher}/books/{book} -- not a singleton, many books
   142  	//   publishers/*/settings -- a singleton; one settings object per publisher
   143  	for _, pattern := range GetResource(m).GetPattern() {
   144  		if !strings.HasSuffix(pattern, "}") {
   145  			return true
   146  		}
   147  	}
   148  	return false
   149  }
   150  
   151  // GetResourceDefinitions returns the google.api.resource_definition annotations
   152  // for a file.
   153  func GetResourceDefinitions(f *desc.FileDescriptor) []*apb.ResourceDescriptor {
   154  	opts := f.GetFileOptions()
   155  	if x := proto.GetExtension(opts, apb.E_ResourceDefinition); x != nil {
   156  		return x.([]*apb.ResourceDescriptor)
   157  	}
   158  	return nil
   159  }
   160  
   161  // HasResourceReference returns if the field has a google.api.resource_reference annotation.
   162  func HasResourceReference(f *desc.FieldDescriptor) bool {
   163  	if f == nil {
   164  		return false
   165  	}
   166  	return proto.HasExtension(f.GetFieldOptions(), apb.E_ResourceReference)
   167  }
   168  
   169  // GetResourceReference returns the google.api.resource_reference annotation.
   170  func GetResourceReference(f *desc.FieldDescriptor) *apb.ResourceReference {
   171  	if f == nil {
   172  		return nil
   173  	}
   174  	opts := f.GetFieldOptions()
   175  	if x := proto.GetExtension(opts, apb.E_ResourceReference); x != nil {
   176  		return x.(*apb.ResourceReference)
   177  	}
   178  	return nil
   179  }
   180  
   181  // FindResource returns first resource of type matching the reference param.
   182  // resource Type name being referenced. It looks within a given file and its
   183  // depenedencies, it cannot search within the entire protobuf package.
   184  // This is especially useful for resolving google.api.resource_reference
   185  // annotations.
   186  func FindResource(reference string, file *desc.FileDescriptor) *apb.ResourceDescriptor {
   187  	m := FindResourceMessage(reference, file)
   188  	return GetResource(m)
   189  }
   190  
   191  // FindResourceMessage returns the message containing the first resource of type
   192  // matching the resource Type name being referenced. It looks within a given
   193  // file and its depenedencies, it cannot search within the entire protobuf
   194  // package. This is especially useful for resolving
   195  // google.api.resource_reference annotations to the message that owns a
   196  // resource.
   197  func FindResourceMessage(reference string, file *desc.FileDescriptor) *desc.MessageDescriptor {
   198  	files := append(file.GetDependencies(), file)
   199  	for _, f := range files {
   200  		for _, m := range f.GetMessageTypes() {
   201  			if r := GetResource(m); r != nil {
   202  				if r.GetType() == reference {
   203  					return m
   204  				}
   205  			}
   206  		}
   207  	}
   208  	return nil
   209  }
   210  
   211  // SplitResourceTypeName splits the `Resource.type` field into the service name
   212  // and the resource type name.
   213  func SplitResourceTypeName(typ string) (service string, typeName string, ok bool) {
   214  	split := strings.Split(typ, "/")
   215  	if len(split) != 2 || split[0] == "" || split[1] == "" {
   216  		return
   217  	}
   218  
   219  	service = split[0]
   220  	typeName = split[1]
   221  	ok = true
   222  
   223  	return
   224  }
   225  
   226  // FindResourceChildren attempts to search for other resources defined in the
   227  // package that are parented by the given resource.
   228  func FindResourceChildren(parent *apb.ResourceDescriptor, file *desc.FileDescriptor) []*apb.ResourceDescriptor {
   229  	pats := parent.GetPattern()
   230  	if len(pats) == 0 {
   231  		return nil
   232  	}
   233  	// Use the first pattern in the resource because:
   234  	// 1. Patterns cannot be rearranged, so this is the true first pattern
   235  	// 2. The true first pattern is the one most likely to be used as a parent.
   236  	first := pats[0]
   237  
   238  	var children []*apb.ResourceDescriptor
   239  	files := append(file.GetDependencies(), file)
   240  	for _, f := range files {
   241  		for _, m := range f.GetMessageTypes() {
   242  			if r := GetResource(m); r != nil && r.GetType() != parent.GetType() {
   243  				for _, p := range r.GetPattern() {
   244  					if strings.HasPrefix(p, first) {
   245  						children = append(children, r)
   246  						break
   247  					}
   248  				}
   249  			}
   250  		}
   251  	}
   252  
   253  	return children
   254  }
   255  
   256  func HasFieldInfo(fd *desc.FieldDescriptor) bool {
   257  	return fd != nil && proto.HasExtension(fd.GetFieldOptions(), apb.E_FieldInfo)
   258  }
   259  
   260  func GetFieldInfo(fd *desc.FieldDescriptor) *apb.FieldInfo {
   261  	if !HasFieldInfo(fd) {
   262  		return nil
   263  	}
   264  
   265  	return proto.GetExtension(fd.GetFieldOptions(), apb.E_FieldInfo).(*apb.FieldInfo)
   266  }
   267  
   268  func HasFormat(fd *desc.FieldDescriptor) bool {
   269  	if !HasFieldInfo(fd) {
   270  		return false
   271  	}
   272  
   273  	fi := GetFieldInfo(fd)
   274  	return fi.GetFormat() != apb.FieldInfo_FORMAT_UNSPECIFIED
   275  }
   276  
   277  func GetFormat(fd *desc.FieldDescriptor) apb.FieldInfo_Format {
   278  	if !HasFormat(fd) {
   279  		return apb.FieldInfo_FORMAT_UNSPECIFIED
   280  	}
   281  	return GetFieldInfo(fd).GetFormat()
   282  }