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 }