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 }