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