github.com/anthropics/anthropic-sdk-go@v1.70.1/internal/apiquery/query_test.go (about) 1 package apiquery 2 3 import ( 4 "github.com/anthropics/anthropic-sdk-go/packages/param" 5 "net/url" 6 "testing" 7 "time" 8 ) 9 10 func P[T any](v T) *T { return &v } 11 12 type Primitives struct { 13 A bool `query:"a"` 14 B int `query:"b"` 15 C uint `query:"c"` 16 D float64 `query:"d"` 17 E float32 `query:"e"` 18 F []int `query:"f"` 19 } 20 21 type PrimitivePointers struct { 22 A *bool `query:"a"` 23 B *int `query:"b"` 24 C *uint `query:"c"` 25 D *float64 `query:"d"` 26 E *float32 `query:"e"` 27 F *[]int `query:"f"` 28 } 29 30 type Slices struct { 31 Slice []Primitives `query:"slices"` 32 Mixed []any `query:"mixed"` 33 } 34 35 type DateTime struct { 36 Date time.Time `query:"date" format:"date"` 37 DateTime time.Time `query:"date-time" format:"date-time"` 38 } 39 40 type AdditionalProperties struct { 41 A bool `query:"a"` 42 Extras map[string]any `query:"-,inline"` 43 } 44 45 type Recursive struct { 46 Name string `query:"name"` 47 Child *Recursive `query:"child"` 48 } 49 50 type UnknownStruct struct { 51 Unknown any `query:"unknown"` 52 } 53 54 type UnionStruct struct { 55 Union Union `query:"union" format:"date"` 56 } 57 58 type Union interface { 59 union() 60 } 61 62 type UnionInteger int64 63 64 func (UnionInteger) union() {} 65 66 type UnionString string 67 68 func (UnionString) union() {} 69 70 type UnionStructA struct { 71 Type string `query:"type"` 72 A string `query:"a"` 73 B string `query:"b"` 74 } 75 76 func (UnionStructA) union() {} 77 78 type UnionStructB struct { 79 Type string `query:"type"` 80 A string `query:"a"` 81 } 82 83 func (UnionStructB) union() {} 84 85 type UnionTime time.Time 86 87 func (UnionTime) union() {} 88 89 type DeeplyNested struct { 90 A DeeplyNested1 `query:"a"` 91 } 92 93 type DeeplyNested1 struct { 94 B DeeplyNested2 `query:"b"` 95 } 96 97 type DeeplyNested2 struct { 98 C DeeplyNested3 `query:"c"` 99 } 100 101 type DeeplyNested3 struct { 102 D *string `query:"d"` 103 } 104 105 type RichPrimitives struct { 106 A param.Opt[string] `query:"a"` 107 } 108 109 type QueryOmitTest struct { 110 A param.Opt[string] `query:"a,omitzero"` 111 B string `query:"b,omitzero"` 112 } 113 114 type NamedEnum string 115 116 const NamedEnumFoo NamedEnum = "foo" 117 118 type StructUnionWrapper struct { 119 Union StructUnion `query:"union"` 120 } 121 122 type StructUnion struct { 123 OfInt param.Opt[int64] `query:",omitzero,inline"` 124 OfString param.Opt[string] `query:",omitzero,inline"` 125 OfEnum param.Opt[NamedEnum] `query:",omitzero,inline"` 126 OfA UnionStructA `query:",omitzero,inline"` 127 OfB UnionStructB `query:",omitzero,inline"` 128 param.APIUnion 129 } 130 131 var tests = map[string]struct { 132 enc string 133 val any 134 settings QuerySettings 135 }{ 136 "primitives": { 137 "a=false&b=237628372683&c=654&d=9999.43&e=43.7599983215332&f=1,2,3,4", 138 Primitives{A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}}, 139 QuerySettings{}, 140 }, 141 142 "slices_brackets": { 143 `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`, 144 Slices{ 145 Slice: []Primitives{ 146 {A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}}, 147 {A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}}, 148 }, 149 Mixed: []any{1, 2.3, "hello"}, 150 }, 151 QuerySettings{ArrayFormat: ArrayQueryFormatBrackets}, 152 }, 153 154 "slices_comma": { 155 `mixed=1,2.3,hello`, 156 Slices{ 157 Mixed: []any{1, 2.3, "hello"}, 158 }, 159 QuerySettings{ArrayFormat: ArrayQueryFormatComma}, 160 }, 161 162 "slices_repeat": { 163 `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`, 164 Slices{ 165 Slice: []Primitives{ 166 {A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}}, 167 {A: false, B: 237628372683, C: uint(654), D: 9999.43, E: 43.76, F: []int{1, 2, 3, 4}}, 168 }, 169 Mixed: []any{1, 2.3, "hello"}, 170 }, 171 QuerySettings{ArrayFormat: ArrayQueryFormatRepeat}, 172 }, 173 174 "primitive_pointer_struct": { 175 "a=false&b=237628372683&c=654&d=9999.43&e=43.7599983215332&f=1,2,3,4,5", 176 PrimitivePointers{ 177 A: P(false), 178 B: P(237628372683), 179 C: P(uint(654)), 180 D: P(9999.43), 181 E: P(float32(43.76)), 182 F: &[]int{1, 2, 3, 4, 5}, 183 }, 184 QuerySettings{}, 185 }, 186 187 "datetime_struct": { 188 `date=2006-01-02&date-time=2006-01-02T15:04:05Z`, 189 DateTime{ 190 Date: time.Date(2006, time.January, 2, 0, 0, 0, 0, time.UTC), 191 DateTime: time.Date(2006, time.January, 2, 15, 4, 5, 0, time.UTC), 192 }, 193 QuerySettings{}, 194 }, 195 196 "additional_properties": { 197 `a=true&bar=value&foo=true`, 198 AdditionalProperties{ 199 A: true, 200 Extras: map[string]any{ 201 "bar": "value", 202 "foo": true, 203 }, 204 }, 205 QuerySettings{}, 206 }, 207 208 "recursive_struct_brackets": { 209 `child[name]=Alex&name=Robert`, 210 Recursive{Name: "Robert", Child: &Recursive{Name: "Alex"}}, 211 QuerySettings{NestedFormat: NestedQueryFormatBrackets}, 212 }, 213 214 "recursive_struct_dots": { 215 `child.name=Alex&name=Robert`, 216 Recursive{Name: "Robert", Child: &Recursive{Name: "Alex"}}, 217 QuerySettings{NestedFormat: NestedQueryFormatDots}, 218 }, 219 220 "unknown_struct_number": { 221 `unknown=12`, 222 UnknownStruct{ 223 Unknown: 12., 224 }, 225 QuerySettings{}, 226 }, 227 228 "unknown_struct_map_brackets": { 229 `unknown[foo]=bar`, 230 UnknownStruct{ 231 Unknown: map[string]any{ 232 "foo": "bar", 233 }, 234 }, 235 QuerySettings{NestedFormat: NestedQueryFormatBrackets}, 236 }, 237 238 "unknown_struct_map_dots": { 239 `unknown.foo=bar`, 240 UnknownStruct{ 241 Unknown: map[string]any{ 242 "foo": "bar", 243 }, 244 }, 245 QuerySettings{NestedFormat: NestedQueryFormatDots}, 246 }, 247 248 "struct_union_string": { 249 `union=hello`, 250 StructUnionWrapper{ 251 Union: StructUnion{OfString: param.NewOpt("hello")}, 252 }, 253 QuerySettings{}, 254 }, 255 256 "union_string": { 257 `union=hello`, 258 UnionStruct{ 259 Union: UnionString("hello"), 260 }, 261 QuerySettings{}, 262 }, 263 264 "struct_union_integer": { 265 `union=12`, 266 StructUnionWrapper{ 267 Union: StructUnion{OfInt: param.NewOpt[int64](12)}, 268 }, 269 QuerySettings{}, 270 }, 271 272 "union_integer": { 273 `union=12`, 274 UnionStruct{ 275 Union: UnionInteger(12), 276 }, 277 QuerySettings{}, 278 }, 279 280 "struct_union_enum": { 281 `union=foo`, 282 StructUnionWrapper{ 283 Union: StructUnion{OfEnum: param.NewOpt[NamedEnum](NamedEnumFoo)}, 284 }, 285 QuerySettings{}, 286 }, 287 288 "struct_union_struct_discriminated_a": { 289 `union[a]=foo&union[b]=bar&union[type]=typeA`, 290 StructUnionWrapper{ 291 Union: StructUnion{OfA: UnionStructA{ 292 Type: "typeA", 293 A: "foo", 294 B: "bar", 295 }}, 296 }, 297 QuerySettings{}, 298 }, 299 300 "union_struct_discriminated_a": { 301 `union[a]=foo&union[b]=bar&union[type]=typeA`, 302 UnionStruct{ 303 Union: UnionStructA{ 304 Type: "typeA", 305 A: "foo", 306 B: "bar", 307 }, 308 }, 309 QuerySettings{}, 310 }, 311 312 "struct_union_struct_discriminated_b": { 313 `union[a]=foo&union[type]=typeB`, 314 StructUnionWrapper{ 315 Union: StructUnion{OfB: UnionStructB{ 316 Type: "typeB", 317 A: "foo", 318 }}, 319 }, 320 QuerySettings{}, 321 }, 322 323 "union_struct_discriminated_b": { 324 `union[a]=foo&union[type]=typeB`, 325 UnionStruct{ 326 Union: UnionStructB{ 327 Type: "typeB", 328 A: "foo", 329 }, 330 }, 331 QuerySettings{}, 332 }, 333 334 "union_struct_time": { 335 `union=2010-05-23`, 336 UnionStruct{ 337 Union: UnionTime(time.Date(2010, 05, 23, 0, 0, 0, 0, time.UTC)), 338 }, 339 QuerySettings{}, 340 }, 341 342 "deeply_nested_brackets": { 343 `a[b][c][d]=hello`, 344 DeeplyNested{ 345 A: DeeplyNested1{ 346 B: DeeplyNested2{ 347 C: DeeplyNested3{ 348 D: P("hello"), 349 }, 350 }, 351 }, 352 }, 353 QuerySettings{NestedFormat: NestedQueryFormatBrackets}, 354 }, 355 356 "deeply_nested_dots": { 357 `a.b.c.d=hello`, 358 DeeplyNested{ 359 A: DeeplyNested1{ 360 B: DeeplyNested2{ 361 C: DeeplyNested3{ 362 D: P("hello"), 363 }, 364 }, 365 }, 366 }, 367 QuerySettings{NestedFormat: NestedQueryFormatDots}, 368 }, 369 370 "deeply_nested_brackets_empty": { 371 ``, 372 DeeplyNested{ 373 A: DeeplyNested1{ 374 B: DeeplyNested2{ 375 C: DeeplyNested3{ 376 D: nil, 377 }, 378 }, 379 }, 380 }, 381 QuerySettings{NestedFormat: NestedQueryFormatBrackets}, 382 }, 383 384 "deeply_nested_dots_empty": { 385 ``, 386 DeeplyNested{ 387 A: DeeplyNested1{ 388 B: DeeplyNested2{ 389 C: DeeplyNested3{ 390 D: nil, 391 }, 392 }, 393 }, 394 }, 395 QuerySettings{NestedFormat: NestedQueryFormatDots}, 396 }, 397 398 "rich_primitives": { 399 `a=hello`, 400 RichPrimitives{ 401 A: param.Opt[string]{Value: "hello"}, 402 }, 403 QuerySettings{}, 404 }, 405 406 "rich_primitives_omit": { 407 ``, 408 QueryOmitTest{ 409 A: param.Opt[string]{}, 410 }, 411 QuerySettings{}, 412 }, 413 "query_omit": { 414 `a=hello`, 415 QueryOmitTest{ 416 A: param.Opt[string]{Value: "hello"}, 417 }, 418 QuerySettings{}, 419 }, 420 } 421 422 func TestEncode(t *testing.T) { 423 for name, test := range tests { 424 t.Run(name, func(t *testing.T) { 425 values, err := MarshalWithSettings(test.val, test.settings) 426 if err != nil { 427 t.Fatalf("failed to marshal url %s", err) 428 } 429 str, _ := url.QueryUnescape(values.Encode()) 430 if str != test.enc { 431 t.Fatalf("expected %+#v to serialize to %s but got %s", test.val, test.enc, str) 432 } 433 }) 434 } 435 }