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  }