queryutil.go 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223
  1. package queryutil
  2. import (
  3. "encoding/base64"
  4. "fmt"
  5. "net/url"
  6. "reflect"
  7. "sort"
  8. "strconv"
  9. "strings"
  10. "time"
  11. )
  12. // Parse parses an object i and fills a url.Values object. The isEC2 flag
  13. // indicates if this is the EC2 Query sub-protocol.
  14. func Parse(body url.Values, i interface{}, isEC2 bool) error {
  15. q := queryParser{isEC2: isEC2}
  16. return q.parseValue(body, reflect.ValueOf(i), "", "")
  17. }
  18. func elemOf(value reflect.Value) reflect.Value {
  19. for value.Kind() == reflect.Ptr {
  20. value = value.Elem()
  21. }
  22. return value
  23. }
  24. type queryParser struct {
  25. isEC2 bool
  26. }
  27. func (q *queryParser) parseValue(v url.Values, value reflect.Value, prefix string, tag reflect.StructTag) error {
  28. value = elemOf(value)
  29. // no need to handle zero values
  30. if !value.IsValid() {
  31. return nil
  32. }
  33. t := tag.Get("type")
  34. if t == "" {
  35. switch value.Kind() {
  36. case reflect.Struct:
  37. t = "structure"
  38. case reflect.Slice:
  39. t = "list"
  40. case reflect.Map:
  41. t = "map"
  42. }
  43. }
  44. switch t {
  45. case "structure":
  46. return q.parseStruct(v, value, prefix)
  47. case "list":
  48. return q.parseList(v, value, prefix, tag)
  49. case "map":
  50. return q.parseMap(v, value, prefix, tag)
  51. default:
  52. return q.parseScalar(v, value, prefix, tag)
  53. }
  54. }
  55. func (q *queryParser) parseStruct(v url.Values, value reflect.Value, prefix string) error {
  56. if !value.IsValid() {
  57. return nil
  58. }
  59. t := value.Type()
  60. for i := 0; i < value.NumField(); i++ {
  61. if c := t.Field(i).Name[0:1]; strings.ToLower(c) == c {
  62. continue // ignore unexported fields
  63. }
  64. value := elemOf(value.Field(i))
  65. field := t.Field(i)
  66. var name string
  67. if q.isEC2 {
  68. name = field.Tag.Get("queryName")
  69. }
  70. if name == "" {
  71. if field.Tag.Get("flattened") != "" && field.Tag.Get("locationNameList") != "" {
  72. name = field.Tag.Get("locationNameList")
  73. } else if locName := field.Tag.Get("locationName"); locName != "" {
  74. name = locName
  75. }
  76. if name != "" && q.isEC2 {
  77. name = strings.ToUpper(name[0:1]) + name[1:]
  78. }
  79. }
  80. if name == "" {
  81. name = field.Name
  82. }
  83. if prefix != "" {
  84. name = prefix + "." + name
  85. }
  86. if err := q.parseValue(v, value, name, field.Tag); err != nil {
  87. return err
  88. }
  89. }
  90. return nil
  91. }
  92. func (q *queryParser) parseList(v url.Values, value reflect.Value, prefix string, tag reflect.StructTag) error {
  93. // If it's empty, generate an empty value
  94. if !value.IsNil() && value.Len() == 0 {
  95. v.Set(prefix, "")
  96. return nil
  97. }
  98. // check for unflattened list member
  99. if !q.isEC2 && tag.Get("flattened") == "" {
  100. prefix += ".member"
  101. }
  102. for i := 0; i < value.Len(); i++ {
  103. slicePrefix := prefix
  104. if slicePrefix == "" {
  105. slicePrefix = strconv.Itoa(i + 1)
  106. } else {
  107. slicePrefix = slicePrefix + "." + strconv.Itoa(i+1)
  108. }
  109. if err := q.parseValue(v, value.Index(i), slicePrefix, ""); err != nil {
  110. return err
  111. }
  112. }
  113. return nil
  114. }
  115. func (q *queryParser) parseMap(v url.Values, value reflect.Value, prefix string, tag reflect.StructTag) error {
  116. // If it's empty, generate an empty value
  117. if !value.IsNil() && value.Len() == 0 {
  118. v.Set(prefix, "")
  119. return nil
  120. }
  121. // check for unflattened list member
  122. if !q.isEC2 && tag.Get("flattened") == "" {
  123. prefix += ".entry"
  124. }
  125. // sort keys for improved serialization consistency.
  126. // this is not strictly necessary for protocol support.
  127. mapKeyValues := value.MapKeys()
  128. mapKeys := map[string]reflect.Value{}
  129. mapKeyNames := make([]string, len(mapKeyValues))
  130. for i, mapKey := range mapKeyValues {
  131. name := mapKey.String()
  132. mapKeys[name] = mapKey
  133. mapKeyNames[i] = name
  134. }
  135. sort.Strings(mapKeyNames)
  136. for i, mapKeyName := range mapKeyNames {
  137. mapKey := mapKeys[mapKeyName]
  138. mapValue := value.MapIndex(mapKey)
  139. kname := tag.Get("locationNameKey")
  140. if kname == "" {
  141. kname = "key"
  142. }
  143. vname := tag.Get("locationNameValue")
  144. if vname == "" {
  145. vname = "value"
  146. }
  147. // serialize key
  148. var keyName string
  149. if prefix == "" {
  150. keyName = strconv.Itoa(i+1) + "." + kname
  151. } else {
  152. keyName = prefix + "." + strconv.Itoa(i+1) + "." + kname
  153. }
  154. if err := q.parseValue(v, mapKey, keyName, ""); err != nil {
  155. return err
  156. }
  157. // serialize value
  158. var valueName string
  159. if prefix == "" {
  160. valueName = strconv.Itoa(i+1) + "." + vname
  161. } else {
  162. valueName = prefix + "." + strconv.Itoa(i+1) + "." + vname
  163. }
  164. if err := q.parseValue(v, mapValue, valueName, ""); err != nil {
  165. return err
  166. }
  167. }
  168. return nil
  169. }
  170. func (q *queryParser) parseScalar(v url.Values, r reflect.Value, name string, tag reflect.StructTag) error {
  171. switch value := r.Interface().(type) {
  172. case string:
  173. v.Set(name, value)
  174. case []byte:
  175. if !r.IsNil() {
  176. v.Set(name, base64.StdEncoding.EncodeToString(value))
  177. }
  178. case bool:
  179. v.Set(name, strconv.FormatBool(value))
  180. case int64:
  181. v.Set(name, strconv.FormatInt(value, 10))
  182. case int:
  183. v.Set(name, strconv.Itoa(value))
  184. case float64:
  185. v.Set(name, strconv.FormatFloat(value, 'f', -1, 64))
  186. case float32:
  187. v.Set(name, strconv.FormatFloat(float64(value), 'f', -1, 32))
  188. case time.Time:
  189. const ISO8601UTC = "2006-01-02T15:04:05Z"
  190. v.Set(name, value.UTC().Format(ISO8601UTC))
  191. default:
  192. return fmt.Errorf("unsupported value for param %s: %v (%s)", name, r.Interface(), r.Type().Name())
  193. }
  194. return nil
  195. }