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  )