github.com/huaweicloud/golangsdk@v0.0.0-20210831081626-d823fe11ceba/params.go (about)

     1  package golangsdk
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"net/url"
     7  	"reflect"
     8  	"strconv"
     9  	"strings"
    10  	"time"
    11  )
    12  
    13  /*
    14  BuildRequestBody builds a map[string]interface from the given `struct`. If
    15  parent is not an empty string, the final map[string]interface returned will
    16  encapsulate the built one. For example:
    17  
    18    disk := 1
    19    createOpts := flavors.CreateOpts{
    20      ID:         "1",
    21      Name:       "m1.tiny",
    22      Disk:       &disk,
    23      RAM:        512,
    24      VCPUs:      1,
    25      RxTxFactor: 1.0,
    26    }
    27  
    28    body, err := golangsdk.BuildRequestBody(createOpts, "flavor")
    29  
    30  The above example can be run as-is, however it is recommended to look at how
    31  BuildRequestBody is used within Gophercloud to more fully understand how it
    32  fits within the request process as a whole rather than use it directly as shown
    33  above.
    34  */
    35  func BuildRequestBody(opts interface{}, parent string) (map[string]interface{}, error) {
    36  	optsValue := reflect.ValueOf(opts)
    37  	if optsValue.Kind() == reflect.Ptr {
    38  		optsValue = optsValue.Elem()
    39  	}
    40  
    41  	optsType := reflect.TypeOf(opts)
    42  	if optsType.Kind() == reflect.Ptr {
    43  		optsType = optsType.Elem()
    44  	}
    45  
    46  	optsMap := make(map[string]interface{})
    47  	if optsValue.Kind() == reflect.Struct {
    48  		//fmt.Printf("optsValue.Kind() is a reflect.Struct: %+v\n", optsValue.Kind())
    49  		for i := 0; i < optsValue.NumField(); i++ {
    50  			v := optsValue.Field(i)
    51  			f := optsType.Field(i)
    52  
    53  			if f.Name != strings.Title(f.Name) {
    54  				//fmt.Printf("Skipping field: %s...\n", f.Name)
    55  				continue
    56  			}
    57  
    58  			//fmt.Printf("Starting on field: %s...\n", f.Name)
    59  
    60  			zero := isZero(v)
    61  			//fmt.Printf("v is zero?: %v\n", zero)
    62  
    63  			// if the field has a required tag that's set to "true"
    64  			if requiredTag := f.Tag.Get("required"); requiredTag == "true" {
    65  				//fmt.Printf("Checking required field [%s]:\n\tv: %+v\n\tisZero:%v\n", f.Name, v.Interface(), zero)
    66  				// if the field's value is zero, return a missing-argument error
    67  				if zero {
    68  					// if the field has a 'required' tag, it can't have a zero-value
    69  					err := ErrMissingInput{}
    70  					err.Argument = f.Name
    71  					return nil, err
    72  				}
    73  			}
    74  
    75  			if xorTag := f.Tag.Get("xor"); xorTag != "" {
    76  				//fmt.Printf("Checking `xor` tag for field [%s] with value %+v:\n\txorTag: %s\n", f.Name, v, xorTag)
    77  				xorField := optsValue.FieldByName(xorTag)
    78  				var xorFieldIsZero bool
    79  				if reflect.ValueOf(xorField.Interface()) == reflect.Zero(xorField.Type()) {
    80  					xorFieldIsZero = true
    81  				} else {
    82  					if xorField.Kind() == reflect.Ptr {
    83  						xorField = xorField.Elem()
    84  					}
    85  					xorFieldIsZero = isZero(xorField)
    86  				}
    87  				if !(zero != xorFieldIsZero) {
    88  					err := ErrMissingInput{}
    89  					err.Argument = fmt.Sprintf("%s/%s", f.Name, xorTag)
    90  					err.Info = fmt.Sprintf("Exactly one of %s and %s must be provided", f.Name, xorTag)
    91  					return nil, err
    92  				}
    93  			}
    94  
    95  			if orTag := f.Tag.Get("or"); orTag != "" {
    96  				//fmt.Printf("Checking `or` tag for field with:\n\tname: %+v\n\torTag:%s\n", f.Name, orTag)
    97  				//fmt.Printf("field is zero?: %v\n", zero)
    98  				if zero {
    99  					orField := optsValue.FieldByName(orTag)
   100  					var orFieldIsZero bool
   101  					if reflect.ValueOf(orField.Interface()) == reflect.Zero(orField.Type()) {
   102  						orFieldIsZero = true
   103  					} else {
   104  						if orField.Kind() == reflect.Ptr {
   105  							orField = orField.Elem()
   106  						}
   107  						orFieldIsZero = isZero(orField)
   108  					}
   109  					if orFieldIsZero {
   110  						err := ErrMissingInput{}
   111  						err.Argument = fmt.Sprintf("%s/%s", f.Name, orTag)
   112  						err.Info = fmt.Sprintf("At least one of %s and %s must be provided", f.Name, orTag)
   113  						return nil, err
   114  					}
   115  				}
   116  			}
   117  
   118  			jsonTag := f.Tag.Get("json")
   119  			if jsonTag == "-" {
   120  				continue
   121  			}
   122  
   123  			if v.Kind() == reflect.Slice || (v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Slice) {
   124  				sliceValue := v
   125  				if sliceValue.Kind() == reflect.Ptr {
   126  					sliceValue = sliceValue.Elem()
   127  				}
   128  
   129  				for i := 0; i < sliceValue.Len(); i++ {
   130  					element := sliceValue.Index(i)
   131  					if element.Kind() == reflect.Struct || (element.Kind() == reflect.Ptr && element.Elem().Kind() == reflect.Struct) {
   132  						_, err := BuildRequestBody(element.Interface(), "")
   133  						if err != nil {
   134  							return nil, err
   135  						}
   136  					}
   137  				}
   138  			}
   139  			if v.Kind() == reflect.Struct || (v.Kind() == reflect.Ptr && v.Elem().Kind() == reflect.Struct) {
   140  				if zero {
   141  					//fmt.Printf("value before change: %+v\n", optsValue.Field(i))
   142  					if jsonTag != "" {
   143  						jsonTagPieces := strings.Split(jsonTag, ",")
   144  						if len(jsonTagPieces) > 1 && jsonTagPieces[1] == "omitempty" {
   145  							if v.CanSet() {
   146  								if !v.IsNil() {
   147  									if v.Kind() == reflect.Ptr {
   148  										v.Set(reflect.Zero(v.Type()))
   149  									}
   150  								}
   151  								//fmt.Printf("value after change: %+v\n", optsValue.Field(i))
   152  							}
   153  						}
   154  					}
   155  					continue
   156  				}
   157  
   158  				//fmt.Printf("Calling BuildRequestBody with:\n\tv: %+v\n\tf.Name:%s\n", v.Interface(), f.Name)
   159  				_, err := BuildRequestBody(v.Interface(), f.Name)
   160  				if err != nil {
   161  					return nil, err
   162  				}
   163  			}
   164  		}
   165  
   166  		//fmt.Printf("opts: %+v \n", opts)
   167  
   168  		b, err := json.Marshal(opts)
   169  		if err != nil {
   170  			return nil, err
   171  		}
   172  
   173  		//fmt.Printf("string(b): %s\n", string(b))
   174  
   175  		err = json.Unmarshal(b, &optsMap)
   176  		if err != nil {
   177  			return nil, err
   178  		}
   179  
   180  		//fmt.Printf("optsMap: %+v\n", optsMap)
   181  
   182  		if parent != "" {
   183  			optsMap = map[string]interface{}{parent: optsMap}
   184  		}
   185  		//fmt.Printf("optsMap after parent added: %+v\n", optsMap)
   186  		return optsMap, nil
   187  	}
   188  	// Return an error if the underlying type of 'opts' isn't a struct.
   189  	return nil, fmt.Errorf("Options type is not a struct.")
   190  }
   191  
   192  // EnabledState is a convenience type, mostly used in Create and Update
   193  // operations. Because the zero value of a bool is FALSE, we need to use a
   194  // pointer instead to indicate zero-ness.
   195  type EnabledState *bool
   196  
   197  // Convenience vars for EnabledState values.
   198  var (
   199  	iTrue  = true
   200  	iFalse = false
   201  
   202  	Enabled  EnabledState = &iTrue
   203  	Disabled EnabledState = &iFalse
   204  )
   205  
   206  // IPVersion is a type for the possible IP address versions. Valid instances
   207  // are IPv4 and IPv6
   208  type IPVersion int
   209  
   210  const (
   211  	// IPv4 is used for IP version 4 addresses
   212  	IPv4 IPVersion = 4
   213  	// IPv6 is used for IP version 6 addresses
   214  	IPv6 IPVersion = 6
   215  )
   216  
   217  // IntToPointer is a function for converting integers into integer pointers.
   218  // This is useful when passing in options to operations.
   219  func IntToPointer(i int) *int {
   220  	return &i
   221  }
   222  
   223  /*
   224  MaybeString is an internal function to be used by request methods in individual
   225  resource packages.
   226  
   227  It takes a string that might be a zero value and returns either a pointer to its
   228  address or nil. This is useful for allowing users to conveniently omit values
   229  from an options struct by leaving them zeroed, but still pass nil to the JSON
   230  serializer so they'll be omitted from the request body.
   231  */
   232  func MaybeString(original string) *string {
   233  	if original != "" {
   234  		return &original
   235  	}
   236  	return nil
   237  }
   238  
   239  /*
   240  MaybeInt is an internal function to be used by request methods in individual
   241  resource packages.
   242  
   243  Like MaybeString, it accepts an int that may or may not be a zero value, and
   244  returns either a pointer to its address or nil. It's intended to hint that the
   245  JSON serializer should omit its field.
   246  */
   247  func MaybeInt(original int) *int {
   248  	if original != 0 {
   249  		return &original
   250  	}
   251  	return nil
   252  }
   253  
   254  /*
   255  func isUnderlyingStructZero(v reflect.Value) bool {
   256  	switch v.Kind() {
   257  	case reflect.Ptr:
   258  		return isUnderlyingStructZero(v.Elem())
   259  	default:
   260  		return isZero(v)
   261  	}
   262  }
   263  */
   264  
   265  var t time.Time
   266  
   267  func isZero(v reflect.Value) bool {
   268  	//fmt.Printf("\n\nchecking isZero for value: %+v\n", v)
   269  	switch v.Kind() {
   270  	case reflect.Ptr:
   271  		if v.IsNil() {
   272  			return true
   273  		}
   274  		return false
   275  	case reflect.Func, reflect.Map, reflect.Slice:
   276  		return v.IsNil()
   277  	case reflect.Array:
   278  		z := true
   279  		for i := 0; i < v.Len(); i++ {
   280  			z = z && isZero(v.Index(i))
   281  		}
   282  		return z
   283  	case reflect.Struct:
   284  		if v.Type() == reflect.TypeOf(t) {
   285  			if v.Interface().(time.Time).IsZero() {
   286  				return true
   287  			}
   288  			return false
   289  		}
   290  		z := true
   291  		for i := 0; i < v.NumField(); i++ {
   292  			z = z && isZero(v.Field(i))
   293  		}
   294  		return z
   295  	}
   296  	// Compare other types directly:
   297  	z := reflect.Zero(v.Type())
   298  	//fmt.Printf("zero type for value: %+v\n\n\n", z)
   299  	return v.Interface() == z.Interface()
   300  }
   301  
   302  /*
   303  BuildQueryString is an internal function to be used by request methods in
   304  individual resource packages.
   305  
   306  It accepts a tagged structure and expands it into a URL struct. Field names are
   307  converted into query parameters based on a "q" tag. For example:
   308  
   309  	type struct Something {
   310  	   Bar string `q:"x_bar"`
   311  	   Baz int    `q:"lorem_ipsum"`
   312  	}
   313  
   314  	instance := Something{
   315  	   Bar: "AAA",
   316  	   Baz: "BBB",
   317  	}
   318  
   319  will be converted into "?x_bar=AAA&lorem_ipsum=BBB".
   320  
   321  The struct's fields may be strings, integers, or boolean values. Fields left at
   322  their type's zero value will be omitted from the query.
   323  */
   324  func BuildQueryString(opts interface{}) (*url.URL, error) {
   325  	optsValue := reflect.ValueOf(opts)
   326  	if optsValue.Kind() == reflect.Ptr {
   327  		optsValue = optsValue.Elem()
   328  	}
   329  
   330  	optsType := reflect.TypeOf(opts)
   331  	if optsType.Kind() == reflect.Ptr {
   332  		optsType = optsType.Elem()
   333  	}
   334  
   335  	params := url.Values{}
   336  
   337  	if optsValue.Kind() == reflect.Struct {
   338  		for i := 0; i < optsValue.NumField(); i++ {
   339  			v := optsValue.Field(i)
   340  			f := optsType.Field(i)
   341  			qTag := f.Tag.Get("q")
   342  
   343  			// if the field has a 'q' tag, it goes in the query string
   344  			if qTag != "" {
   345  				tags := strings.Split(qTag, ",")
   346  
   347  				// if the field is set, add it to the slice of query pieces
   348  				if !isZero(v) {
   349  				loop:
   350  					switch v.Kind() {
   351  					case reflect.Ptr:
   352  						v = v.Elem()
   353  						goto loop
   354  					case reflect.String:
   355  						params.Add(tags[0], v.String())
   356  					case reflect.Int:
   357  						params.Add(tags[0], strconv.FormatInt(v.Int(), 10))
   358  					case reflect.Bool:
   359  						params.Add(tags[0], strconv.FormatBool(v.Bool()))
   360  					case reflect.Slice:
   361  						switch v.Type().Elem() {
   362  						case reflect.TypeOf(0):
   363  							for i := 0; i < v.Len(); i++ {
   364  								params.Add(tags[0], strconv.FormatInt(v.Index(i).Int(), 10))
   365  							}
   366  						default:
   367  							for i := 0; i < v.Len(); i++ {
   368  								params.Add(tags[0], v.Index(i).String())
   369  							}
   370  						}
   371  					case reflect.Map:
   372  						if v.Type().Key().Kind() == reflect.String && v.Type().Elem().Kind() == reflect.String {
   373  							var s []string
   374  							for _, k := range v.MapKeys() {
   375  								value := v.MapIndex(k).String()
   376  								s = append(s, fmt.Sprintf("'%s':'%s'", k.String(), value))
   377  							}
   378  							params.Add(tags[0], fmt.Sprintf("{%s}", strings.Join(s, ", ")))
   379  						}
   380  					}
   381  				} else {
   382  					// if the field has a 'required' tag, it can't have a zero-value
   383  					if requiredTag := f.Tag.Get("required"); requiredTag == "true" {
   384  						return &url.URL{}, fmt.Errorf("Required query parameter [%s] not set.", f.Name)
   385  					}
   386  				}
   387  			}
   388  		}
   389  
   390  		return &url.URL{RawQuery: params.Encode()}, nil
   391  	}
   392  	// Return an error if the underlying type of 'opts' isn't a struct.
   393  	return nil, fmt.Errorf("Options type is not a struct.")
   394  }
   395  
   396  /*
   397  BuildHeaders is an internal function to be used by request methods in
   398  individual resource packages.
   399  
   400  It accepts an arbitrary tagged structure and produces a string map that's
   401  suitable for use as the HTTP headers of an outgoing request. Field names are
   402  mapped to header names based in "h" tags.
   403  
   404    type struct Something {
   405      Bar string `h:"x_bar"`
   406      Baz int    `h:"lorem_ipsum"`
   407    }
   408  
   409    instance := Something{
   410      Bar: "AAA",
   411      Baz: "BBB",
   412    }
   413  
   414  will be converted into:
   415  
   416    map[string]string{
   417      "x_bar": "AAA",
   418      "lorem_ipsum": "BBB",
   419    }
   420  
   421  Untagged fields and fields left at their zero values are skipped. Integers,
   422  booleans and string values are supported.
   423  */
   424  func BuildHeaders(opts interface{}) (map[string]string, error) {
   425  	optsValue := reflect.ValueOf(opts)
   426  	if optsValue.Kind() == reflect.Ptr {
   427  		optsValue = optsValue.Elem()
   428  	}
   429  
   430  	optsType := reflect.TypeOf(opts)
   431  	if optsType.Kind() == reflect.Ptr {
   432  		optsType = optsType.Elem()
   433  	}
   434  
   435  	optsMap := make(map[string]string)
   436  	if optsValue.Kind() == reflect.Struct {
   437  		for i := 0; i < optsValue.NumField(); i++ {
   438  			v := optsValue.Field(i)
   439  			f := optsType.Field(i)
   440  			hTag := f.Tag.Get("h")
   441  
   442  			// if the field has a 'h' tag, it goes in the header
   443  			if hTag != "" {
   444  				tags := strings.Split(hTag, ",")
   445  
   446  				// if the field is set, add it to the slice of query pieces
   447  				if !isZero(v) {
   448  					switch v.Kind() {
   449  					case reflect.String:
   450  						optsMap[tags[0]] = v.String()
   451  					case reflect.Int:
   452  						optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10)
   453  					case reflect.Int64:
   454  						optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10)
   455  					case reflect.Bool:
   456  						optsMap[tags[0]] = strconv.FormatBool(v.Bool())
   457  					}
   458  				} else {
   459  					// if the field has a 'required' tag, it can't have a zero-value
   460  					if requiredTag := f.Tag.Get("required"); requiredTag == "true" {
   461  						return optsMap, fmt.Errorf("Required header [%s] not set.", f.Name)
   462  					}
   463  				}
   464  			}
   465  
   466  		}
   467  		return optsMap, nil
   468  	}
   469  	// Return an error if the underlying type of 'opts' isn't a struct.
   470  	return optsMap, fmt.Errorf("Options type is not a struct.")
   471  }
   472  
   473  // IDSliceToQueryString takes a slice of elements and converts them into a query
   474  // string. For example, if name=foo and slice=[]int{20, 40, 60}, then the
   475  // result would be `?name=20&name=40&name=60'
   476  func IDSliceToQueryString(name string, ids []int) string {
   477  	str := ""
   478  	for k, v := range ids {
   479  		if k == 0 {
   480  			str += "?"
   481  		} else {
   482  			str += "&"
   483  		}
   484  		str += fmt.Sprintf("%s=%s", name, strconv.Itoa(v))
   485  	}
   486  	return str
   487  }
   488  
   489  // IntWithinRange returns TRUE if an integer falls within a defined range, and
   490  // FALSE if not.
   491  func IntWithinRange(val, min, max int) bool {
   492  	return val > min && val < max
   493  }