1package gophercloud
2
3import (
4	"encoding/json"
5	"fmt"
6	"net/url"
7	"reflect"
8	"strconv"
9	"strings"
10	"time"
11)
12
13/*
14BuildRequestBody builds a map[string]interface from the given `struct`. If
15parent is not an empty string, the final map[string]interface returned will
16encapsulate 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
30The above example can be run as-is, however it is recommended to look at how
31BuildRequestBody is used within Gophercloud to more fully understand how it
32fits within the request process as a whole rather than use it directly as shown
33above.
34*/
35func 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.
195type EnabledState *bool
196
197// Convenience vars for EnabledState values.
198var (
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
208type IPVersion int
209
210const (
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.
219func IntToPointer(i int) *int {
220	return &i
221}
222
223/*
224MaybeString is an internal function to be used by request methods in individual
225resource packages.
226
227It takes a string that might be a zero value and returns either a pointer to its
228address or nil. This is useful for allowing users to conveniently omit values
229from an options struct by leaving them zeroed, but still pass nil to the JSON
230serializer so they'll be omitted from the request body.
231*/
232func MaybeString(original string) *string {
233	if original != "" {
234		return &original
235	}
236	return nil
237}
238
239/*
240MaybeInt is an internal function to be used by request methods in individual
241resource packages.
242
243Like MaybeString, it accepts an int that may or may not be a zero value, and
244returns either a pointer to its address or nil. It's intended to hint that the
245JSON serializer should omit its field.
246*/
247func MaybeInt(original int) *int {
248	if original != 0 {
249		return &original
250	}
251	return nil
252}
253
254/*
255func 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
265var t time.Time
266
267func 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/*
303BuildQueryString is an internal function to be used by request methods in
304individual resource packages.
305
306It accepts a tagged structure and expands it into a URL struct. Field names are
307converted 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
319will be converted into "?x_bar=AAA&lorem_ipsum=BBB".
320
321The struct's fields may be strings, integers, or boolean values. Fields left at
322their type's zero value will be omitted from the query.
323*/
324func 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/*
397BuildHeaders is an internal function to be used by request methods in
398individual resource packages.
399
400It accepts an arbitrary tagged structure and produces a string map that's
401suitable for use as the HTTP headers of an outgoing request. Field names are
402mapped 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
414will be converted into:
415
416  map[string]string{
417    "x_bar": "AAA",
418    "lorem_ipsum": "BBB",
419  }
420
421Untagged fields and fields left at their zero values are skipped. Integers,
422booleans and string values are supported.
423*/
424func 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.Bool:
454						optsMap[tags[0]] = strconv.FormatBool(v.Bool())
455					}
456				} else {
457					// if the field has a 'required' tag, it can't have a zero-value
458					if requiredTag := f.Tag.Get("required"); requiredTag == "true" {
459						return optsMap, fmt.Errorf("Required header [%s] not set.", f.Name)
460					}
461				}
462			}
463
464		}
465		return optsMap, nil
466	}
467	// Return an error if the underlying type of 'opts' isn't a struct.
468	return optsMap, fmt.Errorf("Options type is not a struct.")
469}
470
471// IDSliceToQueryString takes a slice of elements and converts them into a query
472// string. For example, if name=foo and slice=[]int{20, 40, 60}, then the
473// result would be `?name=20&name=40&name=60'
474func IDSliceToQueryString(name string, ids []int) string {
475	str := ""
476	for k, v := range ids {
477		if k == 0 {
478			str += "?"
479		} else {
480			str += "&"
481		}
482		str += fmt.Sprintf("%s=%s", name, strconv.Itoa(v))
483	}
484	return str
485}
486
487// IntWithinRange returns TRUE if an integer falls within a defined range, and
488// FALSE if not.
489func IntWithinRange(val, min, max int) bool {
490	return val > min && val < max
491}
492