1package gophercloud
2
3import (
4	"bytes"
5	"encoding/json"
6	"fmt"
7	"io"
8	"net/http"
9	"reflect"
10	"strconv"
11	"time"
12)
13
14/*
15Result 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.
17
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*/
28type Result struct {
29	// Body is the payload of the HTTP response from the server. In most cases,
30	// this will be the deserialized JSON structure.
31	Body interface{}
32
33	// Header contains the HTTP header structure from the original response.
34	Header http.Header
35
36	// Err is an error that occurred during the operation. It's deferred until
37	// extraction to make it easier to chain the Extract call.
38	Err error
39}
40
41// 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		}
53		return json.NewDecoder(reader).Decode(to)
54	}
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
65func (r Result) extractIntoPtr(to interface{}, label string) error {
66	if label == "" {
67		return r.ExtractInto(&to)
68	}
69
70	var m map[string]interface{}
71	err := r.ExtractInto(&m)
72	if err != nil {
73		return err
74	}
75
76	b, err := json.Marshal(m[label])
77	if err != nil {
78		return err
79	}
80
81	toValue := reflect.ValueOf(to)
82	if toValue.Kind() == reflect.Ptr {
83		toValue = toValue.Elem()
84	}
85
86	switch toValue.Kind() {
87	case reflect.Slice:
88		typeOfV := toValue.Type().Elem()
89		if typeOfV.Kind() == reflect.Struct {
90			if typeOfV.NumField() > 0 && typeOfV.Field(0).Anonymous {
91				newSlice := reflect.MakeSlice(reflect.SliceOf(typeOfV), 0, 0)
92
93				if mSlice, ok := m[label].([]interface{}); ok {
94					for _, v := range mSlice {
95						// For each iteration of the slice, we create a new struct.
96						// This is to work around a bug where elements of a slice
97						// are reused and not overwritten when the same copy of the
98						// struct is used:
99						//
100						// https://github.com/golang/go/issues/21092
101						// https://github.com/golang/go/issues/24155
102						// https://play.golang.org/p/NHo3ywlPZli
103						newType := reflect.New(typeOfV).Elem()
104
105						b, err := json.Marshal(v)
106						if err != nil {
107							return err
108						}
109
110						// This is needed for structs with an UnmarshalJSON method.
111						// Technically this is just unmarshalling the response into
112						// a struct that is never used, but it's good enough to
113						// trigger the UnmarshalJSON method.
114						for i := 0; i < newType.NumField(); i++ {
115							s := newType.Field(i).Addr().Interface()
116
117							// Unmarshal is used rather than NewDecoder to also work
118							// around the above-mentioned bug.
119							err = json.Unmarshal(b, s)
120							if err != nil {
121								return err
122							}
123						}
124
125						newSlice = reflect.Append(newSlice, newType)
126					}
127				}
128
129				// "to" should now be properly modeled to receive the
130				// JSON response body and unmarshal into all the correct
131				// fields of the struct or composed extension struct
132				// at the end of this method.
133				toValue.Set(newSlice)
134			}
135		}
136	case reflect.Struct:
137		typeOfV := toValue.Type()
138		if typeOfV.NumField() > 0 && typeOfV.Field(0).Anonymous {
139			for i := 0; i < toValue.NumField(); i++ {
140				toField := toValue.Field(i)
141				if toField.Kind() == reflect.Struct {
142					s := toField.Addr().Interface()
143					err = json.NewDecoder(bytes.NewReader(b)).Decode(s)
144					if err != nil {
145						return err
146					}
147				}
148			}
149		}
150	}
151
152	err = json.Unmarshal(b, &to)
153	return err
154}
155
156// ExtractIntoStructPtr will unmarshal the Result (r) into the provided
157// interface{} (to).
158//
159// NOTE: For internal use only
160//
161// `to` must be a pointer to an underlying struct type
162//
163// If provided, `label` will be filtered out of the response
164// body prior to `r` being unmarshalled into `to`.
165func (r Result) ExtractIntoStructPtr(to interface{}, label string) error {
166	if r.Err != nil {
167		return r.Err
168	}
169
170	t := reflect.TypeOf(to)
171	if k := t.Kind(); k != reflect.Ptr {
172		return fmt.Errorf("Expected pointer, got %v", k)
173	}
174	switch t.Elem().Kind() {
175	case reflect.Struct:
176		return r.extractIntoPtr(to, label)
177	default:
178		return fmt.Errorf("Expected pointer to struct, got: %v", t)
179	}
180}
181
182// ExtractIntoSlicePtr will unmarshal the Result (r) into the provided
183// interface{} (to).
184//
185// NOTE: For internal use only
186//
187// `to` must be a pointer to an underlying slice type
188//
189// If provided, `label` will be filtered out of the response
190// body prior to `r` being unmarshalled into `to`.
191func (r Result) ExtractIntoSlicePtr(to interface{}, label string) error {
192	if r.Err != nil {
193		return r.Err
194	}
195
196	t := reflect.TypeOf(to)
197	if k := t.Kind(); k != reflect.Ptr {
198		return fmt.Errorf("Expected pointer, got %v", k)
199	}
200	switch t.Elem().Kind() {
201	case reflect.Slice:
202		return r.extractIntoPtr(to, label)
203	default:
204		return fmt.Errorf("Expected pointer to slice, got: %v", t)
205	}
206}
207
208// PrettyPrintJSON creates a string containing the full response body as
209// pretty-printed JSON. It's useful for capturing test fixtures and for
210// debugging extraction bugs. If you include its output in an issue related to
211// a buggy extraction function, we will all love you forever.
212func (r Result) PrettyPrintJSON() string {
213	pretty, err := json.MarshalIndent(r.Body, "", "  ")
214	if err != nil {
215		panic(err.Error())
216	}
217	return string(pretty)
218}
219
220// ErrResult is an internal type to be used by individual resource packages, but
221// its methods will be available on a wide variety of user-facing embedding
222// types.
223//
224// It represents results that only contain a potential error and
225// nothing else. Usually, if the operation executed successfully, the Err field
226// will be nil; otherwise it will be stocked with a relevant error. Use the
227// ExtractErr method
228// to cleanly pull it out.
229type ErrResult struct {
230	Result
231}
232
233// ExtractErr is a function that extracts error information, or nil, from a result.
234func (r ErrResult) ExtractErr() error {
235	return r.Err
236}
237
238/*
239HeaderResult is an internal type to be used by individual resource packages, but
240its methods will be available on a wide variety of user-facing embedding types.
241
242It represents a result that only contains an error (possibly nil) and an
243http.Header. This is used, for example, by the objectstorage packages in
244openstack, because most of the operations don't return response bodies, but do
245have relevant information in headers.
246*/
247type HeaderResult struct {
248	Result
249}
250
251// ExtractInto allows users to provide an object into which `Extract` will
252// extract the http.Header headers of the result.
253func (r HeaderResult) ExtractInto(to interface{}) error {
254	if r.Err != nil {
255		return r.Err
256	}
257
258	tmpHeaderMap := map[string]string{}
259	for k, v := range r.Header {
260		if len(v) > 0 {
261			tmpHeaderMap[k] = v[0]
262		}
263	}
264
265	b, err := json.Marshal(tmpHeaderMap)
266	if err != nil {
267		return err
268	}
269	err = json.Unmarshal(b, to)
270
271	return err
272}
273
274// RFC3339Milli describes a common time format used by some API responses.
275const RFC3339Milli = "2006-01-02T15:04:05.999999Z"
276
277type JSONRFC3339Milli time.Time
278
279func (jt *JSONRFC3339Milli) UnmarshalJSON(data []byte) error {
280	b := bytes.NewBuffer(data)
281	dec := json.NewDecoder(b)
282	var s string
283	if err := dec.Decode(&s); err != nil {
284		return err
285	}
286	t, err := time.Parse(RFC3339Milli, s)
287	if err != nil {
288		return err
289	}
290	*jt = JSONRFC3339Milli(t)
291	return nil
292}
293
294const RFC3339MilliNoZ = "2006-01-02T15:04:05.999999"
295
296type JSONRFC3339MilliNoZ time.Time
297
298func (jt *JSONRFC3339MilliNoZ) UnmarshalJSON(data []byte) error {
299	var s string
300	if err := json.Unmarshal(data, &s); err != nil {
301		return err
302	}
303	if s == "" {
304		return nil
305	}
306	t, err := time.Parse(RFC3339MilliNoZ, s)
307	if err != nil {
308		return err
309	}
310	*jt = JSONRFC3339MilliNoZ(t)
311	return nil
312}
313
314type JSONRFC1123 time.Time
315
316func (jt *JSONRFC1123) UnmarshalJSON(data []byte) error {
317	var s string
318	if err := json.Unmarshal(data, &s); err != nil {
319		return err
320	}
321	if s == "" {
322		return nil
323	}
324	t, err := time.Parse(time.RFC1123, s)
325	if err != nil {
326		return err
327	}
328	*jt = JSONRFC1123(t)
329	return nil
330}
331
332type JSONUnix time.Time
333
334func (jt *JSONUnix) UnmarshalJSON(data []byte) error {
335	var s string
336	if err := json.Unmarshal(data, &s); err != nil {
337		return err
338	}
339	if s == "" {
340		return nil
341	}
342	unix, err := strconv.ParseInt(s, 10, 64)
343	if err != nil {
344		return err
345	}
346	t = time.Unix(unix, 0)
347	*jt = JSONUnix(t)
348	return nil
349}
350
351// RFC3339NoZ is the time format used in Heat (Orchestration).
352const RFC3339NoZ = "2006-01-02T15:04:05"
353
354type JSONRFC3339NoZ time.Time
355
356func (jt *JSONRFC3339NoZ) UnmarshalJSON(data []byte) error {
357	var s string
358	if err := json.Unmarshal(data, &s); err != nil {
359		return err
360	}
361	if s == "" {
362		return nil
363	}
364	t, err := time.Parse(RFC3339NoZ, s)
365	if err != nil {
366		return err
367	}
368	*jt = JSONRFC3339NoZ(t)
369	return nil
370}
371
372// RFC3339ZNoT is the time format used in Zun (Containers Service).
373const RFC3339ZNoT = "2006-01-02 15:04:05-07:00"
374
375type JSONRFC3339ZNoT time.Time
376
377func (jt *JSONRFC3339ZNoT) UnmarshalJSON(data []byte) error {
378	var s string
379	if err := json.Unmarshal(data, &s); err != nil {
380		return err
381	}
382	if s == "" {
383		return nil
384	}
385	t, err := time.Parse(RFC3339ZNoT, s)
386	if err != nil {
387		return err
388	}
389	*jt = JSONRFC3339ZNoT(t)
390	return nil
391}
392
393// RFC3339ZNoTNoZ is another time format used in Zun (Containers Service).
394const RFC3339ZNoTNoZ = "2006-01-02 15:04:05"
395
396type JSONRFC3339ZNoTNoZ time.Time
397
398func (jt *JSONRFC3339ZNoTNoZ) UnmarshalJSON(data []byte) error {
399	var s string
400	if err := json.Unmarshal(data, &s); err != nil {
401		return err
402	}
403	if s == "" {
404		return nil
405	}
406	t, err := time.Parse(RFC3339ZNoTNoZ, s)
407	if err != nil {
408		return err
409	}
410	*jt = JSONRFC3339ZNoTNoZ(t)
411	return nil
412}
413
414/*
415Link is an internal type to be used in packages of collection resources that are
416paginated in a certain way.
417
418It's a response substructure common to many paginated collection results that is
419used to point to related pages. Usually, the one we care about is the one with
420Rel field set to "next".
421*/
422type Link struct {
423	Href string `json:"href"`
424	Rel  string `json:"rel"`
425}
426
427/*
428ExtractNextURL is an internal function useful for packages of collection
429resources that are paginated in a certain way.
430
431It attempts to extract the "next" URL from slice of Link structs, or
432"" if no such URL is present.
433*/
434func ExtractNextURL(links []Link) (string, error) {
435	var url string
436
437	for _, l := range links {
438		if l.Rel == "next" {
439			url = l.Href
440		}
441	}
442
443	if url == "" {
444		return "", nil
445	}
446
447	return url, nil
448}
449