blob: 1a3aaa473b92490bbd3ae06b25a174c875a5efa9 [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"
Jon Perritt12395212016-02-24 10:41:17 -06006 "io"
Ash Wilsone8192ac2014-10-21 09:02:01 -04007 "net/http"
Jon Perritt66822822016-02-25 03:06:56 -06008 "strconv"
Jon Perritt12395212016-02-24 10:41:17 -06009 "time"
Ash Wilsone8192ac2014-10-21 09:02:01 -040010)
Ash Wilsoneab6a702014-10-20 08:18:30 -040011
Ash Wilson3ce1bd82014-10-31 12:20:00 -040012/*
Ash Wilson64ba49f2014-10-31 15:31:46 -040013Result is an internal type to be used by individual resource packages, but its
14methods will be available on a wide variety of user-facing embedding types.
Ash Wilson3ce1bd82014-10-31 12:20:00 -040015
16It acts as a base struct that other Result types, returned from request
17functions, can embed for convenience. All Results capture basic information
18from the HTTP transaction that was performed, including the response body,
19HTTP headers, and any errors that happened.
20
21Generally, each Result type will have an Extract method that can be used to
22further interpret the result's payload in a specific context. Extensions or
23providers can then provide additional extraction functions to pull out
24provider- or extension-specific information as well.
25*/
Ash Wilsoneab6a702014-10-20 08:18:30 -040026type Result struct {
Ash Wilson3ce1bd82014-10-31 12:20:00 -040027 // Body is the payload of the HTTP response from the server. In most cases,
28 // this will be the deserialized JSON structure.
Ash Wilsond3dc2542014-10-20 10:10:48 -040029 Body interface{}
Ash Wilsoneab6a702014-10-20 08:18:30 -040030
Ash Wilson72e4d2c2014-10-20 10:27:30 -040031 // Header contains the HTTP header structure from the original response.
32 Header http.Header
Ash Wilsoneab6a702014-10-20 08:18:30 -040033
Ash Wilson3ce1bd82014-10-31 12:20:00 -040034 // Err is an error that occurred during the operation. It's deferred until
35 // extraction to make it easier to chain the Extract call.
Ash Wilsoneab6a702014-10-20 08:18:30 -040036 Err error
Jamie Hannafordb3120f52014-09-23 15:17:57 +020037}
Ash Wilsona6b08312014-10-02 15:27:45 -040038
Jon Perritt12395212016-02-24 10:41:17 -060039// ExtractInto allows users to provide an object into which `Extract` will extract
40// the `Result.Body`. This would be useful for OpenStack providers that have
41// different fields in the response object than OpenStack proper.
42func (r Result) ExtractInto(to interface{}) error {
43 if r.Err != nil {
44 return r.Err
45 }
46
47 if reader, ok := r.Body.(io.Reader); ok {
48 if readCloser, ok := reader.(io.Closer); ok {
49 defer readCloser.Close()
50 }
51 jsonDecoder := json.NewDecoder(reader)
52 return jsonDecoder.Decode(to)
53 }
54
55 b, err := json.Marshal(r.Body)
56 if err != nil {
57 return err
58 }
59 err = json.Unmarshal(b, to)
60
61 return err
62}
63
Ash Wilson3ce1bd82014-10-31 12:20:00 -040064// PrettyPrintJSON creates a string containing the full response body as
65// pretty-printed JSON. It's useful for capturing test fixtures and for
Ash Wilson0fe6c962014-10-31 15:34:24 -040066// debugging extraction bugs. If you include its output in an issue related to
67// a buggy extraction function, we will all love you forever.
Ash Wilsone8192ac2014-10-21 09:02:01 -040068func (r Result) PrettyPrintJSON() string {
69 pretty, err := json.MarshalIndent(r.Body, "", " ")
70 if err != nil {
71 panic(err.Error())
72 }
73 return string(pretty)
74}
75
Ash Wilson64ba49f2014-10-31 15:31:46 -040076// ErrResult is an internal type to be used by individual resource packages, but
77// its methods will be available on a wide variety of user-facing embedding
78// types.
79//
80// It represents results that only contain a potential error and
Ash Wilson3ce1bd82014-10-31 12:20:00 -040081// nothing else. Usually, if the operation executed successfully, the Err field
82// will be nil; otherwise it will be stocked with a relevant error. Use the
Ash Wilson64ba49f2014-10-31 15:31:46 -040083// ExtractErr method
84// to cleanly pull it out.
Jon Perrittba2395e2014-10-27 15:23:21 -050085type ErrResult struct {
Jon Perritt0c2b0372014-10-27 15:57:29 -050086 Result
Jamie Hannaford021b35c2014-10-27 14:01:53 +010087}
88
Ash Wilson3ce1bd82014-10-31 12:20:00 -040089// ExtractErr is a function that extracts error information, or nil, from a result.
Jon Perrittba2395e2014-10-27 15:23:21 -050090func (r ErrResult) ExtractErr() error {
Jamie Hannaford021b35c2014-10-27 14:01:53 +010091 return r.Err
92}
93
Ash Wilson3ce1bd82014-10-31 12:20:00 -040094/*
Ash Wilson64ba49f2014-10-31 15:31:46 -040095HeaderResult is an internal type to be used by individual resource packages, but
96its methods will be available on a wide variety of user-facing embedding types.
Ash Wilson3ce1bd82014-10-31 12:20:00 -040097
98It represents a result that only contains an error (possibly nil) and an
99http.Header. This is used, for example, by the objectstorage packages in
100openstack, because most of the operations don't return response bodies, but do
101have relevant information in headers.
102*/
Jon Perrittd50f93e2014-10-27 14:19:27 -0500103type HeaderResult struct {
Jon Perritt0c2b0372014-10-27 15:57:29 -0500104 Result
Jon Perrittd50f93e2014-10-27 14:19:27 -0500105}
106
107// ExtractHeader will return the http.Header and error from the HeaderResult.
Ash Wilson3ce1bd82014-10-31 12:20:00 -0400108//
109// header, err := objects.Create(client, "my_container", objects.CreateOpts{}).ExtractHeader()
Jon Perritt66822822016-02-25 03:06:56 -0600110func (r HeaderResult) ExtractInto(to interface{}) error {
111 if r.Err != nil {
112 return r.Err
Jon Perritt63e7a482014-12-04 09:47:23 -0700113 }
Jon Perritt66822822016-02-25 03:06:56 -0600114
115 tmpHeaderMap := map[string]string{}
116 for k, v := range r.Header {
Jon Perritt31b66462016-02-25 22:25:30 -0600117 if len(v) > 0 {
118 tmpHeaderMap[k] = v[0]
119 }
Jon Perritt66822822016-02-25 03:06:56 -0600120 }
121
122 b, err := json.Marshal(tmpHeaderMap)
Jon Perritt63e7a482014-12-04 09:47:23 -0700123 if err != nil {
124 return err
125 }
Jon Perritt66822822016-02-25 03:06:56 -0600126 err = json.Unmarshal(b, to)
127
128 return err
Jon Perritt63e7a482014-12-04 09:47:23 -0700129}
130
Ash Wilson3ce1bd82014-10-31 12:20:00 -0400131// RFC3339Milli describes a common time format used by some API responses.
Ash Wilsona6b08312014-10-02 15:27:45 -0400132const RFC3339Milli = "2006-01-02T15:04:05.999999Z"
Jamie Hannaford369c9c62014-10-08 15:14:43 +0200133
Jon Perritt12395212016-02-24 10:41:17 -0600134type JSONRFC3339Milli time.Time
135
136func (jt *JSONRFC3339Milli) UnmarshalJSON(data []byte) error {
137 b := bytes.NewBuffer(data)
138 dec := json.NewDecoder(b)
139 var s string
140 if err := dec.Decode(&s); err != nil {
141 return err
142 }
143 t, err := time.Parse(RFC3339Milli, s)
144 if err != nil {
145 return err
146 }
147 *jt = JSONRFC3339Milli(t)
148 return nil
149}
150
Jon Perritt66822822016-02-25 03:06:56 -0600151const RFC3339MilliNoZ = "2006-01-02T03:04:05.999999"
152
Jon Perritt12395212016-02-24 10:41:17 -0600153type JSONRFC3339MilliNoZ time.Time
154
155func (jt *JSONRFC3339MilliNoZ) UnmarshalJSON(data []byte) error {
Jon Perritt12395212016-02-24 10:41:17 -0600156 var s string
Jon Perritt82583e72016-02-25 06:41:51 -0600157 if err := json.Unmarshal(data, &s); err != nil {
Jon Perritt12395212016-02-24 10:41:17 -0600158 return err
159 }
Jon Perritt82583e72016-02-25 06:41:51 -0600160 if s == "" {
161 return nil
162 }
Jon Perritt12395212016-02-24 10:41:17 -0600163 t, err := time.Parse(RFC3339MilliNoZ, s)
164 if err != nil {
165 return err
166 }
167 *jt = JSONRFC3339MilliNoZ(t)
168 return nil
169}
170
Jon Perritt66822822016-02-25 03:06:56 -0600171type JSONRFC1123 time.Time
172
173func (jt *JSONRFC1123) UnmarshalJSON(data []byte) error {
Jon Perritt66822822016-02-25 03:06:56 -0600174 var s string
Jon Perritt82583e72016-02-25 06:41:51 -0600175 if err := json.Unmarshal(data, &s); err != nil {
Jon Perritt66822822016-02-25 03:06:56 -0600176 return err
177 }
Jon Perritt82583e72016-02-25 06:41:51 -0600178 if s == "" {
179 return nil
180 }
Jon Perritt66822822016-02-25 03:06:56 -0600181 t, err := time.Parse(time.RFC1123, s)
182 if err != nil {
183 return err
184 }
185 *jt = JSONRFC1123(t)
186 return nil
187}
188
189type JSONUnix time.Time
190
191func (jt *JSONUnix) UnmarshalJSON(data []byte) error {
Jon Perritt66822822016-02-25 03:06:56 -0600192 var s string
Jon Perritt82583e72016-02-25 06:41:51 -0600193 if err := json.Unmarshal(data, &s); err != nil {
Jon Perritt66822822016-02-25 03:06:56 -0600194 return err
195 }
Jon Perritt82583e72016-02-25 06:41:51 -0600196 if s == "" {
197 return nil
198 }
Jon Perritt66822822016-02-25 03:06:56 -0600199 unix, err := strconv.ParseInt(s, 10, 64)
200 if err != nil {
201 return err
202 }
203 t = time.Unix(unix, 0)
204 *jt = JSONUnix(t)
205 return nil
206}
207
Jon Perritt31b66462016-02-25 22:25:30 -0600208// RFC3339NoZ is the time format used in Heat (Orchestration).
Jon Perritt66822822016-02-25 03:06:56 -0600209const RFC3339NoZ = "2006-01-02T15:04:05"
210
211type JSONRFC3339NoZ time.Time
212
213func (jt *JSONRFC3339NoZ) UnmarshalJSON(data []byte) error {
Jon Perritt66822822016-02-25 03:06:56 -0600214 var s string
Jon Perritt82583e72016-02-25 06:41:51 -0600215 if err := json.Unmarshal(data, &s); err != nil {
Jon Perritt66822822016-02-25 03:06:56 -0600216 return err
217 }
218 if s == "" {
219 return nil
220 }
221 t, err := time.Parse(RFC3339NoZ, s)
222 if err != nil {
223 return err
224 }
225 *jt = JSONRFC3339NoZ(t)
226 return nil
227}
Pratik Mallyae1b6cbb2015-09-09 14:24:14 -0500228
Ash Wilson3ce1bd82014-10-31 12:20:00 -0400229/*
230Link is an internal type to be used in packages of collection resources that are
231paginated in a certain way.
232
233It's a response substructure common to many paginated collection results that is
234used to point to related pages. Usually, the one we care about is the one with
235Rel field set to "next".
236*/
Jamie Hannaford369c9c62014-10-08 15:14:43 +0200237type Link struct {
Jon Perritt12395212016-02-24 10:41:17 -0600238 Href string `json:"href"`
239 Rel string `json:"rel"`
Jamie Hannaford369c9c62014-10-08 15:14:43 +0200240}
241
Ash Wilson3ce1bd82014-10-31 12:20:00 -0400242/*
243ExtractNextURL is an internal function useful for packages of collection
244resources that are paginated in a certain way.
245
jrperrittb1013232016-02-10 19:01:53 -0600246It attempts to extract the "next" URL from slice of Link structs, or
Ash Wilson3ce1bd82014-10-31 12:20:00 -0400247"" if no such URL is present.
248*/
Jamie Hannaford369c9c62014-10-08 15:14:43 +0200249func ExtractNextURL(links []Link) (string, error) {
250 var url string
251
252 for _, l := range links {
253 if l.Rel == "next" {
254 url = l.Href
255 }
256 }
257
258 if url == "" {
259 return "", nil
260 }
261
262 return url, nil
263}