github.com/gophercloud/gophercloud@v1.14.1/params.go (about)

     1  package gophercloud
     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 := gophercloud.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, slices, or boolean values. Fields
   322  left at their type's zero value will be omitted from the query.
   323  
   324  Slice are handled in one of two ways:
   325  
   326  	type struct Something {
   327  	   Bar []string `q:"bar"` // E.g. ?bar=1&bar=2
   328  	   Baz []int    `q:"baz" format="comma-separated"` // E.g. ?baz=1,2
   329  	}
   330  */
   331  func BuildQueryString(opts interface{}) (*url.URL, error) {
   332  	optsValue := reflect.ValueOf(opts)
   333  	if optsValue.Kind() == reflect.Ptr {
   334  		optsValue = optsValue.Elem()
   335  	}
   336  
   337  	optsType := reflect.TypeOf(opts)
   338  	if optsType.Kind() == reflect.Ptr {
   339  		optsType = optsType.Elem()
   340  	}
   341  
   342  	params := url.Values{}
   343  
   344  	if optsValue.Kind() == reflect.Struct {
   345  		for i := 0; i < optsValue.NumField(); i++ {
   346  			v := optsValue.Field(i)
   347  			f := optsType.Field(i)
   348  			qTag := f.Tag.Get("q")
   349  
   350  			// if the field has a 'q' tag, it goes in the query string
   351  			if qTag != "" {
   352  				tags := strings.Split(qTag, ",")
   353  
   354  				// if the field is set, add it to the slice of query pieces
   355  				if !isZero(v) {
   356  				loop:
   357  					switch v.Kind() {
   358  					case reflect.Ptr:
   359  						v = v.Elem()
   360  						goto loop
   361  					case reflect.String:
   362  						params.Add(tags[0], v.String())
   363  					case reflect.Int:
   364  						params.Add(tags[0], strconv.FormatInt(v.Int(), 10))
   365  					case reflect.Bool:
   366  						params.Add(tags[0], strconv.FormatBool(v.Bool()))
   367  					case reflect.Slice:
   368  						var values []string
   369  						switch v.Type().Elem() {
   370  						case reflect.TypeOf(0):
   371  							for i := 0; i < v.Len(); i++ {
   372  								values = append(values, strconv.FormatInt(v.Index(i).Int(), 10))
   373  							}
   374  						default:
   375  							for i := 0; i < v.Len(); i++ {
   376  								values = append(values, v.Index(i).String())
   377  							}
   378  						}
   379  						if sliceFormat := f.Tag.Get("format"); sliceFormat == "comma-separated" {
   380  							params.Add(tags[0], strings.Join(values, ","))
   381  						} else {
   382  							params[tags[0]] = append(params[tags[0]], values...)
   383  						}
   384  					case reflect.Map:
   385  						if v.Type().Key().Kind() == reflect.String && v.Type().Elem().Kind() == reflect.String {
   386  							var s []string
   387  							for _, k := range v.MapKeys() {
   388  								value := v.MapIndex(k).String()
   389  								s = append(s, fmt.Sprintf("'%s':'%s'", k.String(), value))
   390  							}
   391  							params.Add(tags[0], fmt.Sprintf("{%s}", strings.Join(s, ", ")))
   392  						}
   393  					}
   394  				} else {
   395  					// if the field has a 'required' tag, it can't have a zero-value
   396  					if requiredTag := f.Tag.Get("required"); requiredTag == "true" {
   397  						return &url.URL{}, fmt.Errorf("Required query parameter [%s] not set.", f.Name)
   398  					}
   399  				}
   400  			}
   401  		}
   402  
   403  		return &url.URL{RawQuery: params.Encode()}, nil
   404  	}
   405  	// Return an error if the underlying type of 'opts' isn't a struct.
   406  	return nil, fmt.Errorf("Options type is not a struct.")
   407  }
   408  
   409  /*
   410  BuildHeaders is an internal function to be used by request methods in
   411  individual resource packages.
   412  
   413  It accepts an arbitrary tagged structure and produces a string map that's
   414  suitable for use as the HTTP headers of an outgoing request. Field names are
   415  mapped to header names based in "h" tags.
   416  
   417  	type struct Something {
   418  	  Bar string `h:"x_bar"`
   419  	  Baz int    `h:"lorem_ipsum"`
   420  	}
   421  
   422  	instance := Something{
   423  	  Bar: "AAA",
   424  	  Baz: "BBB",
   425  	}
   426  
   427  will be converted into:
   428  
   429  	map[string]string{
   430  	  "x_bar": "AAA",
   431  	  "lorem_ipsum": "BBB",
   432  	}
   433  
   434  Untagged fields and fields left at their zero values are skipped. Integers,
   435  booleans and string values are supported.
   436  */
   437  func BuildHeaders(opts interface{}) (map[string]string, error) {
   438  	optsValue := reflect.ValueOf(opts)
   439  	if optsValue.Kind() == reflect.Ptr {
   440  		optsValue = optsValue.Elem()
   441  	}
   442  
   443  	optsType := reflect.TypeOf(opts)
   444  	if optsType.Kind() == reflect.Ptr {
   445  		optsType = optsType.Elem()
   446  	}
   447  
   448  	optsMap := make(map[string]string)
   449  	if optsValue.Kind() == reflect.Struct {
   450  		for i := 0; i < optsValue.NumField(); i++ {
   451  			v := optsValue.Field(i)
   452  			f := optsType.Field(i)
   453  			hTag := f.Tag.Get("h")
   454  
   455  			// if the field has a 'h' tag, it goes in the header
   456  			if hTag != "" {
   457  				tags := strings.Split(hTag, ",")
   458  
   459  				// if the field is set, add it to the slice of query pieces
   460  				if !isZero(v) {
   461  					if v.Kind() == reflect.Ptr {
   462  						v = v.Elem()
   463  					}
   464  					switch v.Kind() {
   465  					case reflect.String:
   466  						optsMap[tags[0]] = v.String()
   467  					case reflect.Int:
   468  						optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10)
   469  					case reflect.Int64:
   470  						optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10)
   471  					case reflect.Bool:
   472  						optsMap[tags[0]] = strconv.FormatBool(v.Bool())
   473  					}
   474  				} else {
   475  					// if the field has a 'required' tag, it can't have a zero-value
   476  					if requiredTag := f.Tag.Get("required"); requiredTag == "true" {
   477  						return optsMap, fmt.Errorf("Required header [%s] not set.", f.Name)
   478  					}
   479  				}
   480  			}
   481  
   482  		}
   483  		return optsMap, nil
   484  	}
   485  	// Return an error if the underlying type of 'opts' isn't a struct.
   486  	return optsMap, fmt.Errorf("Options type is not a struct.")
   487  }
   488  
   489  // IDSliceToQueryString takes a slice of elements and converts them into a query
   490  // string. For example, if name=foo and slice=[]int{20, 40, 60}, then the
   491  // result would be `?name=20&name=40&name=60'
   492  func IDSliceToQueryString(name string, ids []int) string {
   493  	str := ""
   494  	for k, v := range ids {
   495  		if k == 0 {
   496  			str += "?"
   497  		} else {
   498  			str += "&"
   499  		}
   500  		str += fmt.Sprintf("%s=%s", name, strconv.Itoa(v))
   501  	}
   502  	return str
   503  }
   504  
   505  // IntWithinRange returns TRUE if an integer falls within a defined range, and
   506  // FALSE if not.
   507  func IntWithinRange(val, min, max int) bool {
   508  	return val > min && val < max
   509  }