params.go 13 KB

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