15 Result is an internal type to be used by individual resource packages, but its
16 methods will be available on a wide variety of user-facing embedding types.
18 It acts as a base struct that other Result types, returned from request
19 functions, can embed for convenience. All Results capture basic information
20 from the HTTP transaction that was performed, including the response body,
21 HTTP headers, and any errors that happened.
23 Generally, each Result type will have an Extract method that can be used to
24 further interpret the result's payload in a specific context. Extensions or
25 providers can then provide additional extraction functions to pull out
26 provider- or extension-specific information as well.
29 // Body is the payload of the HTTP response from the server. In most cases,
30 // this will be the deserialized JSON structure.
33 // Header contains the HTTP header structure from the original response.
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.
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.
44 func (r Result) ExtractInto(to interface{}) error {
49 if reader, ok := r.Body.(io.Reader); ok {
50 if readCloser, ok := reader.(io.Closer); ok {
51 defer readCloser.Close()
53 return json.NewDecoder(reader).Decode(to)
56 b, err := json.Marshal(r.Body)
60 err = json.Unmarshal(b, to)
65 func (r Result) extractIntoPtr(to interface{}, label string) error {
67 return r.ExtractInto(&to)
70 var m map[string]interface{}
71 err := r.ExtractInto(&m)
76 b, err := json.Marshal(m[label])
81 toValue := reflect.ValueOf(to)
82 if toValue.Kind() == reflect.Ptr {
83 toValue = toValue.Elem()
86 switch toValue.Kind() {
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)
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
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()
105 b, err := json.Marshal(v)
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()
117 // Unmarshal is used rather than NewDecoder to also work
118 // around the above-mentioned bug.
119 err = json.Unmarshal(b, s)
125 newSlice = reflect.Append(newSlice, newType)
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)
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)
152 err = json.Unmarshal(b, &to)
156 // ExtractIntoStructPtr will unmarshal the Result (r) into the provided
159 // NOTE: For internal use only
161 // `to` must be a pointer to an underlying struct type
163 // If provided, `label` will be filtered out of the response
164 // body prior to `r` being unmarshalled into `to`.
165 func (r Result) ExtractIntoStructPtr(to interface{}, label string) error {
170 t := reflect.TypeOf(to)
171 if k := t.Kind(); k != reflect.Ptr {
172 return fmt.Errorf("Expected pointer, got %v", k)
174 switch t.Elem().Kind() {
176 return r.extractIntoPtr(to, label)
178 return fmt.Errorf("Expected pointer to struct, got: %v", t)
182 // ExtractIntoSlicePtr will unmarshal the Result (r) into the provided
185 // NOTE: For internal use only
187 // `to` must be a pointer to an underlying slice type
189 // If provided, `label` will be filtered out of the response
190 // body prior to `r` being unmarshalled into `to`.
191 func (r Result) ExtractIntoSlicePtr(to interface{}, label string) error {
196 t := reflect.TypeOf(to)
197 if k := t.Kind(); k != reflect.Ptr {
198 return fmt.Errorf("Expected pointer, got %v", k)
200 switch t.Elem().Kind() {
202 return r.extractIntoPtr(to, label)
204 return fmt.Errorf("Expected pointer to slice, got: %v", t)
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.
212 func (r Result) PrettyPrintJSON() string {
213 pretty, err := json.MarshalIndent(r.Body, "", " ")
217 return string(pretty)
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
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
228 // to cleanly pull it out.
229 type ErrResult struct {
233 // ExtractErr is a function that extracts error information, or nil, from a result.
234 func (r ErrResult) ExtractErr() error {
239 HeaderResult is an internal type to be used by individual resource packages, but
240 its methods will be available on a wide variety of user-facing embedding types.
242 It represents a result that only contains an error (possibly nil) and an
243 http.Header. This is used, for example, by the objectstorage packages in
244 openstack, because most of the operations don't return response bodies, but do
245 have relevant information in headers.
247 type HeaderResult struct {
251 // ExtractInto allows users to provide an object into which `Extract` will
252 // extract the http.Header headers of the result.
253 func (r HeaderResult) ExtractInto(to interface{}) error {
258 tmpHeaderMap := map[string]string{}
259 for k, v := range r.Header {
261 tmpHeaderMap[k] = v[0]
265 b, err := json.Marshal(tmpHeaderMap)
269 err = json.Unmarshal(b, to)
274 // RFC3339Milli describes a common time format used by some API responses.
275 const RFC3339Milli = "2006-01-02T15:04:05.999999Z"
277 type JSONRFC3339Milli time.Time
279 func (jt *JSONRFC3339Milli) UnmarshalJSON(data []byte) error {
280 b := bytes.NewBuffer(data)
281 dec := json.NewDecoder(b)
283 if err := dec.Decode(&s); err != nil {
286 t, err := time.Parse(RFC3339Milli, s)
290 *jt = JSONRFC3339Milli(t)
294 const RFC3339MilliNoZ = "2006-01-02T15:04:05.999999"
296 type JSONRFC3339MilliNoZ time.Time
298 func (jt *JSONRFC3339MilliNoZ) UnmarshalJSON(data []byte) error {
300 if err := json.Unmarshal(data, &s); err != nil {
306 t, err := time.Parse(RFC3339MilliNoZ, s)
310 *jt = JSONRFC3339MilliNoZ(t)
314 type JSONRFC1123 time.Time
316 func (jt *JSONRFC1123) UnmarshalJSON(data []byte) error {
318 if err := json.Unmarshal(data, &s); err != nil {
324 t, err := time.Parse(time.RFC1123, s)
332 type JSONUnix time.Time
334 func (jt *JSONUnix) UnmarshalJSON(data []byte) error {
336 if err := json.Unmarshal(data, &s); err != nil {
342 unix, err := strconv.ParseInt(s, 10, 64)
346 t = time.Unix(unix, 0)
351 // RFC3339NoZ is the time format used in Heat (Orchestration).
352 const RFC3339NoZ = "2006-01-02T15:04:05"
354 type JSONRFC3339NoZ time.Time
356 func (jt *JSONRFC3339NoZ) UnmarshalJSON(data []byte) error {
358 if err := json.Unmarshal(data, &s); err != nil {
364 t, err := time.Parse(RFC3339NoZ, s)
368 *jt = JSONRFC3339NoZ(t)
372 // RFC3339ZNoT is the time format used in Zun (Containers Service).
373 const RFC3339ZNoT = "2006-01-02 15:04:05-07:00"
375 type JSONRFC3339ZNoT time.Time
377 func (jt *JSONRFC3339ZNoT) UnmarshalJSON(data []byte) error {
379 if err := json.Unmarshal(data, &s); err != nil {
385 t, err := time.Parse(RFC3339ZNoT, s)
389 *jt = JSONRFC3339ZNoT(t)
393 // RFC3339ZNoTNoZ is another time format used in Zun (Containers Service).
394 const RFC3339ZNoTNoZ = "2006-01-02 15:04:05"
396 type JSONRFC3339ZNoTNoZ time.Time
398 func (jt *JSONRFC3339ZNoTNoZ) UnmarshalJSON(data []byte) error {
400 if err := json.Unmarshal(data, &s); err != nil {
406 t, err := time.Parse(RFC3339ZNoTNoZ, s)
410 *jt = JSONRFC3339ZNoTNoZ(t)
415 Link is an internal type to be used in packages of collection resources that are
416 paginated in a certain way.
418 It's a response substructure common to many paginated collection results that is
419 used to point to related pages. Usually, the one we care about is the one with
420 Rel field set to "next".
423 Href string `json:"href"`
424 Rel string `json:"rel"`
428 ExtractNextURL is an internal function useful for packages of collection
429 resources that are paginated in a certain way.
431 It attempts to extract the "next" URL from slice of Link structs, or
432 "" if no such URL is present.
434 func ExtractNextURL(links []Link) (string, error) {
437 for _, l := range links {