github.com/huaweicloud/golangsdk@v0.0.0-20210831081626-d823fe11ceba/results.go (about) 1 package golangsdk 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 // 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. 44 func (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 := jsonMarshal(r.Body) 57 if err != nil { 58 return err 59 } 60 err = json.Unmarshal(b, to) 61 62 return err 63 } 64 65 func (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 := jsonMarshal(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 := jsonMarshal(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 // jtopjian: This was put into place to resolve the issue 136 // described at 137 // https://github.com/gophercloud/gophercloud/issues/1963 138 // 139 // This probably isn't the best fix, but it appears to 140 // be resolving the issue, so I'm going to implement it 141 // for now. 142 // 143 // For future readers, this entire case statement could 144 // use a review. 145 return nil 146 } 147 } 148 case reflect.Struct: 149 typeOfV := toValue.Type() 150 if typeOfV.NumField() > 0 && typeOfV.Field(0).Anonymous { 151 for i := 0; i < toValue.NumField(); i++ { 152 toField := toValue.Field(i) 153 if toField.Kind() == reflect.Struct { 154 s := toField.Addr().Interface() 155 err = json.NewDecoder(bytes.NewReader(b)).Decode(s) 156 if err != nil { 157 return err 158 } 159 } 160 } 161 } 162 } 163 164 err = json.Unmarshal(b, &to) 165 return err 166 } 167 168 // ExtractIntoStructPtr will unmarshal the Result (r) into the provided 169 // interface{} (to). 170 // 171 // NOTE: For internal use only 172 // 173 // `to` must be a pointer to an underlying struct type 174 // 175 // If provided, `label` will be filtered out of the response 176 // body prior to `r` being unmarshalled into `to`. 177 func (r Result) ExtractIntoStructPtr(to interface{}, label string) error { 178 if r.Err != nil { 179 return r.Err 180 } 181 182 t := reflect.TypeOf(to) 183 if k := t.Kind(); k != reflect.Ptr { 184 return fmt.Errorf("Expected pointer, got %v", k) 185 } 186 switch t.Elem().Kind() { 187 case reflect.Struct: 188 return r.extractIntoPtr(to, label) 189 default: 190 return fmt.Errorf("Expected pointer to struct, got: %v", t) 191 } 192 } 193 194 // ExtractIntoSlicePtr will unmarshal the Result (r) into the provided 195 // interface{} (to). 196 // 197 // NOTE: For internal use only 198 // 199 // `to` must be a pointer to an underlying slice type 200 // 201 // If provided, `label` will be filtered out of the response 202 // body prior to `r` being unmarshalled into `to`. 203 func (r Result) ExtractIntoSlicePtr(to interface{}, label string) error { 204 if r.Err != nil { 205 return r.Err 206 } 207 208 t := reflect.TypeOf(to) 209 if k := t.Kind(); k != reflect.Ptr { 210 return fmt.Errorf("Expected pointer, got %v", k) 211 } 212 switch t.Elem().Kind() { 213 case reflect.Slice: 214 return r.extractIntoPtr(to, label) 215 default: 216 return fmt.Errorf("Expected pointer to slice, got: %v", t) 217 } 218 } 219 220 // PrettyPrintJSON creates a string containing the full response body as 221 // pretty-printed JSON. It's useful for capturing test fixtures and for 222 // debugging extraction bugs. If you include its output in an issue related to 223 // a buggy extraction function, we will all love you forever. 224 func (r Result) PrettyPrintJSON() string { 225 pretty, err := json.MarshalIndent(r.Body, "", " ") 226 if err != nil { 227 panic(err.Error()) 228 } 229 return string(pretty) 230 } 231 232 // ErrResult is an internal type to be used by individual resource packages, but 233 // its methods will be available on a wide variety of user-facing embedding 234 // types. 235 // 236 // It represents results that only contain a potential error and 237 // nothing else. Usually, if the operation executed successfully, the Err field 238 // will be nil; otherwise it will be stocked with a relevant error. Use the 239 // ExtractErr method 240 // to cleanly pull it out. 241 type ErrResult struct { 242 Result 243 } 244 245 // ExtractErr is a function that extracts error information, or nil, from a result. 246 func (r ErrResult) ExtractErr() error { 247 return r.Err 248 } 249 250 /* 251 HeaderResult is an internal type to be used by individual resource packages, but 252 its methods will be available on a wide variety of user-facing embedding types. 253 254 It represents a result that only contains an error (possibly nil) and an 255 http.Header. This is used, for example, by the objectstorage packages in 256 openstack, because most of the operations don't return response bodies, but do 257 have relevant information in headers. 258 */ 259 type HeaderResult struct { 260 Result 261 } 262 263 // ExtractInto allows users to provide an object into which `Extract` will 264 // extract the http.Header headers of the result. 265 func (r HeaderResult) ExtractInto(to interface{}) error { 266 if r.Err != nil { 267 return r.Err 268 } 269 270 tmpHeaderMap := map[string]string{} 271 for k, v := range r.Header { 272 if len(v) > 0 { 273 tmpHeaderMap[k] = v[0] 274 } 275 } 276 277 b, err := jsonMarshal(tmpHeaderMap) 278 if err != nil { 279 return err 280 } 281 err = json.Unmarshal(b, to) 282 283 return err 284 } 285 286 // RFC3339Milli describes a common time format used by some API responses. 287 const RFC3339Milli = "2006-01-02T15:04:05.999999Z" 288 289 type JSONRFC3339Milli time.Time 290 291 func (jt *JSONRFC3339Milli) UnmarshalJSON(data []byte) error { 292 b := bytes.NewBuffer(data) 293 dec := json.NewDecoder(b) 294 var s string 295 if err := dec.Decode(&s); err != nil { 296 return err 297 } 298 t, err := time.Parse(RFC3339Milli, s) 299 if err != nil { 300 return err 301 } 302 *jt = JSONRFC3339Milli(t) 303 return nil 304 } 305 306 const RFC3339MilliNoZ = "2006-01-02T15:04:05.999999" 307 308 type JSONRFC3339MilliNoZ time.Time 309 310 func (jt *JSONRFC3339MilliNoZ) UnmarshalJSON(data []byte) error { 311 var s string 312 if err := json.Unmarshal(data, &s); err != nil { 313 return err 314 } 315 if s == "" { 316 return nil 317 } 318 t, err := time.Parse(RFC3339MilliNoZ, s) 319 if err != nil { 320 return err 321 } 322 *jt = JSONRFC3339MilliNoZ(t) 323 return nil 324 } 325 326 type JSONRFC1123 time.Time 327 328 func (jt *JSONRFC1123) UnmarshalJSON(data []byte) error { 329 var s string 330 if err := json.Unmarshal(data, &s); err != nil { 331 return err 332 } 333 if s == "" { 334 return nil 335 } 336 t, err := time.Parse(time.RFC1123, s) 337 if err != nil { 338 return err 339 } 340 *jt = JSONRFC1123(t) 341 return nil 342 } 343 344 type JSONUnix time.Time 345 346 func (jt *JSONUnix) UnmarshalJSON(data []byte) error { 347 var s string 348 if err := json.Unmarshal(data, &s); err != nil { 349 return err 350 } 351 if s == "" { 352 return nil 353 } 354 unix, err := strconv.ParseInt(s, 10, 64) 355 if err != nil { 356 return err 357 } 358 t = time.Unix(unix, 0) 359 *jt = JSONUnix(t) 360 return nil 361 } 362 363 // RFC3339NoZ is the time format used in Heat (Orchestration). 364 const RFC3339NoZ = "2006-01-02T15:04:05" 365 366 type JSONRFC3339NoZ time.Time 367 368 func (jt *JSONRFC3339NoZ) UnmarshalJSON(data []byte) error { 369 var s string 370 if err := json.Unmarshal(data, &s); err != nil { 371 return err 372 } 373 if s == "" { 374 return nil 375 } 376 t, err := time.Parse(RFC3339NoZ, s) 377 if err != nil { 378 return err 379 } 380 *jt = JSONRFC3339NoZ(t) 381 return nil 382 } 383 384 // RFC3339ZNoT is the time format used in Zun (Containers Service). 385 const RFC3339ZNoT = "2006-01-02 15:04:05-07:00" 386 387 type JSONRFC3339ZNoT time.Time 388 389 func (jt *JSONRFC3339ZNoT) UnmarshalJSON(data []byte) error { 390 var s string 391 if err := json.Unmarshal(data, &s); err != nil { 392 return err 393 } 394 if s == "" { 395 return nil 396 } 397 t, err := time.Parse(RFC3339ZNoT, s) 398 if err != nil { 399 return err 400 } 401 *jt = JSONRFC3339ZNoT(t) 402 return nil 403 } 404 405 // RFC3339ZNoTNoZ is another time format used in Zun (Containers Service). 406 const RFC3339ZNoTNoZ = "2006-01-02 15:04:05" 407 408 type JSONRFC3339ZNoTNoZ time.Time 409 410 func (jt *JSONRFC3339ZNoTNoZ) UnmarshalJSON(data []byte) error { 411 var s string 412 if err := json.Unmarshal(data, &s); err != nil { 413 return err 414 } 415 if s == "" { 416 return nil 417 } 418 t, err := time.Parse(RFC3339ZNoTNoZ, s) 419 if err != nil { 420 return err 421 } 422 *jt = JSONRFC3339ZNoTNoZ(t) 423 return nil 424 } 425 426 /* 427 Link is an internal type to be used in packages of collection resources that are 428 paginated in a certain way. 429 430 It's a response substructure common to many paginated collection results that is 431 used to point to related pages. Usually, the one we care about is the one with 432 Rel field set to "next". 433 */ 434 type Link struct { 435 Href string `json:"href"` 436 Rel string `json:"rel"` 437 } 438 439 /* 440 ExtractNextURL is an internal function useful for packages of collection 441 resources that are paginated in a certain way. 442 443 It attempts to extract the "next" URL from slice of Link structs, or 444 "" if no such URL is present. 445 */ 446 func ExtractNextURL(links []Link) (string, error) { 447 var url string 448 449 for _, l := range links { 450 if l.Rel == "next" { 451 url = l.Href 452 } 453 } 454 455 if url == "" { 456 return "", nil 457 } 458 459 return url, nil 460 }