package gophercloud import ( "fmt" "net/url" "reflect" "strconv" "strings" "time" ) // EnabledState is a convenience type, mostly used in Create and Update // operations. Because the zero value of a bool is FALSE, we need to use a // pointer instead to indicate zero-ness. type EnabledState *bool // Convenience vars for EnabledState values. var ( iTrue = true iFalse = false Enabled EnabledState = &iTrue Disabled EnabledState = &iFalse ) // IntToPointer is a function for converting integers into integer pointers. // This is useful when passing in options to operations. func IntToPointer(i int) *int { return &i } /* MaybeString is an internal function to be used by request methods in individual resource packages. It takes a string that might be a zero value and returns either a pointer to its address or nil. This is useful for allowing users to conveniently omit values from an options struct by leaving them zeroed, but still pass nil to the JSON serializer so they'll be omitted from the request body. */ func MaybeString(original string) *string { if original != "" { return &original } return nil } /* MaybeInt is an internal function to be used by request methods in individual resource packages. Like MaybeString, it accepts an int that may or may not be a zero value, and returns either a pointer to its address or nil. It's intended to hint that the JSON serializer should omit its field. */ func MaybeInt(original int) *int { if original != 0 { return &original } return nil } var t time.Time func isZero(v reflect.Value) bool { switch v.Kind() { case reflect.Func, reflect.Map, reflect.Slice: return v.IsNil() case reflect.Array: z := true for i := 0; i < v.Len(); i++ { z = z && isZero(v.Index(i)) } return z case reflect.Struct: if v.Type() == reflect.TypeOf(t) { if v.Interface().(time.Time).IsZero() { return true } return false } z := true for i := 0; i < v.NumField(); i++ { z = z && isZero(v.Field(i)) } return z } // Compare other types directly: z := reflect.Zero(v.Type()) return v.Interface() == z.Interface() } /* BuildQueryString is an internal function to be used by request methods in individual resource packages. It accepts a tagged structure and expands it into a URL struct. Field names are converted into query parameters based on a "q" tag. For example: type struct Something { Bar string `q:"x_bar"` Baz int `q:"lorem_ipsum"` } instance := Something{ Bar: "AAA", Baz: "BBB", } will be converted into "?x_bar=AAA&lorem_ipsum=BBB". The struct's fields may be strings, integers, or boolean values. Fields left at their type's zero value will be omitted from the query. */ func BuildQueryString(opts interface{}) (*url.URL, error) { optsValue := reflect.ValueOf(opts) if optsValue.Kind() == reflect.Ptr { optsValue = optsValue.Elem() } optsType := reflect.TypeOf(opts) if optsType.Kind() == reflect.Ptr { optsType = optsType.Elem() } params := url.Values{} if optsValue.Kind() == reflect.Struct { for i := 0; i < optsValue.NumField(); i++ { v := optsValue.Field(i) f := optsType.Field(i) qTag := f.Tag.Get("q") // if the field has a 'q' tag, it goes in the query string if qTag != "" { tags := strings.Split(qTag, ",") // if the field is set, add it to the slice of query pieces if !isZero(v) { switch v.Kind() { case reflect.String: params.Add(tags[0], v.String()) case reflect.Int: params.Add(tags[0], strconv.FormatInt(v.Int(), 10)) case reflect.Bool: params.Add(tags[0], strconv.FormatBool(v.Bool())) case reflect.Slice: switch v.Type().Elem() { case reflect.TypeOf(0): for i := 0; i < v.Len(); i++ { params.Add(tags[0], strconv.FormatInt(v.Index(i).Int(), 10)) } default: for i := 0; i < v.Len(); i++ { params.Add(tags[0], v.Index(i).String()) } } } } else { // Otherwise, the field is not set. if len(tags) == 2 && tags[1] == "required" { // And the field is required. Return an error. return nil, fmt.Errorf("Required query parameter [%s] not set.", f.Name) } } } } return &url.URL{RawQuery: params.Encode()}, nil } // Return an error if the underlying type of 'opts' isn't a struct. return nil, fmt.Errorf("Options type is not a struct.") } /* BuildHeaders is an internal function to be used by request methods in individual resource packages. It accepts an arbitrary tagged structure and produces a string map that's suitable for use as the HTTP headers of an outgoing request. Field names are mapped to header names based in "h" tags. type struct Something { Bar string `h:"x_bar"` Baz int `h:"lorem_ipsum"` } instance := Something{ Bar: "AAA", Baz: "BBB", } will be converted into: map[string]string{ "x_bar": "AAA", "lorem_ipsum": "BBB", } Untagged fields and fields left at their zero values are skipped. Integers, booleans and string values are supported. */ func BuildHeaders(opts interface{}) (map[string]string, error) { optsValue := reflect.ValueOf(opts) if optsValue.Kind() == reflect.Ptr { optsValue = optsValue.Elem() } optsType := reflect.TypeOf(opts) if optsType.Kind() == reflect.Ptr { optsType = optsType.Elem() } optsMap := make(map[string]string) if optsValue.Kind() == reflect.Struct { for i := 0; i < optsValue.NumField(); i++ { v := optsValue.Field(i) f := optsType.Field(i) hTag := f.Tag.Get("h") // if the field has a 'h' tag, it goes in the header if hTag != "" { tags := strings.Split(hTag, ",") // if the field is set, add it to the slice of query pieces if !isZero(v) { switch v.Kind() { case reflect.String: optsMap[tags[0]] = v.String() case reflect.Int: optsMap[tags[0]] = strconv.FormatInt(v.Int(), 10) case reflect.Bool: optsMap[tags[0]] = strconv.FormatBool(v.Bool()) } } else { // Otherwise, the field is not set. if len(tags) == 2 && tags[1] == "required" { // And the field is required. Return an error. return optsMap, fmt.Errorf("Required header not set.") } } } } return optsMap, nil } // Return an error if the underlying type of 'opts' isn't a struct. return optsMap, fmt.Errorf("Options type is not a struct.") } // IDSliceToQueryString takes a slice of elements and converts them into a query // string. For example, if name=foo and slice=[]int{20, 40, 60}, then the // result would be `?name=20&name=40&name=60' func IDSliceToQueryString(name string, ids []int) string { str := "" for k, v := range ids { if k == 0 { str += "?" } else { str += "&" } str += fmt.Sprintf("%s=%s", name, strconv.Itoa(v)) } return str } // IntWithinRange returns TRUE if an integer falls within a defined range, and // FALSE if not. func IntWithinRange(val, min, max int) bool { return val > min && val < max }