blob: 76c16ef8ff638df8f1d445fed1b4b66788068a72 [file] [log] [blame]
Jamie Hannaford4baa1232014-09-23 15:23:04 +02001package gophercloud
Jamie Hannafordb3120f52014-09-23 15:17:57 +02002
Ash Wilsone8192ac2014-10-21 09:02:01 -04003import (
Jon Perritt12395212016-02-24 10:41:17 -06004 "bytes"
Ash Wilsone8192ac2014-10-21 09:02:01 -04005 "encoding/json"
jrperritt613bea22016-11-08 16:58:10 -06006 "fmt"
Jon Perritt12395212016-02-24 10:41:17 -06007 "io"
Ash Wilsone8192ac2014-10-21 09:02:01 -04008 "net/http"
jrperritt613bea22016-11-08 16:58:10 -06009 "reflect"
Jon Perritt66822822016-02-25 03:06:56 -060010 "strconv"
Jon Perritt12395212016-02-24 10:41:17 -060011 "time"
Ash Wilsone8192ac2014-10-21 09:02:01 -040012)
Ash Wilsoneab6a702014-10-20 08:18:30 -040013
Ash Wilson3ce1bd82014-10-31 12:20:00 -040014/*
Ash Wilson64ba49f2014-10-31 15:31:46 -040015Result is an internal type to be used by individual resource packages, but its
16methods will be available on a wide variety of user-facing embedding types.
Ash Wilson3ce1bd82014-10-31 12:20:00 -040017
18It acts as a base struct that other Result types, returned from request
19functions, can embed for convenience. All Results capture basic information
20from the HTTP transaction that was performed, including the response body,
21HTTP headers, and any errors that happened.
22
23Generally, each Result type will have an Extract method that can be used to
24further interpret the result's payload in a specific context. Extensions or
25providers can then provide additional extraction functions to pull out
26provider- or extension-specific information as well.
27*/
Ash Wilsoneab6a702014-10-20 08:18:30 -040028type Result struct {
Ash Wilson3ce1bd82014-10-31 12:20:00 -040029 // Body is the payload of the HTTP response from the server. In most cases,
30 // this will be the deserialized JSON structure.
Ash Wilsond3dc2542014-10-20 10:10:48 -040031 Body interface{}
Ash Wilsoneab6a702014-10-20 08:18:30 -040032
Ash Wilson72e4d2c2014-10-20 10:27:30 -040033 // Header contains the HTTP header structure from the original response.
34 Header http.Header
Ash Wilsoneab6a702014-10-20 08:18:30 -040035
Ash Wilson3ce1bd82014-10-31 12:20:00 -040036 // Err is an error that occurred during the operation. It's deferred until
37 // extraction to make it easier to chain the Extract call.
Ash Wilsoneab6a702014-10-20 08:18:30 -040038 Err error
Jamie Hannafordb3120f52014-09-23 15:17:57 +020039}
Ash Wilsona6b08312014-10-02 15:27:45 -040040
Jon Perritt12395212016-02-24 10:41:17 -060041// ExtractInto allows users to provide an object into which `Extract` will extract
42// the `Result.Body`. This would be useful for OpenStack providers that have
43// different fields in the response object than OpenStack proper.
44func (r Result) ExtractInto(to interface{}) error {
45 if r.Err != nil {
46 return r.Err
47 }
48
49 if reader, ok := r.Body.(io.Reader); ok {
50 if readCloser, ok := reader.(io.Closer); ok {
51 defer readCloser.Close()
52 }
Jon Perritta33da232016-03-02 04:43:08 -060053 return json.NewDecoder(reader).Decode(to)
Jon Perritt12395212016-02-24 10:41:17 -060054 }
55
56 b, err := json.Marshal(r.Body)
57 if err != nil {
58 return err
59 }
60 err = json.Unmarshal(b, to)
61
62 return err
63}
64
jrperritt613bea22016-11-08 16:58:10 -060065func (r Result) extractIntoPtr(to interface{}, label string) error {
jrperritt41a70782016-11-09 14:40:46 -060066 if label == "" {
67 return r.ExtractInto(&to)
jrperritt613bea22016-11-08 16:58:10 -060068 }
69
jrperritt41a70782016-11-09 14:40:46 -060070 var m map[string]interface{}
71 err := r.ExtractInto(&m)
72 if err != nil {
73 return err
jrperritt613bea22016-11-08 16:58:10 -060074 }
jrperritt41a70782016-11-09 14:40:46 -060075
76 b, err := json.Marshal(m[label])
jrperritt613bea22016-11-08 16:58:10 -060077 if err != nil {
78 return err
79 }
80
81 err = json.Unmarshal(b, &to)
82 return err
83}
84
jrperritt41a70782016-11-09 14:40:46 -060085// ExtractIntoStructPtr will unmarshal the Result (r) into the provided
86// interface{} (to).
87//
jrperrittc501bc12016-11-09 15:52:55 -060088// NOTE: For internal use only
89//
jrperritt41a70782016-11-09 14:40:46 -060090// `to` must be a pointer to an underlying struct type
91//
92// If provided, `label` will be filtered out of the response
93// body prior to `r` being unmarshalled into `to`.
jrperritt613bea22016-11-08 16:58:10 -060094func (r Result) ExtractIntoStructPtr(to interface{}, label string) error {
jrperritt25492a92016-11-09 17:42:24 -060095 if r.Err != nil {
96 return r.Err
97 }
98
jrperritt41a70782016-11-09 14:40:46 -060099 t := reflect.TypeOf(to)
100 if k := t.Kind(); k != reflect.Ptr {
101 return fmt.Errorf("Expected pointer, got %v", k)
102 }
103 switch t.Elem().Kind() {
104 case reflect.Struct:
105 return r.extractIntoPtr(to, label)
106 default:
107 return fmt.Errorf("Expected pointer to struct, got: %v", t)
108 }
jrperritt613bea22016-11-08 16:58:10 -0600109}
110
jrperritt41a70782016-11-09 14:40:46 -0600111// ExtractIntoSlicePtr will unmarshal the Result (r) into the provided
112// interface{} (to).
113//
jrperrittc501bc12016-11-09 15:52:55 -0600114// NOTE: For internal use only
115//
jrperritt41a70782016-11-09 14:40:46 -0600116// `to` must be a pointer to an underlying slice type
117//
118// If provided, `label` will be filtered out of the response
119// body prior to `r` being unmarshalled into `to`.
jrperritt613bea22016-11-08 16:58:10 -0600120func (r Result) ExtractIntoSlicePtr(to interface{}, label string) error {
jrperritt25492a92016-11-09 17:42:24 -0600121 if r.Err != nil {
122 return r.Err
123 }
124
jrperritt41a70782016-11-09 14:40:46 -0600125 t := reflect.TypeOf(to)
126 if k := t.Kind(); k != reflect.Ptr {
127 return fmt.Errorf("Expected pointer, got %v", k)
128 }
129 switch t.Elem().Kind() {
130 case reflect.Slice:
131 return r.extractIntoPtr(to, label)
132 default:
133 return fmt.Errorf("Expected pointer to slice, got: %v", t)
134 }
jrperritt613bea22016-11-08 16:58:10 -0600135}
136
Ash Wilson3ce1bd82014-10-31 12:20:00 -0400137// PrettyPrintJSON creates a string containing the full response body as
138// pretty-printed JSON. It's useful for capturing test fixtures and for
Ash Wilson0fe6c962014-10-31 15:34:24 -0400139// debugging extraction bugs. If you include its output in an issue related to
140// a buggy extraction function, we will all love you forever.
Ash Wilsone8192ac2014-10-21 09:02:01 -0400141func (r Result) PrettyPrintJSON() string {
142 pretty, err := json.MarshalIndent(r.Body, "", " ")
143 if err != nil {
144 panic(err.Error())
145 }
146 return string(pretty)
147}
148
Ash Wilson64ba49f2014-10-31 15:31:46 -0400149// ErrResult is an internal type to be used by individual resource packages, but
150// its methods will be available on a wide variety of user-facing embedding
151// types.
152//
153// It represents results that only contain a potential error and
Ash Wilson3ce1bd82014-10-31 12:20:00 -0400154// nothing else. Usually, if the operation executed successfully, the Err field
155// will be nil; otherwise it will be stocked with a relevant error. Use the
Ash Wilson64ba49f2014-10-31 15:31:46 -0400156// ExtractErr method
157// to cleanly pull it out.
Jon Perrittba2395e2014-10-27 15:23:21 -0500158type ErrResult struct {
Jon Perritt0c2b0372014-10-27 15:57:29 -0500159 Result
Jamie Hannaford021b35c2014-10-27 14:01:53 +0100160}
161
Ash Wilson3ce1bd82014-10-31 12:20:00 -0400162// ExtractErr is a function that extracts error information, or nil, from a result.
Jon Perrittba2395e2014-10-27 15:23:21 -0500163func (r ErrResult) ExtractErr() error {
Jamie Hannaford021b35c2014-10-27 14:01:53 +0100164 return r.Err
165}
166
Ash Wilson3ce1bd82014-10-31 12:20:00 -0400167/*
Ash Wilson64ba49f2014-10-31 15:31:46 -0400168HeaderResult is an internal type to be used by individual resource packages, but
169its methods will be available on a wide variety of user-facing embedding types.
Ash Wilson3ce1bd82014-10-31 12:20:00 -0400170
171It represents a result that only contains an error (possibly nil) and an
172http.Header. This is used, for example, by the objectstorage packages in
173openstack, because most of the operations don't return response bodies, but do
174have relevant information in headers.
175*/
Jon Perrittd50f93e2014-10-27 14:19:27 -0500176type HeaderResult struct {
Jon Perritt0c2b0372014-10-27 15:57:29 -0500177 Result
Jon Perrittd50f93e2014-10-27 14:19:27 -0500178}
179
180// ExtractHeader will return the http.Header and error from the HeaderResult.
Ash Wilson3ce1bd82014-10-31 12:20:00 -0400181//
182// header, err := objects.Create(client, "my_container", objects.CreateOpts{}).ExtractHeader()
Jon Perritt66822822016-02-25 03:06:56 -0600183func (r HeaderResult) ExtractInto(to interface{}) error {
184 if r.Err != nil {
185 return r.Err
Jon Perritt63e7a482014-12-04 09:47:23 -0700186 }
Jon Perritt66822822016-02-25 03:06:56 -0600187
188 tmpHeaderMap := map[string]string{}
189 for k, v := range r.Header {
Jon Perritt31b66462016-02-25 22:25:30 -0600190 if len(v) > 0 {
191 tmpHeaderMap[k] = v[0]
192 }
Jon Perritt66822822016-02-25 03:06:56 -0600193 }
194
195 b, err := json.Marshal(tmpHeaderMap)
Jon Perritt63e7a482014-12-04 09:47:23 -0700196 if err != nil {
197 return err
198 }
Jon Perritt66822822016-02-25 03:06:56 -0600199 err = json.Unmarshal(b, to)
200
201 return err
Jon Perritt63e7a482014-12-04 09:47:23 -0700202}
203
Ash Wilson3ce1bd82014-10-31 12:20:00 -0400204// RFC3339Milli describes a common time format used by some API responses.
Ash Wilsona6b08312014-10-02 15:27:45 -0400205const RFC3339Milli = "2006-01-02T15:04:05.999999Z"
Jamie Hannaford369c9c62014-10-08 15:14:43 +0200206
Jon Perritt12395212016-02-24 10:41:17 -0600207type JSONRFC3339Milli time.Time
208
209func (jt *JSONRFC3339Milli) UnmarshalJSON(data []byte) error {
210 b := bytes.NewBuffer(data)
211 dec := json.NewDecoder(b)
212 var s string
213 if err := dec.Decode(&s); err != nil {
214 return err
215 }
216 t, err := time.Parse(RFC3339Milli, s)
217 if err != nil {
218 return err
219 }
220 *jt = JSONRFC3339Milli(t)
221 return nil
222}
223
jrperritt9b7b9e62016-07-11 22:30:50 -0500224const RFC3339MilliNoZ = "2006-01-02T15:04:05.999999"
Jon Perritt66822822016-02-25 03:06:56 -0600225
Jon Perritt12395212016-02-24 10:41:17 -0600226type JSONRFC3339MilliNoZ time.Time
227
228func (jt *JSONRFC3339MilliNoZ) UnmarshalJSON(data []byte) error {
Jon Perritt12395212016-02-24 10:41:17 -0600229 var s string
Jon Perritt82583e72016-02-25 06:41:51 -0600230 if err := json.Unmarshal(data, &s); err != nil {
Jon Perritt12395212016-02-24 10:41:17 -0600231 return err
232 }
Jon Perritt82583e72016-02-25 06:41:51 -0600233 if s == "" {
234 return nil
235 }
Jon Perritt12395212016-02-24 10:41:17 -0600236 t, err := time.Parse(RFC3339MilliNoZ, s)
237 if err != nil {
238 return err
239 }
240 *jt = JSONRFC3339MilliNoZ(t)
241 return nil
242}
243
Jon Perritt66822822016-02-25 03:06:56 -0600244type JSONRFC1123 time.Time
245
246func (jt *JSONRFC1123) UnmarshalJSON(data []byte) error {
Jon Perritt66822822016-02-25 03:06:56 -0600247 var s string
Jon Perritt82583e72016-02-25 06:41:51 -0600248 if err := json.Unmarshal(data, &s); err != nil {
Jon Perritt66822822016-02-25 03:06:56 -0600249 return err
250 }
Jon Perritt82583e72016-02-25 06:41:51 -0600251 if s == "" {
252 return nil
253 }
Jon Perritt66822822016-02-25 03:06:56 -0600254 t, err := time.Parse(time.RFC1123, s)
255 if err != nil {
256 return err
257 }
258 *jt = JSONRFC1123(t)
259 return nil
260}
261
262type JSONUnix time.Time
263
264func (jt *JSONUnix) UnmarshalJSON(data []byte) error {
Jon Perritt66822822016-02-25 03:06:56 -0600265 var s string
Jon Perritt82583e72016-02-25 06:41:51 -0600266 if err := json.Unmarshal(data, &s); err != nil {
Jon Perritt66822822016-02-25 03:06:56 -0600267 return err
268 }
Jon Perritt82583e72016-02-25 06:41:51 -0600269 if s == "" {
270 return nil
271 }
Jon Perritt66822822016-02-25 03:06:56 -0600272 unix, err := strconv.ParseInt(s, 10, 64)
273 if err != nil {
274 return err
275 }
276 t = time.Unix(unix, 0)
277 *jt = JSONUnix(t)
278 return nil
279}
280
Jon Perritt31b66462016-02-25 22:25:30 -0600281// RFC3339NoZ is the time format used in Heat (Orchestration).
Jon Perritt66822822016-02-25 03:06:56 -0600282const RFC3339NoZ = "2006-01-02T15:04:05"
283
284type JSONRFC3339NoZ time.Time
285
286func (jt *JSONRFC3339NoZ) UnmarshalJSON(data []byte) error {
Jon Perritt66822822016-02-25 03:06:56 -0600287 var s string
Jon Perritt82583e72016-02-25 06:41:51 -0600288 if err := json.Unmarshal(data, &s); err != nil {
Jon Perritt66822822016-02-25 03:06:56 -0600289 return err
290 }
291 if s == "" {
292 return nil
293 }
294 t, err := time.Parse(RFC3339NoZ, s)
295 if err != nil {
296 return err
297 }
298 *jt = JSONRFC3339NoZ(t)
299 return nil
300}
Pratik Mallyae1b6cbb2015-09-09 14:24:14 -0500301
Ash Wilson3ce1bd82014-10-31 12:20:00 -0400302/*
303Link is an internal type to be used in packages of collection resources that are
304paginated in a certain way.
305
306It's a response substructure common to many paginated collection results that is
307used to point to related pages. Usually, the one we care about is the one with
308Rel field set to "next".
309*/
Jamie Hannaford369c9c62014-10-08 15:14:43 +0200310type Link struct {
Jon Perritt12395212016-02-24 10:41:17 -0600311 Href string `json:"href"`
312 Rel string `json:"rel"`
Jamie Hannaford369c9c62014-10-08 15:14:43 +0200313}
314
Ash Wilson3ce1bd82014-10-31 12:20:00 -0400315/*
316ExtractNextURL is an internal function useful for packages of collection
317resources that are paginated in a certain way.
318
jrperrittb1013232016-02-10 19:01:53 -0600319It attempts to extract the "next" URL from slice of Link structs, or
Ash Wilson3ce1bd82014-10-31 12:20:00 -0400320"" if no such URL is present.
321*/
Jamie Hannaford369c9c62014-10-08 15:14:43 +0200322func ExtractNextURL(links []Link) (string, error) {
323 var url string
324
325 for _, l := range links {
326 if l.Rel == "next" {
327 url = l.Href
328 }
329 }
330
331 if url == "" {
332 return "", nil
333 }
334
335 return url, nil
336}