blob: cc961d09b268bcbb6a7b20bbd9de92493ed9a1ea [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 {
66 if r.Err != nil {
67 return r.Err
68 }
69
jrperritt41a70782016-11-09 14:40:46 -060070 if label == "" {
71 return r.ExtractInto(&to)
jrperritt613bea22016-11-08 16:58:10 -060072 }
73
jrperritt41a70782016-11-09 14:40:46 -060074 var m map[string]interface{}
75 err := r.ExtractInto(&m)
76 if err != nil {
77 return err
jrperritt613bea22016-11-08 16:58:10 -060078 }
jrperritt41a70782016-11-09 14:40:46 -060079
80 b, err := json.Marshal(m[label])
jrperritt613bea22016-11-08 16:58:10 -060081 if err != nil {
82 return err
83 }
84
85 err = json.Unmarshal(b, &to)
86 return err
87}
88
jrperritt41a70782016-11-09 14:40:46 -060089// ExtractIntoStructPtr will unmarshal the Result (r) into the provided
90// interface{} (to).
91//
92// `to` must be a pointer to an underlying struct type
93//
94// If provided, `label` will be filtered out of the response
95// body prior to `r` being unmarshalled into `to`.
jrperritt613bea22016-11-08 16:58:10 -060096func (r Result) ExtractIntoStructPtr(to interface{}, label string) error {
jrperritt41a70782016-11-09 14:40:46 -060097 t := reflect.TypeOf(to)
98 if k := t.Kind(); k != reflect.Ptr {
99 return fmt.Errorf("Expected pointer, got %v", k)
100 }
101 switch t.Elem().Kind() {
102 case reflect.Struct:
103 return r.extractIntoPtr(to, label)
104 default:
105 return fmt.Errorf("Expected pointer to struct, got: %v", t)
106 }
jrperritt613bea22016-11-08 16:58:10 -0600107}
108
jrperritt41a70782016-11-09 14:40:46 -0600109// ExtractIntoSlicePtr will unmarshal the Result (r) into the provided
110// interface{} (to).
111//
112// `to` must be a pointer to an underlying slice type
113//
114// If provided, `label` will be filtered out of the response
115// body prior to `r` being unmarshalled into `to`.
jrperritt613bea22016-11-08 16:58:10 -0600116func (r Result) ExtractIntoSlicePtr(to interface{}, label string) error {
jrperritt41a70782016-11-09 14:40:46 -0600117 t := reflect.TypeOf(to)
118 if k := t.Kind(); k != reflect.Ptr {
119 return fmt.Errorf("Expected pointer, got %v", k)
120 }
121 switch t.Elem().Kind() {
122 case reflect.Slice:
123 return r.extractIntoPtr(to, label)
124 default:
125 return fmt.Errorf("Expected pointer to slice, got: %v", t)
126 }
jrperritt613bea22016-11-08 16:58:10 -0600127}
128
Ash Wilson3ce1bd82014-10-31 12:20:00 -0400129// PrettyPrintJSON creates a string containing the full response body as
130// pretty-printed JSON. It's useful for capturing test fixtures and for
Ash Wilson0fe6c962014-10-31 15:34:24 -0400131// debugging extraction bugs. If you include its output in an issue related to
132// a buggy extraction function, we will all love you forever.
Ash Wilsone8192ac2014-10-21 09:02:01 -0400133func (r Result) PrettyPrintJSON() string {
134 pretty, err := json.MarshalIndent(r.Body, "", " ")
135 if err != nil {
136 panic(err.Error())
137 }
138 return string(pretty)
139}
140
Ash Wilson64ba49f2014-10-31 15:31:46 -0400141// ErrResult is an internal type to be used by individual resource packages, but
142// its methods will be available on a wide variety of user-facing embedding
143// types.
144//
145// It represents results that only contain a potential error and
Ash Wilson3ce1bd82014-10-31 12:20:00 -0400146// nothing else. Usually, if the operation executed successfully, the Err field
147// will be nil; otherwise it will be stocked with a relevant error. Use the
Ash Wilson64ba49f2014-10-31 15:31:46 -0400148// ExtractErr method
149// to cleanly pull it out.
Jon Perrittba2395e2014-10-27 15:23:21 -0500150type ErrResult struct {
Jon Perritt0c2b0372014-10-27 15:57:29 -0500151 Result
Jamie Hannaford021b35c2014-10-27 14:01:53 +0100152}
153
Ash Wilson3ce1bd82014-10-31 12:20:00 -0400154// ExtractErr is a function that extracts error information, or nil, from a result.
Jon Perrittba2395e2014-10-27 15:23:21 -0500155func (r ErrResult) ExtractErr() error {
Jamie Hannaford021b35c2014-10-27 14:01:53 +0100156 return r.Err
157}
158
Ash Wilson3ce1bd82014-10-31 12:20:00 -0400159/*
Ash Wilson64ba49f2014-10-31 15:31:46 -0400160HeaderResult is an internal type to be used by individual resource packages, but
161its methods will be available on a wide variety of user-facing embedding types.
Ash Wilson3ce1bd82014-10-31 12:20:00 -0400162
163It represents a result that only contains an error (possibly nil) and an
164http.Header. This is used, for example, by the objectstorage packages in
165openstack, because most of the operations don't return response bodies, but do
166have relevant information in headers.
167*/
Jon Perrittd50f93e2014-10-27 14:19:27 -0500168type HeaderResult struct {
Jon Perritt0c2b0372014-10-27 15:57:29 -0500169 Result
Jon Perrittd50f93e2014-10-27 14:19:27 -0500170}
171
172// ExtractHeader will return the http.Header and error from the HeaderResult.
Ash Wilson3ce1bd82014-10-31 12:20:00 -0400173//
174// header, err := objects.Create(client, "my_container", objects.CreateOpts{}).ExtractHeader()
Jon Perritt66822822016-02-25 03:06:56 -0600175func (r HeaderResult) ExtractInto(to interface{}) error {
176 if r.Err != nil {
177 return r.Err
Jon Perritt63e7a482014-12-04 09:47:23 -0700178 }
Jon Perritt66822822016-02-25 03:06:56 -0600179
180 tmpHeaderMap := map[string]string{}
181 for k, v := range r.Header {
Jon Perritt31b66462016-02-25 22:25:30 -0600182 if len(v) > 0 {
183 tmpHeaderMap[k] = v[0]
184 }
Jon Perritt66822822016-02-25 03:06:56 -0600185 }
186
187 b, err := json.Marshal(tmpHeaderMap)
Jon Perritt63e7a482014-12-04 09:47:23 -0700188 if err != nil {
189 return err
190 }
Jon Perritt66822822016-02-25 03:06:56 -0600191 err = json.Unmarshal(b, to)
192
193 return err
Jon Perritt63e7a482014-12-04 09:47:23 -0700194}
195
Ash Wilson3ce1bd82014-10-31 12:20:00 -0400196// RFC3339Milli describes a common time format used by some API responses.
Ash Wilsona6b08312014-10-02 15:27:45 -0400197const RFC3339Milli = "2006-01-02T15:04:05.999999Z"
Jamie Hannaford369c9c62014-10-08 15:14:43 +0200198
Jon Perritt12395212016-02-24 10:41:17 -0600199type JSONRFC3339Milli time.Time
200
201func (jt *JSONRFC3339Milli) UnmarshalJSON(data []byte) error {
202 b := bytes.NewBuffer(data)
203 dec := json.NewDecoder(b)
204 var s string
205 if err := dec.Decode(&s); err != nil {
206 return err
207 }
208 t, err := time.Parse(RFC3339Milli, s)
209 if err != nil {
210 return err
211 }
212 *jt = JSONRFC3339Milli(t)
213 return nil
214}
215
jrperritt9b7b9e62016-07-11 22:30:50 -0500216const RFC3339MilliNoZ = "2006-01-02T15:04:05.999999"
Jon Perritt66822822016-02-25 03:06:56 -0600217
Jon Perritt12395212016-02-24 10:41:17 -0600218type JSONRFC3339MilliNoZ time.Time
219
220func (jt *JSONRFC3339MilliNoZ) UnmarshalJSON(data []byte) error {
Jon Perritt12395212016-02-24 10:41:17 -0600221 var s string
Jon Perritt82583e72016-02-25 06:41:51 -0600222 if err := json.Unmarshal(data, &s); err != nil {
Jon Perritt12395212016-02-24 10:41:17 -0600223 return err
224 }
Jon Perritt82583e72016-02-25 06:41:51 -0600225 if s == "" {
226 return nil
227 }
Jon Perritt12395212016-02-24 10:41:17 -0600228 t, err := time.Parse(RFC3339MilliNoZ, s)
229 if err != nil {
230 return err
231 }
232 *jt = JSONRFC3339MilliNoZ(t)
233 return nil
234}
235
Jon Perritt66822822016-02-25 03:06:56 -0600236type JSONRFC1123 time.Time
237
238func (jt *JSONRFC1123) UnmarshalJSON(data []byte) error {
Jon Perritt66822822016-02-25 03:06:56 -0600239 var s string
Jon Perritt82583e72016-02-25 06:41:51 -0600240 if err := json.Unmarshal(data, &s); err != nil {
Jon Perritt66822822016-02-25 03:06:56 -0600241 return err
242 }
Jon Perritt82583e72016-02-25 06:41:51 -0600243 if s == "" {
244 return nil
245 }
Jon Perritt66822822016-02-25 03:06:56 -0600246 t, err := time.Parse(time.RFC1123, s)
247 if err != nil {
248 return err
249 }
250 *jt = JSONRFC1123(t)
251 return nil
252}
253
254type JSONUnix time.Time
255
256func (jt *JSONUnix) UnmarshalJSON(data []byte) error {
Jon Perritt66822822016-02-25 03:06:56 -0600257 var s string
Jon Perritt82583e72016-02-25 06:41:51 -0600258 if err := json.Unmarshal(data, &s); err != nil {
Jon Perritt66822822016-02-25 03:06:56 -0600259 return err
260 }
Jon Perritt82583e72016-02-25 06:41:51 -0600261 if s == "" {
262 return nil
263 }
Jon Perritt66822822016-02-25 03:06:56 -0600264 unix, err := strconv.ParseInt(s, 10, 64)
265 if err != nil {
266 return err
267 }
268 t = time.Unix(unix, 0)
269 *jt = JSONUnix(t)
270 return nil
271}
272
Jon Perritt31b66462016-02-25 22:25:30 -0600273// RFC3339NoZ is the time format used in Heat (Orchestration).
Jon Perritt66822822016-02-25 03:06:56 -0600274const RFC3339NoZ = "2006-01-02T15:04:05"
275
276type JSONRFC3339NoZ time.Time
277
278func (jt *JSONRFC3339NoZ) UnmarshalJSON(data []byte) error {
Jon Perritt66822822016-02-25 03:06:56 -0600279 var s string
Jon Perritt82583e72016-02-25 06:41:51 -0600280 if err := json.Unmarshal(data, &s); err != nil {
Jon Perritt66822822016-02-25 03:06:56 -0600281 return err
282 }
283 if s == "" {
284 return nil
285 }
286 t, err := time.Parse(RFC3339NoZ, s)
287 if err != nil {
288 return err
289 }
290 *jt = JSONRFC3339NoZ(t)
291 return nil
292}
Pratik Mallyae1b6cbb2015-09-09 14:24:14 -0500293
Ash Wilson3ce1bd82014-10-31 12:20:00 -0400294/*
295Link is an internal type to be used in packages of collection resources that are
296paginated in a certain way.
297
298It's a response substructure common to many paginated collection results that is
299used to point to related pages. Usually, the one we care about is the one with
300Rel field set to "next".
301*/
Jamie Hannaford369c9c62014-10-08 15:14:43 +0200302type Link struct {
Jon Perritt12395212016-02-24 10:41:17 -0600303 Href string `json:"href"`
304 Rel string `json:"rel"`
Jamie Hannaford369c9c62014-10-08 15:14:43 +0200305}
306
Ash Wilson3ce1bd82014-10-31 12:20:00 -0400307/*
308ExtractNextURL is an internal function useful for packages of collection
309resources that are paginated in a certain way.
310
jrperrittb1013232016-02-10 19:01:53 -0600311It attempts to extract the "next" URL from slice of Link structs, or
Ash Wilson3ce1bd82014-10-31 12:20:00 -0400312"" if no such URL is present.
313*/
Jamie Hannaford369c9c62014-10-08 15:14:43 +0200314func ExtractNextURL(links []Link) (string, error) {
315 var url string
316
317 for _, l := range links {
318 if l.Rel == "next" {
319 url = l.Href
320 }
321 }
322
323 if url == "" {
324 return "", nil
325 }
326
327 return url, nil
328}