github.com/googleapis/api-linter@v1.65.2/rules/internal/utils/http.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 "regexp" 19 20 "github.com/jhump/protoreflect/desc" 21 apb "google.golang.org/genproto/googleapis/api/annotations" 22 "google.golang.org/protobuf/proto" 23 ) 24 25 // HasHTTPRules returns true when the given method descriptor is annotated with 26 // a google.api.http option. 27 func HasHTTPRules(m *desc.MethodDescriptor) bool { 28 got := proto.GetExtension(m.GetMethodOptions(), apb.E_Http).(*apb.HttpRule) 29 return got != nil 30 } 31 32 // GetHTTPRules returns a slice of HTTP rules for a given method descriptor. 33 // 34 // Note: This returns a slice -- it takes the google.api.http annotation, 35 // and then flattens the values in `additional_bindings`. 36 // This allows rule authors to simply range over all of the HTTP rules, 37 // since the common case is to want to apply the checks to all of them. 38 func GetHTTPRules(m *desc.MethodDescriptor) []*HTTPRule { 39 rules := []*HTTPRule{} 40 41 // Get the method options. 42 opts := m.GetMethodOptions() 43 44 // Get the "primary" rule (the direct google.api.http annotation). 45 if x := proto.GetExtension(opts, apb.E_Http); x != nil { 46 httpRule := x.(*apb.HttpRule) 47 if parsedRule := parseRule(httpRule); parsedRule != nil { 48 rules = append(rules, parsedRule) 49 50 // Add any additional bindings and flatten them into `rules`. 51 for _, binding := range httpRule.GetAdditionalBindings() { 52 rules = append(rules, parseRule(binding)) 53 } 54 } 55 } 56 57 // Done; return the rules. 58 return rules 59 } 60 61 func parseRule(rule *apb.HttpRule) *HTTPRule { 62 oneof := map[string]string{ 63 "GET": rule.GetGet(), 64 "POST": rule.GetPost(), 65 "PUT": rule.GetPut(), 66 "PATCH": rule.GetPatch(), 67 "DELETE": rule.GetDelete(), 68 } 69 if custom := rule.GetCustom(); custom != nil { 70 oneof[custom.GetKind()] = custom.GetPath() 71 } 72 for method, uri := range oneof { 73 if uri != "" { 74 return &HTTPRule{ 75 Method: method, 76 URI: uri, 77 Body: rule.GetBody(), 78 ResponseBody: rule.GetResponseBody(), 79 } 80 } 81 } 82 return nil 83 } 84 85 // HTTPRule defines a parsed, easier-to-query equivalent to `apb.HttpRule`. 86 type HTTPRule struct { 87 // The HTTP method. Guaranteed to be in all caps. 88 // This is set to "CUSTOM" if the Custom property is set. 89 Method string 90 91 // The HTTP URI (the value corresponding to the selected HTTP method). 92 URI string 93 94 // The `body` value forwarded from the generated proto's HttpRule. 95 Body string 96 97 // The `response_body` value forwarded from the generated proto's HttpRule. 98 ResponseBody string 99 } 100 101 // GetVariables returns the variable segments in a URI as a map. 102 // 103 // For a given variable, the key is the variable's field path. The value is the 104 // variable's template, which will match segment(s) of the URL. 105 // 106 // For more details on the path template syntax, see 107 // https://github.com/googleapis/googleapis/blob/6e1a5a066659794f26091674e3668229e7750052/google/api/http.proto#L224. 108 func (h *HTTPRule) GetVariables() map[string]string { 109 vars := map[string]string{} 110 111 // Replace the version template variable with "v". 112 uri := VersionedSegment.ReplaceAllString(h.URI, "v") 113 for _, match := range plainVar.FindAllStringSubmatch(uri, -1) { 114 vars[match[1]] = "*" 115 } 116 for _, match := range varSegment.FindAllStringSubmatch(uri, -1) { 117 vars[match[1]] = match[2] 118 } 119 return vars 120 } 121 122 // GetPlainURI returns the URI with variable segment information removed. 123 func (h *HTTPRule) GetPlainURI() string { 124 return plainVar.ReplaceAllString( 125 varSegment.ReplaceAllString( 126 VersionedSegment.ReplaceAllString(h.URI, "v"), 127 "$2"), 128 "*") 129 } 130 131 var ( 132 plainVar = regexp.MustCompile(`\{([^}=]+)\}`) 133 varSegment = regexp.MustCompile(`\{([^}=]+)=([^}]+)\}`) 134 // VersionedSegment is a regex to extract the API version from 135 // an HTTP path. 136 VersionedSegment = regexp.MustCompile(`\{\$api_version\}`) 137 )