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  }