github.com/googleapis/api-linter@v1.65.2/rules/internal/utils/declarative_friendly.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 "strings" 19 20 "bitbucket.org/creachadair/stringset" 21 "github.com/jhump/protoreflect/desc" 22 "github.com/stoewer/go-strcase" 23 apb "google.golang.org/genproto/googleapis/api/annotations" 24 ) 25 26 // DeclarativeFriendlyResource returns the declarative-friendly resource 27 // associated with this descriptor. 28 // 29 // For messages: 30 // If the message is annotated with google.api.resource and 31 // style: DECLARATIVE_FRIENDLY is set, that message is returned. 32 // If the message is a standard method request message for a resource with 33 // google.api.resource and style:DECLARATIVE_FRIENDLY set, then the resource 34 // is returned. 35 // 36 // For methods: 37 // If the output message is a declarative-friendly resource, it is returned. 38 // If the method begins with "List" and the first repeated field is a 39 // declarative-friendly resource, the resource is returned. 40 // If the method begins with "Delete", the return type is Empty, and an 41 // appropriate resource message is found and is declarative-friendly, that 42 // resource is returned. 43 // If the method is a custom method where a matching resource is found (by 44 // subset checks on the name) and is declarative-friendly, the resource is 45 // returned. 46 // 47 // If there is no declarative-friendly resource, it returns nil. 48 func DeclarativeFriendlyResource(d desc.Descriptor) *desc.MessageDescriptor { 49 switch m := d.(type) { 50 case *desc.MessageDescriptor: 51 // Get the google.api.resource annotation and see if it is styled 52 // declarative-friendly. 53 if resource := GetResource(m); resource != nil { 54 for _, style := range resource.GetStyle() { 55 if style == apb.ResourceDescriptor_DECLARATIVE_FRIENDLY { 56 return m 57 } 58 } 59 } 60 61 // If this is a standard method request message, find the corresponding 62 // resource message. The easiest way to do this is to farm it out to the 63 // corresponding method. 64 if n := m.GetName(); strings.HasSuffix(n, "Request") { 65 if method := FindMethod(m.GetFile(), strings.TrimSuffix(n, "Request")); method != nil { 66 return DeclarativeFriendlyResource(method) 67 } 68 } 69 case *desc.MethodDescriptor: 70 response := m.GetOutputType() 71 72 // If this is a Delete method (AIP-135) with a return value of Empty, 73 // try to find the resource. 74 // 75 // Note: This needs to precede the LRO logic because Delete requests 76 // may resolve to Empty, in which case FindMessage will return nil and 77 // short-circuit this logic. 78 if strings.HasPrefix(m.GetName(), "Delete") && stringset.New("Empty", "Operation").Contains(m.GetOutputType().GetName()) { 79 if resource := FindMessage(m.GetFile(), strings.TrimPrefix(m.GetName(), "Delete")); resource != nil { 80 return DeclarativeFriendlyResource(resource) 81 } 82 } 83 84 // If the method is an LRO, then get the response type from the 85 // operation_info annotation. 86 if IsOperation(response) { 87 if opInfo := GetOperationInfo(m); opInfo != nil { 88 response = FindMessage(m.GetFile(), opInfo.GetResponseType()) 89 90 // Sanity check: We may not have found the message. 91 // If that is the case, give up and assume the method is not 92 // declarative-friendly. 93 if response == nil { 94 return nil 95 } 96 } 97 } 98 99 // If the return value has a google.api.resource annotation, we can 100 // assume it is the resource and check it. 101 if IsResource(response) { 102 return DeclarativeFriendlyResource(response) 103 } 104 105 // If the return value is a List response (AIP-132), we should be able 106 // to find the resource as a field in the response. 107 if n := response.GetName(); strings.HasPrefix(n, "List") && strings.HasSuffix(n, "Response") { 108 for _, field := range response.GetFields() { 109 if field.IsRepeated() && field.GetMessageType() != nil { 110 return DeclarativeFriendlyResource(field.GetMessageType()) 111 } 112 } 113 } 114 115 // At this point, we probably have a custom method. 116 // Try to identify a resource by whittling away at the method name and 117 // seeing if there is a match. 118 snakeName := strings.Split(strcase.SnakeCase(m.GetName()), "_") 119 for i := 1; i < len(snakeName); i++ { 120 name := strcase.UpperCamelCase(strings.Join(snakeName[i:], "_")) 121 if resource := FindMessage(m.GetFile(), name); resource != nil { 122 return DeclarativeFriendlyResource(resource) 123 } 124 } 125 } 126 return nil 127 } 128 129 // IsDeclarativeFriendlyMessage returns true if the descriptor is 130 // declarative-friendly (if DeclarativeFriendlyResource(m) is not nil). 131 func IsDeclarativeFriendlyMessage(m *desc.MessageDescriptor) bool { 132 return DeclarativeFriendlyResource(m) != nil 133 } 134 135 // IsDeclarativeFriendlyMethod returns true if the method is for a 136 // declarative-friendly resource (if DeclarativeFriendlyResource(m) is not nil). 137 func IsDeclarativeFriendlyMethod(m *desc.MethodDescriptor) bool { 138 return DeclarativeFriendlyResource(m) != nil 139 }