blob: 8d262ec56830a2f8065e7242a4c9dcc45d3c54e1 [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
70 t := reflect.TypeOf(to)
71 if k := t.Kind(); k != reflect.Ptr {
72 return fmt.Errorf("Expected pointer, got %v", k)
73 }
74 t = t.Elem()
75 switch t.Kind() {
76 case reflect.Struct, reflect.Slice:
77 default:
78 return fmt.Errorf("Invalid type: %v", t)
79 }
80
81 var (
82 b []byte
83 err error
84 )
85
86 switch label {
87 case "":
88 var m interface{}
89 err = r.ExtractInto(&m)
90 if err != nil {
91 return err
92 }
93 b, err = json.Marshal(m)
94 default:
95 var m map[string]interface{}
96 err = r.ExtractInto(&m)
97 if err != nil {
98 return err
99 }
100 b, err = json.Marshal(m[label])
101 }
102 if err != nil {
103 return err
104 }
105
106 err = json.Unmarshal(b, &to)
107 return err
108}
109
110func (r Result) ExtractIntoStructPtr(to interface{}, label string) error {
111 return r.extractIntoPtr(to, label)
112}
113
114func (r Result) ExtractIntoSlicePtr(to interface{}, label string) error {
115 return r.extractIntoPtr(to, label)
116}
117
Ash Wilson3ce1bd82014-10-31 12:20:00 -0400118// PrettyPrintJSON creates a string containing the full response body as
119// pretty-printed JSON. It's useful for capturing test fixtures and for
Ash Wilson0fe6c962014-10-31 15:34:24 -0400120// debugging extraction bugs. If you include its output in an issue related to
121// a buggy extraction function, we will all love you forever.
Ash Wilsone8192ac2014-10-21 09:02:01 -0400122func (r Result) PrettyPrintJSON() string {
123 pretty, err := json.MarshalIndent(r.Body, "", " ")
124 if err != nil {
125 panic(err.Error())
126 }
127 return string(pretty)
128}
129
Ash Wilson64ba49f2014-10-31 15:31:46 -0400130// ErrResult is an internal type to be used by individual resource packages, but
131// its methods will be available on a wide variety of user-facing embedding
132// types.
133//
134// It represents results that only contain a potential error and
Ash Wilson3ce1bd82014-10-31 12:20:00 -0400135// nothing else. Usually, if the operation executed successfully, the Err field
136// will be nil; otherwise it will be stocked with a relevant error. Use the
Ash Wilson64ba49f2014-10-31 15:31:46 -0400137// ExtractErr method
138// to cleanly pull it out.
Jon Perrittba2395e2014-10-27 15:23:21 -0500139type ErrResult struct {
Jon Perritt0c2b0372014-10-27 15:57:29 -0500140 Result
Jamie Hannaford021b35c2014-10-27 14:01:53 +0100141}
142
Ash Wilson3ce1bd82014-10-31 12:20:00 -0400143// ExtractErr is a function that extracts error information, or nil, from a result.
Jon Perrittba2395e2014-10-27 15:23:21 -0500144func (r ErrResult) ExtractErr() error {
Jamie Hannaford021b35c2014-10-27 14:01:53 +0100145 return r.Err
146}
147
Ash Wilson3ce1bd82014-10-31 12:20:00 -0400148/*
Ash Wilson64ba49f2014-10-31 15:31:46 -0400149HeaderResult is an internal type to be used by individual resource packages, but
150its methods will be available on a wide variety of user-facing embedding types.
Ash Wilson3ce1bd82014-10-31 12:20:00 -0400151
152It represents a result that only contains an error (possibly nil) and an
153http.Header. This is used, for example, by the objectstorage packages in
154openstack, because most of the operations don't return response bodies, but do
155have relevant information in headers.
156*/
Jon Perrittd50f93e2014-10-27 14:19:27 -0500157type HeaderResult struct {
Jon Perritt0c2b0372014-10-27 15:57:29 -0500158 Result
Jon Perrittd50f93e2014-10-27 14:19:27 -0500159}
160
161// ExtractHeader will return the http.Header and error from the HeaderResult.
Ash Wilson3ce1bd82014-10-31 12:20:00 -0400162//
163// header, err := objects.Create(client, "my_container", objects.CreateOpts{}).ExtractHeader()
Jon Perritt66822822016-02-25 03:06:56 -0600164func (r HeaderResult) ExtractInto(to interface{}) error {
165 if r.Err != nil {
166 return r.Err
Jon Perritt63e7a482014-12-04 09:47:23 -0700167 }
Jon Perritt66822822016-02-25 03:06:56 -0600168
169 tmpHeaderMap := map[string]string{}
170 for k, v := range r.Header {
Jon Perritt31b66462016-02-25 22:25:30 -0600171 if len(v) > 0 {
172 tmpHeaderMap[k] = v[0]
173 }
Jon Perritt66822822016-02-25 03:06:56 -0600174 }
175
176 b, err := json.Marshal(tmpHeaderMap)
Jon Perritt63e7a482014-12-04 09:47:23 -0700177 if err != nil {
178 return err
179 }
Jon Perritt66822822016-02-25 03:06:56 -0600180 err = json.Unmarshal(b, to)
181
182 return err
Jon Perritt63e7a482014-12-04 09:47:23 -0700183}
184
Ash Wilson3ce1bd82014-10-31 12:20:00 -0400185// RFC3339Milli describes a common time format used by some API responses.
Ash Wilsona6b08312014-10-02 15:27:45 -0400186const RFC3339Milli = "2006-01-02T15:04:05.999999Z"
Jamie Hannaford369c9c62014-10-08 15:14:43 +0200187
Jon Perritt12395212016-02-24 10:41:17 -0600188type JSONRFC3339Milli time.Time
189
190func (jt *JSONRFC3339Milli) UnmarshalJSON(data []byte) error {
191 b := bytes.NewBuffer(data)
192 dec := json.NewDecoder(b)
193 var s string
194 if err := dec.Decode(&s); err != nil {
195 return err
196 }
197 t, err := time.Parse(RFC3339Milli, s)
198 if err != nil {
199 return err
200 }
201 *jt = JSONRFC3339Milli(t)
202 return nil
203}
204
jrperritt9b7b9e62016-07-11 22:30:50 -0500205const RFC3339MilliNoZ = "2006-01-02T15:04:05.999999"
Jon Perritt66822822016-02-25 03:06:56 -0600206
Jon Perritt12395212016-02-24 10:41:17 -0600207type JSONRFC3339MilliNoZ time.Time
208
209func (jt *JSONRFC3339MilliNoZ) UnmarshalJSON(data []byte) error {
Jon Perritt12395212016-02-24 10:41:17 -0600210 var s string
Jon Perritt82583e72016-02-25 06:41:51 -0600211 if err := json.Unmarshal(data, &s); err != nil {
Jon Perritt12395212016-02-24 10:41:17 -0600212 return err
213 }
Jon Perritt82583e72016-02-25 06:41:51 -0600214 if s == "" {
215 return nil
216 }
Jon Perritt12395212016-02-24 10:41:17 -0600217 t, err := time.Parse(RFC3339MilliNoZ, s)
218 if err != nil {
219 return err
220 }
221 *jt = JSONRFC3339MilliNoZ(t)
222 return nil
223}
224
Jon Perritt66822822016-02-25 03:06:56 -0600225type JSONRFC1123 time.Time
226
227func (jt *JSONRFC1123) UnmarshalJSON(data []byte) error {
Jon Perritt66822822016-02-25 03:06:56 -0600228 var s string
Jon Perritt82583e72016-02-25 06:41:51 -0600229 if err := json.Unmarshal(data, &s); err != nil {
Jon Perritt66822822016-02-25 03:06:56 -0600230 return err
231 }
Jon Perritt82583e72016-02-25 06:41:51 -0600232 if s == "" {
233 return nil
234 }
Jon Perritt66822822016-02-25 03:06:56 -0600235 t, err := time.Parse(time.RFC1123, s)
236 if err != nil {
237 return err
238 }
239 *jt = JSONRFC1123(t)
240 return nil
241}
242
243type JSONUnix time.Time
244
245func (jt *JSONUnix) UnmarshalJSON(data []byte) error {
Jon Perritt66822822016-02-25 03:06:56 -0600246 var s string
Jon Perritt82583e72016-02-25 06:41:51 -0600247 if err := json.Unmarshal(data, &s); err != nil {
Jon Perritt66822822016-02-25 03:06:56 -0600248 return err
249 }
Jon Perritt82583e72016-02-25 06:41:51 -0600250 if s == "" {
251 return nil
252 }
Jon Perritt66822822016-02-25 03:06:56 -0600253 unix, err := strconv.ParseInt(s, 10, 64)
254 if err != nil {
255 return err
256 }
257 t = time.Unix(unix, 0)
258 *jt = JSONUnix(t)
259 return nil
260}
261
Jon Perritt31b66462016-02-25 22:25:30 -0600262// RFC3339NoZ is the time format used in Heat (Orchestration).
Jon Perritt66822822016-02-25 03:06:56 -0600263const RFC3339NoZ = "2006-01-02T15:04:05"
264
265type JSONRFC3339NoZ time.Time
266
267func (jt *JSONRFC3339NoZ) UnmarshalJSON(data []byte) error {
Jon Perritt66822822016-02-25 03:06:56 -0600268 var s string
Jon Perritt82583e72016-02-25 06:41:51 -0600269 if err := json.Unmarshal(data, &s); err != nil {
Jon Perritt66822822016-02-25 03:06:56 -0600270 return err
271 }
272 if s == "" {
273 return nil
274 }
275 t, err := time.Parse(RFC3339NoZ, s)
276 if err != nil {
277 return err
278 }
279 *jt = JSONRFC3339NoZ(t)
280 return nil
281}
Pratik Mallyae1b6cbb2015-09-09 14:24:14 -0500282
Ash Wilson3ce1bd82014-10-31 12:20:00 -0400283/*
284Link is an internal type to be used in packages of collection resources that are
285paginated in a certain way.
286
287It's a response substructure common to many paginated collection results that is
288used to point to related pages. Usually, the one we care about is the one with
289Rel field set to "next".
290*/
Jamie Hannaford369c9c62014-10-08 15:14:43 +0200291type Link struct {
Jon Perritt12395212016-02-24 10:41:17 -0600292 Href string `json:"href"`
293 Rel string `json:"rel"`
Jamie Hannaford369c9c62014-10-08 15:14:43 +0200294}
295
Ash Wilson3ce1bd82014-10-31 12:20:00 -0400296/*
297ExtractNextURL is an internal function useful for packages of collection
298resources that are paginated in a certain way.
299
jrperrittb1013232016-02-10 19:01:53 -0600300It attempts to extract the "next" URL from slice of Link structs, or
Ash Wilson3ce1bd82014-10-31 12:20:00 -0400301"" if no such URL is present.
302*/
Jamie Hannaford369c9c62014-10-08 15:14:43 +0200303func ExtractNextURL(links []Link) (string, error) {
304 var url string
305
306 for _, l := range links {
307 if l.Rel == "next" {
308 url = l.Href
309 }
310 }
311
312 if url == "" {
313 return "", nil
314 }
315
316 return url, nil
317}