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