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  }