123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404 |
- package gophercloud
- import (
- "bytes"
- "encoding/json"
- "fmt"
- "io"
- "net/http"
- "reflect"
- "strconv"
- "time"
- )
- /*
- Result is an internal type to be used by individual resource packages, but its
- methods will be available on a wide variety of user-facing embedding types.
- It acts as a base struct that other Result types, returned from request
- functions, can embed for convenience. All Results capture basic information
- from the HTTP transaction that was performed, including the response body,
- HTTP headers, and any errors that happened.
- Generally, each Result type will have an Extract method that can be used to
- further interpret the result's payload in a specific context. Extensions or
- providers can then provide additional extraction functions to pull out
- provider- or extension-specific information as well.
- */
- type Result struct {
- // Body is the payload of the HTTP response from the server. In most cases,
- // this will be the deserialized JSON structure.
- Body interface{}
- // Header contains the HTTP header structure from the original response.
- Header http.Header
- // Err is an error that occurred during the operation. It's deferred until
- // extraction to make it easier to chain the Extract call.
- Err error
- }
- // ExtractInto allows users to provide an object into which `Extract` will extract
- // the `Result.Body`. This would be useful for OpenStack providers that have
- // different fields in the response object than OpenStack proper.
- func (r Result) ExtractInto(to interface{}) error {
- if r.Err != nil {
- return r.Err
- }
- if reader, ok := r.Body.(io.Reader); ok {
- if readCloser, ok := reader.(io.Closer); ok {
- defer readCloser.Close()
- }
- return json.NewDecoder(reader).Decode(to)
- }
- b, err := json.Marshal(r.Body)
- if err != nil {
- return err
- }
- err = json.Unmarshal(b, to)
- return err
- }
- func (r Result) extractIntoPtr(to interface{}, label string) error {
- if label == "" {
- return r.ExtractInto(&to)
- }
- var m map[string]interface{}
- err := r.ExtractInto(&m)
- if err != nil {
- return err
- }
- b, err := json.Marshal(m[label])
- if err != nil {
- return err
- }
- toValue := reflect.ValueOf(to)
- if toValue.Kind() == reflect.Ptr {
- toValue = toValue.Elem()
- }
- switch toValue.Kind() {
- case reflect.Slice:
- typeOfV := toValue.Type().Elem()
- if typeOfV.Kind() == reflect.Struct {
- if typeOfV.NumField() > 0 && typeOfV.Field(0).Anonymous {
- newSlice := reflect.MakeSlice(reflect.SliceOf(typeOfV), 0, 0)
- newType := reflect.New(typeOfV).Elem()
- for _, v := range m[label].([]interface{}) {
- b, err := json.Marshal(v)
- if err != nil {
- return err
- }
- for i := 0; i < newType.NumField(); i++ {
- s := newType.Field(i).Addr().Interface()
- err = json.NewDecoder(bytes.NewReader(b)).Decode(s)
- if err != nil {
- return err
- }
- }
- newSlice = reflect.Append(newSlice, newType)
- }
- toValue.Set(newSlice)
- }
- }
- case reflect.Struct:
- typeOfV := toValue.Type()
- if typeOfV.NumField() > 0 && typeOfV.Field(0).Anonymous {
- for i := 0; i < toValue.NumField(); i++ {
- toField := toValue.Field(i)
- if toField.Kind() == reflect.Struct {
- s := toField.Addr().Interface()
- err = json.NewDecoder(bytes.NewReader(b)).Decode(s)
- if err != nil {
- return err
- }
- }
- }
- }
- }
- err = json.Unmarshal(b, &to)
- return err
- }
- // ExtractIntoStructPtr will unmarshal the Result (r) into the provided
- // interface{} (to).
- //
- // NOTE: For internal use only
- //
- // `to` must be a pointer to an underlying struct type
- //
- // If provided, `label` will be filtered out of the response
- // body prior to `r` being unmarshalled into `to`.
- func (r Result) ExtractIntoStructPtr(to interface{}, label string) error {
- if r.Err != nil {
- return r.Err
- }
- t := reflect.TypeOf(to)
- if k := t.Kind(); k != reflect.Ptr {
- return fmt.Errorf("Expected pointer, got %v", k)
- }
- switch t.Elem().Kind() {
- case reflect.Struct:
- return r.extractIntoPtr(to, label)
- default:
- return fmt.Errorf("Expected pointer to struct, got: %v", t)
- }
- }
- // ExtractIntoSlicePtr will unmarshal the Result (r) into the provided
- // interface{} (to).
- //
- // NOTE: For internal use only
- //
- // `to` must be a pointer to an underlying slice type
- //
- // If provided, `label` will be filtered out of the response
- // body prior to `r` being unmarshalled into `to`.
- func (r Result) ExtractIntoSlicePtr(to interface{}, label string) error {
- if r.Err != nil {
- return r.Err
- }
- t := reflect.TypeOf(to)
- if k := t.Kind(); k != reflect.Ptr {
- return fmt.Errorf("Expected pointer, got %v", k)
- }
- switch t.Elem().Kind() {
- case reflect.Slice:
- return r.extractIntoPtr(to, label)
- default:
- return fmt.Errorf("Expected pointer to slice, got: %v", t)
- }
- }
- // PrettyPrintJSON creates a string containing the full response body as
- // pretty-printed JSON. It's useful for capturing test fixtures and for
- // debugging extraction bugs. If you include its output in an issue related to
- // a buggy extraction function, we will all love you forever.
- func (r Result) PrettyPrintJSON() string {
- pretty, err := json.MarshalIndent(r.Body, "", " ")
- if err != nil {
- panic(err.Error())
- }
- return string(pretty)
- }
- // ErrResult is an internal type to be used by individual resource packages, but
- // its methods will be available on a wide variety of user-facing embedding
- // types.
- //
- // It represents results that only contain a potential error and
- // nothing else. Usually, if the operation executed successfully, the Err field
- // will be nil; otherwise it will be stocked with a relevant error. Use the
- // ExtractErr method
- // to cleanly pull it out.
- type ErrResult struct {
- Result
- }
- // ExtractErr is a function that extracts error information, or nil, from a result.
- func (r ErrResult) ExtractErr() error {
- return r.Err
- }
- /*
- HeaderResult is an internal type to be used by individual resource packages, but
- its methods will be available on a wide variety of user-facing embedding types.
- It represents a result that only contains an error (possibly nil) and an
- http.Header. This is used, for example, by the objectstorage packages in
- openstack, because most of the operations don't return response bodies, but do
- have relevant information in headers.
- */
- type HeaderResult struct {
- Result
- }
- // ExtractInto allows users to provide an object into which `Extract` will
- // extract the http.Header headers of the result.
- func (r HeaderResult) ExtractInto(to interface{}) error {
- if r.Err != nil {
- return r.Err
- }
- tmpHeaderMap := map[string]string{}
- for k, v := range r.Header {
- if len(v) > 0 {
- tmpHeaderMap[k] = v[0]
- }
- }
- b, err := json.Marshal(tmpHeaderMap)
- if err != nil {
- return err
- }
- err = json.Unmarshal(b, to)
- return err
- }
- // RFC3339Milli describes a common time format used by some API responses.
- const RFC3339Milli = "2006-01-02T15:04:05.999999Z"
- type JSONRFC3339Milli time.Time
- func (jt *JSONRFC3339Milli) UnmarshalJSON(data []byte) error {
- b := bytes.NewBuffer(data)
- dec := json.NewDecoder(b)
- var s string
- if err := dec.Decode(&s); err != nil {
- return err
- }
- t, err := time.Parse(RFC3339Milli, s)
- if err != nil {
- return err
- }
- *jt = JSONRFC3339Milli(t)
- return nil
- }
- const RFC3339MilliNoZ = "2006-01-02T15:04:05.999999"
- type JSONRFC3339MilliNoZ time.Time
- func (jt *JSONRFC3339MilliNoZ) UnmarshalJSON(data []byte) error {
- var s string
- if err := json.Unmarshal(data, &s); err != nil {
- return err
- }
- if s == "" {
- return nil
- }
- t, err := time.Parse(RFC3339MilliNoZ, s)
- if err != nil {
- return err
- }
- *jt = JSONRFC3339MilliNoZ(t)
- return nil
- }
- type JSONRFC1123 time.Time
- func (jt *JSONRFC1123) UnmarshalJSON(data []byte) error {
- var s string
- if err := json.Unmarshal(data, &s); err != nil {
- return err
- }
- if s == "" {
- return nil
- }
- t, err := time.Parse(time.RFC1123, s)
- if err != nil {
- return err
- }
- *jt = JSONRFC1123(t)
- return nil
- }
- type JSONUnix time.Time
- func (jt *JSONUnix) UnmarshalJSON(data []byte) error {
- var s string
- if err := json.Unmarshal(data, &s); err != nil {
- return err
- }
- if s == "" {
- return nil
- }
- unix, err := strconv.ParseInt(s, 10, 64)
- if err != nil {
- return err
- }
- t = time.Unix(unix, 0)
- *jt = JSONUnix(t)
- return nil
- }
- // RFC3339NoZ is the time format used in Heat (Orchestration).
- const RFC3339NoZ = "2006-01-02T15:04:05"
- type JSONRFC3339NoZ time.Time
- func (jt *JSONRFC3339NoZ) UnmarshalJSON(data []byte) error {
- var s string
- if err := json.Unmarshal(data, &s); err != nil {
- return err
- }
- if s == "" {
- return nil
- }
- t, err := time.Parse(RFC3339NoZ, s)
- if err != nil {
- return err
- }
- *jt = JSONRFC3339NoZ(t)
- return nil
- }
- // RFC3339ZNoT is the time format used in Zun (Containers Service).
- const RFC3339ZNoT = "2006-01-02 15:04:05-07:00"
- type JSONRFC3339ZNoT time.Time
- func (jt *JSONRFC3339ZNoT) UnmarshalJSON(data []byte) error {
- var s string
- if err := json.Unmarshal(data, &s); err != nil {
- return err
- }
- if s == "" {
- return nil
- }
- t, err := time.Parse(RFC3339ZNoT, s)
- if err != nil {
- return err
- }
- *jt = JSONRFC3339ZNoT(t)
- return nil
- }
- /*
- Link is an internal type to be used in packages of collection resources that are
- paginated in a certain way.
- It's a response substructure common to many paginated collection results that is
- used to point to related pages. Usually, the one we care about is the one with
- Rel field set to "next".
- */
- type Link struct {
- Href string `json:"href"`
- Rel string `json:"rel"`
- }
- /*
- ExtractNextURL is an internal function useful for packages of collection
- resources that are paginated in a certain way.
- It attempts to extract the "next" URL from slice of Link structs, or
- "" if no such URL is present.
- */
- func ExtractNextURL(links []Link) (string, error) {
- var url string
- for _, l := range links {
- if l.Rel == "next" {
- url = l.Href
- }
- }
- if url == "" {
- return "", nil
- }
- return url, nil
- }
|