resource.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120
  1. /*
  2. Copyright (c) 2019 VMware, Inc. All Rights Reserved.
  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. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package rest
  14. import (
  15. "bytes"
  16. "encoding/json"
  17. "io"
  18. "net/http"
  19. "net/url"
  20. )
  21. const (
  22. Path = "/rest"
  23. )
  24. // Resource wraps url.URL with helpers
  25. type Resource struct {
  26. u *url.URL
  27. }
  28. func (r *Resource) String() string {
  29. return r.u.String()
  30. }
  31. // WithSubpath appends the provided subpath to the URL.Path
  32. func (r *Resource) WithSubpath(subpath string) *Resource {
  33. r.u.Path += "/" + subpath
  34. return r
  35. }
  36. // WithID appends id to the URL.Path
  37. func (r *Resource) WithID(id string) *Resource {
  38. r.u.Path += "/id:" + id
  39. return r
  40. }
  41. // WithAction sets adds action to the URL.RawQuery
  42. func (r *Resource) WithAction(action string) *Resource {
  43. return r.WithParam("~action", action)
  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. // WithPathEncodedParam appends a parameter on the URL.RawQuery,
  57. // For special cases where URL Path-style encoding is needed
  58. func (r *Resource) WithPathEncodedParam(name string, value string) *Resource {
  59. t := &url.URL{Path: value}
  60. encodedValue := t.String()
  61. t = &url.URL{Path: name}
  62. encodedName := t.String()
  63. // ParseQuery handles empty case, and we control access to query string so shouldn't encounter an error case
  64. params, err := url.ParseQuery(r.u.RawQuery)
  65. if err != nil {
  66. panic(err)
  67. }
  68. // Values.Encode() doesn't escape exactly how we want, so we need to build the query string ourselves
  69. if len(params) >= 1 {
  70. r.u.RawQuery = r.u.RawQuery + "&" + encodedName + "=" + encodedValue
  71. } else {
  72. r.u.RawQuery = r.u.RawQuery + encodedName + "=" + encodedValue
  73. }
  74. return r
  75. }
  76. // Request returns a new http.Request for the given method.
  77. // An optional body can be provided for POST and PATCH methods.
  78. func (r *Resource) Request(method string, body ...interface{}) *http.Request {
  79. rdr := io.MultiReader() // empty body by default
  80. if len(body) != 0 {
  81. rdr = encode(body[0])
  82. }
  83. req, err := http.NewRequest(method, r.u.String(), rdr)
  84. if err != nil {
  85. panic(err)
  86. }
  87. return req
  88. }
  89. type errorReader struct {
  90. e error
  91. }
  92. func (e errorReader) Read([]byte) (int, error) {
  93. return -1, e.e
  94. }
  95. // encode body as JSON, deferring any errors until io.Reader is used.
  96. func encode(body interface{}) io.Reader {
  97. var b bytes.Buffer
  98. err := json.NewEncoder(&b).Encode(body)
  99. if err != nil {
  100. return errorReader{err}
  101. }
  102. return &b
  103. }