query_test.go 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  1. package apiquery
  2. import (
  3. "net/url"
  4. "testing"
  5. "time"
  6. )
  7. func P[T any](v T) *T { return &v }
  8. type Primitives struct {
  9. A bool `query:"a"`
  10. B int `query:"b"`
  11. C uint `query:"c"`
  12. D float64 `query:"d"`
  13. E float32 `query:"e"`
  14. F []int `query:"f"`
  15. }
  16. type PrimitivePointers struct {
  17. A *bool `query:"a"`
  18. B *int `query:"b"`
  19. C *uint `query:"c"`
  20. D *float64 `query:"d"`
  21. E *float32 `query:"e"`
  22. F *[]int `query:"f"`
  23. }
  24. type Slices struct {
  25. Slice []Primitives `query:"slices"`
  26. Mixed []interface{} `query:"mixed"`
  27. }
  28. type DateTime struct {
  29. Date time.Time `query:"date" format:"date"`
  30. DateTime time.Time `query:"date-time" format:"date-time"`
  31. }
  32. type AdditionalProperties struct {
  33. A bool `query:"a"`
  34. Extras map[string]interface{} `query:"-,inline"`
  35. }
  36. type Recursive struct {
  37. Name string `query:"name"`
  38. Child *Recursive `query:"child"`
  39. }
  40. type UnknownStruct struct {
  41. Unknown interface{} `query:"unknown"`
  42. }
  43. type UnionStruct struct {
  44. Union Union `query:"union" format:"date"`
  45. }
  46. type Union interface {
  47. union()
  48. }
  49. type UnionInteger int64
  50. func (UnionInteger) union() {}
  51. type UnionString string
  52. func (UnionString) union() {}
  53. type UnionStructA struct {
  54. Type string `query:"type"`
  55. A string `query:"a"`
  56. B string `query:"b"`
  57. }
  58. func (UnionStructA) union() {}
  59. type UnionStructB struct {
  60. Type string `query:"type"`
  61. A string `query:"a"`
  62. }
  63. func (UnionStructB) union() {}
  64. type UnionTime time.Time
  65. func (UnionTime) union() {}
  66. type DeeplyNested struct {
  67. A DeeplyNested1 `query:"a"`
  68. }
  69. type DeeplyNested1 struct {
  70. B DeeplyNested2 `query:"b"`
  71. }
  72. type DeeplyNested2 struct {
  73. C DeeplyNested3 `query:"c"`
  74. }
  75. type DeeplyNested3 struct {
  76. D *string `query:"d"`
  77. }
  78. var tests = map[string]struct {
  79. enc string
  80. val interface{}
  81. settings QuerySettings
  82. }{
  83. "primitives": {
  84. "a=false&b=237628372683&c=654&d=9999.43&e=43.7599983215332&f=1,2,3,4",
  85. Primitives{A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}},
  86. QuerySettings{},
  87. },
  88. "slices_brackets": {
  89. `mixed[]=1&mixed[]=2.3&mixed[]=hello&slices[][a]=false&slices[][a]=false&slices[][b]=237628372683&slices[][b]=237628372683&slices[][c]=654&slices[][c]=654&slices[][d]=9999.43&slices[][d]=9999.43&slices[][e]=43.7599983215332&slices[][e]=43.7599983215332&slices[][f][]=1&slices[][f][]=2&slices[][f][]=3&slices[][f][]=4&slices[][f][]=1&slices[][f][]=2&slices[][f][]=3&slices[][f][]=4`,
  90. Slices{
  91. Slice: []Primitives{
  92. {A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}},
  93. {A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}},
  94. },
  95. Mixed: []interface{}{1, 2.3, "hello"},
  96. },
  97. QuerySettings{ArrayFormat: ArrayQueryFormatBrackets},
  98. },
  99. "slices_comma": {
  100. `mixed=1,2.3,hello`,
  101. Slices{
  102. Mixed: []interface{}{1, 2.3, "hello"},
  103. },
  104. QuerySettings{ArrayFormat: ArrayQueryFormatComma},
  105. },
  106. "slices_repeat": {
  107. `mixed=1&mixed=2.3&mixed=hello&slices[a]=false&slices[a]=false&slices[b]=237628372683&slices[b]=237628372683&slices[c]=654&slices[c]=654&slices[d]=9999.43&slices[d]=9999.43&slices[e]=43.7599983215332&slices[e]=43.7599983215332&slices[f]=1&slices[f]=2&slices[f]=3&slices[f]=4&slices[f]=1&slices[f]=2&slices[f]=3&slices[f]=4`,
  108. Slices{
  109. Slice: []Primitives{
  110. {A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}},
  111. {A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}},
  112. },
  113. Mixed: []interface{}{1, 2.3, "hello"},
  114. },
  115. QuerySettings{ArrayFormat: ArrayQueryFormatRepeat},
  116. },
  117. "primitive_pointer_struct": {
  118. "a=false&b=237628372683&c=654&d=9999.43&e=43.7599983215332&f=1,2,3,4,5",
  119. PrimitivePointers{
  120. A: P(false),
  121. B: P(237628372683),
  122. C: P(uint(654)),
  123. D: P(9999.43),
  124. E: P(float32(43.76)),
  125. F: &[]int{1, 2, 3, 4, 5},
  126. },
  127. QuerySettings{},
  128. },
  129. "datetime_struct": {
  130. `date=2006-01-02&date-time=2006-01-02T15:04:05Z`,
  131. DateTime{
  132. Date: time.Date(2006, time.January, 2, 0, 0, 0, 0, time.UTC),
  133. DateTime: time.Date(2006, time.January, 2, 15, 4, 5, 0, time.UTC),
  134. },
  135. QuerySettings{},
  136. },
  137. "additional_properties": {
  138. `a=true&bar=value&foo=true`,
  139. AdditionalProperties{
  140. A: true,
  141. Extras: map[string]interface{}{
  142. "bar": "value",
  143. "foo": true,
  144. },
  145. },
  146. QuerySettings{},
  147. },
  148. "recursive_struct_brackets": {
  149. `child[name]=Alex&name=Robert`,
  150. Recursive{Name: "Robert", Child: &Recursive{Name: "Alex"}},
  151. QuerySettings{NestedFormat: NestedQueryFormatBrackets},
  152. },
  153. "recursive_struct_dots": {
  154. `child.name=Alex&name=Robert`,
  155. Recursive{Name: "Robert", Child: &Recursive{Name: "Alex"}},
  156. QuerySettings{NestedFormat: NestedQueryFormatDots},
  157. },
  158. "unknown_struct_number": {
  159. `unknown=12`,
  160. UnknownStruct{
  161. Unknown: 12.,
  162. },
  163. QuerySettings{},
  164. },
  165. "unknown_struct_map_brackets": {
  166. `unknown[foo]=bar`,
  167. UnknownStruct{
  168. Unknown: map[string]interface{}{
  169. "foo": "bar",
  170. },
  171. },
  172. QuerySettings{NestedFormat: NestedQueryFormatBrackets},
  173. },
  174. "unknown_struct_map_dots": {
  175. `unknown.foo=bar`,
  176. UnknownStruct{
  177. Unknown: map[string]interface{}{
  178. "foo": "bar",
  179. },
  180. },
  181. QuerySettings{NestedFormat: NestedQueryFormatDots},
  182. },
  183. "union_string": {
  184. `union=hello`,
  185. UnionStruct{
  186. Union: UnionString("hello"),
  187. },
  188. QuerySettings{},
  189. },
  190. "union_integer": {
  191. `union=12`,
  192. UnionStruct{
  193. Union: UnionInteger(12),
  194. },
  195. QuerySettings{},
  196. },
  197. "union_struct_discriminated_a": {
  198. `union[a]=foo&union[b]=bar&union[type]=typeA`,
  199. UnionStruct{
  200. Union: UnionStructA{
  201. Type: "typeA",
  202. A: "foo",
  203. B: "bar",
  204. },
  205. },
  206. QuerySettings{},
  207. },
  208. "union_struct_discriminated_b": {
  209. `union[a]=foo&union[type]=typeB`,
  210. UnionStruct{
  211. Union: UnionStructB{
  212. Type: "typeB",
  213. A: "foo",
  214. },
  215. },
  216. QuerySettings{},
  217. },
  218. "union_struct_time": {
  219. `union=2010-05-23`,
  220. UnionStruct{
  221. Union: UnionTime(time.Date(2010, 05, 23, 0, 0, 0, 0, time.UTC)),
  222. },
  223. QuerySettings{},
  224. },
  225. "deeply_nested_brackets": {
  226. `a[b][c][d]=hello`,
  227. DeeplyNested{
  228. A: DeeplyNested1{
  229. B: DeeplyNested2{
  230. C: DeeplyNested3{
  231. D: P("hello"),
  232. },
  233. },
  234. },
  235. },
  236. QuerySettings{NestedFormat: NestedQueryFormatBrackets},
  237. },
  238. "deeply_nested_dots": {
  239. `a.b.c.d=hello`,
  240. DeeplyNested{
  241. A: DeeplyNested1{
  242. B: DeeplyNested2{
  243. C: DeeplyNested3{
  244. D: P("hello"),
  245. },
  246. },
  247. },
  248. },
  249. QuerySettings{NestedFormat: NestedQueryFormatDots},
  250. },
  251. "deeply_nested_brackets_empty": {
  252. ``,
  253. DeeplyNested{
  254. A: DeeplyNested1{
  255. B: DeeplyNested2{
  256. C: DeeplyNested3{
  257. D: nil,
  258. },
  259. },
  260. },
  261. },
  262. QuerySettings{NestedFormat: NestedQueryFormatBrackets},
  263. },
  264. "deeply_nested_dots_empty": {
  265. ``,
  266. DeeplyNested{
  267. A: DeeplyNested1{
  268. B: DeeplyNested2{
  269. C: DeeplyNested3{
  270. D: nil,
  271. },
  272. },
  273. },
  274. },
  275. QuerySettings{NestedFormat: NestedQueryFormatDots},
  276. },
  277. }
  278. func TestEncode(t *testing.T) {
  279. for name, test := range tests {
  280. t.Run(name, func(t *testing.T) {
  281. values := MarshalWithSettings(test.val, test.settings)
  282. str, _ := url.QueryUnescape(values.Encode())
  283. if str != test.enc {
  284. t.Fatalf("expected %+#v to serialize to %s but got %s", test.val, test.enc, str)
  285. }
  286. })
  287. }
  288. }