results.go 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404
  1. package gophercloud
  2. import (
  3. "bytes"
  4. "encoding/json"
  5. "fmt"
  6. "io"
  7. "net/http"
  8. "reflect"
  9. "strconv"
  10. "time"
  11. )
  12. /*
  13. Result is an internal type to be used by individual resource packages, but its
  14. methods will be available on a wide variety of user-facing embedding types.
  15. It acts as a base struct that other Result types, returned from request
  16. functions, can embed for convenience. All Results capture basic information
  17. from the HTTP transaction that was performed, including the response body,
  18. HTTP headers, and any errors that happened.
  19. Generally, each Result type will have an Extract method that can be used to
  20. further interpret the result's payload in a specific context. Extensions or
  21. providers can then provide additional extraction functions to pull out
  22. provider- or extension-specific information as well.
  23. */
  24. type Result struct {
  25. // Body is the payload of the HTTP response from the server. In most cases,
  26. // this will be the deserialized JSON structure.
  27. Body interface{}
  28. // Header contains the HTTP header structure from the original response.
  29. Header http.Header
  30. // Err is an error that occurred during the operation. It's deferred until
  31. // extraction to make it easier to chain the Extract call.
  32. Err error
  33. }
  34. // ExtractInto allows users to provide an object into which `Extract` will extract
  35. // the `Result.Body`. This would be useful for OpenStack providers that have
  36. // different fields in the response object than OpenStack proper.
  37. func (r Result) ExtractInto(to interface{}) error {
  38. if r.Err != nil {
  39. return r.Err
  40. }
  41. if reader, ok := r.Body.(io.Reader); ok {
  42. if readCloser, ok := reader.(io.Closer); ok {
  43. defer readCloser.Close()
  44. }
  45. return json.NewDecoder(reader).Decode(to)
  46. }
  47. b, err := json.Marshal(r.Body)
  48. if err != nil {
  49. return err
  50. }
  51. err = json.Unmarshal(b, to)
  52. return err
  53. }
  54. func (r Result) extractIntoPtr(to interface{}, label string) error {
  55. if label == "" {
  56. return r.ExtractInto(&to)
  57. }
  58. var m map[string]interface{}
  59. err := r.ExtractInto(&m)
  60. if err != nil {
  61. return err
  62. }
  63. b, err := json.Marshal(m[label])
  64. if err != nil {
  65. return err
  66. }
  67. toValue := reflect.ValueOf(to)
  68. if toValue.Kind() == reflect.Ptr {
  69. toValue = toValue.Elem()
  70. }
  71. switch toValue.Kind() {
  72. case reflect.Slice:
  73. typeOfV := toValue.Type().Elem()
  74. if typeOfV.Kind() == reflect.Struct {
  75. if typeOfV.NumField() > 0 && typeOfV.Field(0).Anonymous {
  76. newSlice := reflect.MakeSlice(reflect.SliceOf(typeOfV), 0, 0)
  77. newType := reflect.New(typeOfV).Elem()
  78. for _, v := range m[label].([]interface{}) {
  79. b, err := json.Marshal(v)
  80. if err != nil {
  81. return err
  82. }
  83. for i := 0; i < newType.NumField(); i++ {
  84. s := newType.Field(i).Addr().Interface()
  85. err = json.NewDecoder(bytes.NewReader(b)).Decode(s)
  86. if err != nil {
  87. return err
  88. }
  89. }
  90. newSlice = reflect.Append(newSlice, newType)
  91. }
  92. toValue.Set(newSlice)
  93. }
  94. }
  95. case reflect.Struct:
  96. typeOfV := toValue.Type()
  97. if typeOfV.NumField() > 0 && typeOfV.Field(0).Anonymous {
  98. for i := 0; i < toValue.NumField(); i++ {
  99. toField := toValue.Field(i)
  100. if toField.Kind() == reflect.Struct {
  101. s := toField.Addr().Interface()
  102. err = json.NewDecoder(bytes.NewReader(b)).Decode(s)
  103. if err != nil {
  104. return err
  105. }
  106. }
  107. }
  108. }
  109. }
  110. err = json.Unmarshal(b, &to)
  111. return err
  112. }
  113. // ExtractIntoStructPtr will unmarshal the Result (r) into the provided
  114. // interface{} (to).
  115. //
  116. // NOTE: For internal use only
  117. //
  118. // `to` must be a pointer to an underlying struct type
  119. //
  120. // If provided, `label` will be filtered out of the response
  121. // body prior to `r` being unmarshalled into `to`.
  122. func (r Result) ExtractIntoStructPtr(to interface{}, label string) error {
  123. if r.Err != nil {
  124. return r.Err
  125. }
  126. t := reflect.TypeOf(to)
  127. if k := t.Kind(); k != reflect.Ptr {
  128. return fmt.Errorf("Expected pointer, got %v", k)
  129. }
  130. switch t.Elem().Kind() {
  131. case reflect.Struct:
  132. return r.extractIntoPtr(to, label)
  133. default:
  134. return fmt.Errorf("Expected pointer to struct, got: %v", t)
  135. }
  136. }
  137. // ExtractIntoSlicePtr will unmarshal the Result (r) into the provided
  138. // interface{} (to).
  139. //
  140. // NOTE: For internal use only
  141. //
  142. // `to` must be a pointer to an underlying slice type
  143. //
  144. // If provided, `label` will be filtered out of the response
  145. // body prior to `r` being unmarshalled into `to`.
  146. func (r Result) ExtractIntoSlicePtr(to interface{}, label string) error {
  147. if r.Err != nil {
  148. return r.Err
  149. }
  150. t := reflect.TypeOf(to)
  151. if k := t.Kind(); k != reflect.Ptr {
  152. return fmt.Errorf("Expected pointer, got %v", k)
  153. }
  154. switch t.Elem().Kind() {
  155. case reflect.Slice:
  156. return r.extractIntoPtr(to, label)
  157. default:
  158. return fmt.Errorf("Expected pointer to slice, got: %v", t)
  159. }
  160. }
  161. // PrettyPrintJSON creates a string containing the full response body as
  162. // pretty-printed JSON. It's useful for capturing test fixtures and for
  163. // debugging extraction bugs. If you include its output in an issue related to
  164. // a buggy extraction function, we will all love you forever.
  165. func (r Result) PrettyPrintJSON() string {
  166. pretty, err := json.MarshalIndent(r.Body, "", " ")
  167. if err != nil {
  168. panic(err.Error())
  169. }
  170. return string(pretty)
  171. }
  172. // ErrResult is an internal type to be used by individual resource packages, but
  173. // its methods will be available on a wide variety of user-facing embedding
  174. // types.
  175. //
  176. // It represents results that only contain a potential error and
  177. // nothing else. Usually, if the operation executed successfully, the Err field
  178. // will be nil; otherwise it will be stocked with a relevant error. Use the
  179. // ExtractErr method
  180. // to cleanly pull it out.
  181. type ErrResult struct {
  182. Result
  183. }
  184. // ExtractErr is a function that extracts error information, or nil, from a result.
  185. func (r ErrResult) ExtractErr() error {
  186. return r.Err
  187. }
  188. /*
  189. HeaderResult is an internal type to be used by individual resource packages, but
  190. its methods will be available on a wide variety of user-facing embedding types.
  191. It represents a result that only contains an error (possibly nil) and an
  192. http.Header. This is used, for example, by the objectstorage packages in
  193. openstack, because most of the operations don't return response bodies, but do
  194. have relevant information in headers.
  195. */
  196. type HeaderResult struct {
  197. Result
  198. }
  199. // ExtractInto allows users to provide an object into which `Extract` will
  200. // extract the http.Header headers of the result.
  201. func (r HeaderResult) ExtractInto(to interface{}) error {
  202. if r.Err != nil {
  203. return r.Err
  204. }
  205. tmpHeaderMap := map[string]string{}
  206. for k, v := range r.Header {
  207. if len(v) > 0 {
  208. tmpHeaderMap[k] = v[0]
  209. }
  210. }
  211. b, err := json.Marshal(tmpHeaderMap)
  212. if err != nil {
  213. return err
  214. }
  215. err = json.Unmarshal(b, to)
  216. return err
  217. }
  218. // RFC3339Milli describes a common time format used by some API responses.
  219. const RFC3339Milli = "2006-01-02T15:04:05.999999Z"
  220. type JSONRFC3339Milli time.Time
  221. func (jt *JSONRFC3339Milli) UnmarshalJSON(data []byte) error {
  222. b := bytes.NewBuffer(data)
  223. dec := json.NewDecoder(b)
  224. var s string
  225. if err := dec.Decode(&s); err != nil {
  226. return err
  227. }
  228. t, err := time.Parse(RFC3339Milli, s)
  229. if err != nil {
  230. return err
  231. }
  232. *jt = JSONRFC3339Milli(t)
  233. return nil
  234. }
  235. const RFC3339MilliNoZ = "2006-01-02T15:04:05.999999"
  236. type JSONRFC3339MilliNoZ time.Time
  237. func (jt *JSONRFC3339MilliNoZ) UnmarshalJSON(data []byte) error {
  238. var s string
  239. if err := json.Unmarshal(data, &s); err != nil {
  240. return err
  241. }
  242. if s == "" {
  243. return nil
  244. }
  245. t, err := time.Parse(RFC3339MilliNoZ, s)
  246. if err != nil {
  247. return err
  248. }
  249. *jt = JSONRFC3339MilliNoZ(t)
  250. return nil
  251. }
  252. type JSONRFC1123 time.Time
  253. func (jt *JSONRFC1123) UnmarshalJSON(data []byte) error {
  254. var s string
  255. if err := json.Unmarshal(data, &s); err != nil {
  256. return err
  257. }
  258. if s == "" {
  259. return nil
  260. }
  261. t, err := time.Parse(time.RFC1123, s)
  262. if err != nil {
  263. return err
  264. }
  265. *jt = JSONRFC1123(t)
  266. return nil
  267. }
  268. type JSONUnix time.Time
  269. func (jt *JSONUnix) UnmarshalJSON(data []byte) error {
  270. var s string
  271. if err := json.Unmarshal(data, &s); err != nil {
  272. return err
  273. }
  274. if s == "" {
  275. return nil
  276. }
  277. unix, err := strconv.ParseInt(s, 10, 64)
  278. if err != nil {
  279. return err
  280. }
  281. t = time.Unix(unix, 0)
  282. *jt = JSONUnix(t)
  283. return nil
  284. }
  285. // RFC3339NoZ is the time format used in Heat (Orchestration).
  286. const RFC3339NoZ = "2006-01-02T15:04:05"
  287. type JSONRFC3339NoZ time.Time
  288. func (jt *JSONRFC3339NoZ) UnmarshalJSON(data []byte) error {
  289. var s string
  290. if err := json.Unmarshal(data, &s); err != nil {
  291. return err
  292. }
  293. if s == "" {
  294. return nil
  295. }
  296. t, err := time.Parse(RFC3339NoZ, s)
  297. if err != nil {
  298. return err
  299. }
  300. *jt = JSONRFC3339NoZ(t)
  301. return nil
  302. }
  303. // RFC3339ZNoT is the time format used in Zun (Containers Service).
  304. const RFC3339ZNoT = "2006-01-02 15:04:05-07:00"
  305. type JSONRFC3339ZNoT time.Time
  306. func (jt *JSONRFC3339ZNoT) UnmarshalJSON(data []byte) error {
  307. var s string
  308. if err := json.Unmarshal(data, &s); err != nil {
  309. return err
  310. }
  311. if s == "" {
  312. return nil
  313. }
  314. t, err := time.Parse(RFC3339ZNoT, s)
  315. if err != nil {
  316. return err
  317. }
  318. *jt = JSONRFC3339ZNoT(t)
  319. return nil
  320. }
  321. /*
  322. Link is an internal type to be used in packages of collection resources that are
  323. paginated in a certain way.
  324. It's a response substructure common to many paginated collection results that is
  325. used to point to related pages. Usually, the one we care about is the one with
  326. Rel field set to "next".
  327. */
  328. type Link struct {
  329. Href string `json:"href"`
  330. Rel string `json:"rel"`
  331. }
  332. /*
  333. ExtractNextURL is an internal function useful for packages of collection
  334. resources that are paginated in a certain way.
  335. It attempts to extract the "next" URL from slice of Link structs, or
  336. "" if no such URL is present.
  337. */
  338. func ExtractNextURL(links []Link) (string, error) {
  339. var url string
  340. for _, l := range links {
  341. if l.Rel == "next" {
  342. url = l.Href
  343. }
  344. }
  345. if url == "" {
  346. return "", nil
  347. }
  348. return url, nil
  349. }