github.com/vmware/govmomi@v0.51.0/vapi/rest/resource.go (about)

     1  // © Broadcom. All Rights Reserved.
     2  // The term “Broadcom” refers to Broadcom Inc. and/or its subsidiaries.
     3  // SPDX-License-Identifier: Apache-2.0
     4  
     5  package rest
     6  
     7  import (
     8  	"bytes"
     9  	"encoding/json"
    10  	"io"
    11  	"net/http"
    12  	"net/url"
    13  )
    14  
    15  const (
    16  	Path = "/rest"
    17  )
    18  
    19  // Resource wraps url.URL with helpers
    20  type Resource struct {
    21  	u *url.URL
    22  }
    23  
    24  func (r *Resource) String() string {
    25  	return r.u.String()
    26  }
    27  
    28  // WithSubpath appends the provided subpath to the URL.Path
    29  func (r *Resource) WithSubpath(subpath string) *Resource {
    30  	r.u.Path += "/" + subpath
    31  	return r
    32  }
    33  
    34  // WithID appends id to the URL.Path
    35  func (r *Resource) WithID(id string) *Resource {
    36  	r.u.Path += "/id:" + id
    37  	return r
    38  }
    39  
    40  // WithAction sets adds action to the URL.RawQuery
    41  func (r *Resource) WithAction(action string) *Resource {
    42  	return r.WithParam("~action", action)
    43  }
    44  
    45  // WithParam adds one parameter on the URL.RawQuery
    46  func (r *Resource) WithParam(name string, value string) *Resource {
    47  	// ParseQuery handles empty case, and we control access to query string so shouldn't encounter an error case
    48  	params, err := url.ParseQuery(r.u.RawQuery)
    49  	if err != nil {
    50  		panic(err)
    51  	}
    52  	params[name] = append(params[name], value)
    53  	r.u.RawQuery = params.Encode()
    54  	return r
    55  }
    56  
    57  // WithPathEncodedParam appends a parameter on the URL.RawQuery,
    58  // For special cases where URL Path-style encoding is needed
    59  func (r *Resource) WithPathEncodedParam(name string, value string) *Resource {
    60  	t := &url.URL{Path: value}
    61  	encodedValue := t.String()
    62  	t = &url.URL{Path: name}
    63  	encodedName := t.String()
    64  	// ParseQuery handles empty case, and we control access to query string so shouldn't encounter an error case
    65  	params, err := url.ParseQuery(r.u.RawQuery)
    66  	if err != nil {
    67  		panic(err)
    68  	}
    69  	// Values.Encode() doesn't escape exactly how we want, so we need to build the query string ourselves
    70  	if len(params) >= 1 {
    71  		r.u.RawQuery = r.u.RawQuery + "&" + encodedName + "=" + encodedValue
    72  	} else {
    73  		r.u.RawQuery = r.u.RawQuery + encodedName + "=" + encodedValue
    74  	}
    75  	return r
    76  }
    77  
    78  // Request returns a new http.Request for the given method.
    79  // An optional body can be provided for POST and PATCH methods.
    80  func (r *Resource) Request(method string, body ...any) *http.Request {
    81  	rdr := io.MultiReader() // empty body by default
    82  	if len(body) != 0 {
    83  		rdr = encode(body[0])
    84  	}
    85  	req, err := http.NewRequest(method, r.u.String(), rdr)
    86  	if err != nil {
    87  		panic(err)
    88  	}
    89  	return req
    90  }
    91  
    92  type errorReader struct {
    93  	e error
    94  }
    95  
    96  func (e errorReader) Read([]byte) (int, error) {
    97  	return -1, e.e
    98  }
    99  
   100  // encode body as JSON, deferring any errors until io.Reader is used.
   101  func encode(body any) io.Reader {
   102  	var b bytes.Buffer
   103  	err := json.NewEncoder(&b).Encode(body)
   104  	if err != nil {
   105  		return errorReader{err}
   106  	}
   107  	return &b
   108  }