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