github.com/anthropics/anthropic-sdk-go@v1.70.1/internal/apijson/decodeparam_test.go (about)

     1  package apijson_test
     2  
     3  import (
     4  	"encoding/json"
     5  	"fmt"
     6  	"github.com/anthropics/anthropic-sdk-go/internal/apijson"
     7  	"github.com/anthropics/anthropic-sdk-go/packages/param"
     8  	"reflect"
     9  	"testing"
    10  )
    11  
    12  func TestOptionalDecoders(t *testing.T) {
    13  	cases := map[string]struct {
    14  		buf string
    15  		val any
    16  	}{
    17  
    18  		"opt_string_present": {
    19  			`"hello"`,
    20  			param.NewOpt("hello"),
    21  		},
    22  		"opt_string_empty_present": {
    23  			`""`,
    24  			param.NewOpt(""),
    25  		},
    26  		"opt_string_null": {
    27  			`null`,
    28  			param.Null[string](),
    29  		},
    30  		"opt_string_null_with_whitespace": {
    31  			`  null  `,
    32  			param.Null[string](),
    33  		},
    34  	}
    35  
    36  	for name, test := range cases {
    37  		t.Run(name, func(t *testing.T) {
    38  			result := reflect.New(reflect.TypeOf(test.val))
    39  			if err := json.Unmarshal([]byte(test.buf), result.Interface()); err != nil {
    40  				t.Fatalf("deserialization of %v failed with error %v", result, err)
    41  			}
    42  
    43  			if !reflect.DeepEqual(result.Elem().Interface(), test.val) {
    44  				t.Fatalf("expected '%s' to deserialize to \n%#v\nbut got\n%#v", test.buf, test.val, result.Elem().Interface())
    45  			}
    46  		})
    47  	}
    48  }
    49  
    50  type paramObject = param.APIObject
    51  
    52  type BasicObject struct {
    53  	ReqInt    int64   `json:"req_int" api:"required"`
    54  	ReqFloat  float64 `json:"req_float" api:"required"`
    55  	ReqString string  `json:"req_string" api:"required"`
    56  	ReqBool   bool    `json:"req_bool" api:"required"`
    57  
    58  	OptInt    param.Opt[int64]   `json:"opt_int"`
    59  	OptFloat  param.Opt[float64] `json:"opt_float"`
    60  	OptString param.Opt[string]  `json:"opt_string"`
    61  	OptBool   param.Opt[bool]    `json:"opt_bool"`
    62  
    63  	paramObject
    64  }
    65  
    66  func (o *BasicObject) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, o) }
    67  
    68  func TestBasicObjectWithNull(t *testing.T) {
    69  	raw := `{"req_int":1,"req_float":1.5,"req_string":"s","req_bool":true,"opt_int":null,"opt_string":null,"opt_bool":null}`
    70  	var dst BasicObject
    71  	target := BasicObject{
    72  		ReqInt:    1,
    73  		ReqFloat:  1.5,
    74  		ReqString: "s",
    75  		ReqBool:   true,
    76  		OptInt:    param.Null[int64](),
    77  		// OptFloat:  param.Opt[float64]{},
    78  		OptString: param.Null[string](),
    79  		OptBool:   param.Null[bool](),
    80  	}
    81  
    82  	err := json.Unmarshal([]byte(raw), &dst)
    83  	if err != nil {
    84  		t.Fatalf("failed unmarshal")
    85  	}
    86  
    87  	if !reflect.DeepEqual(dst, target) {
    88  		t.Fatalf("failed equality check %#v", dst)
    89  	}
    90  }
    91  
    92  func TestBasicObject(t *testing.T) {
    93  	raw := `{"req_int":1,"req_float":1.3,"req_string":"test","req_bool":true,"opt_int":2,"opt_float":2.0,"opt_string":"test","opt_bool":false}`
    94  	var dst BasicObject
    95  	target := BasicObject{
    96  		ReqInt:    1,
    97  		ReqFloat:  1.3,
    98  		ReqString: "test",
    99  		ReqBool:   true,
   100  		OptInt:    param.NewOpt[int64](2),
   101  		OptFloat:  param.NewOpt(2.0),
   102  		OptString: param.NewOpt("test"),
   103  		OptBool:   param.NewOpt(false),
   104  	}
   105  
   106  	err := json.Unmarshal([]byte(raw), &dst)
   107  	if err != nil {
   108  		t.Fatalf("failed unmarshal")
   109  	}
   110  
   111  	if !reflect.DeepEqual(dst, target) {
   112  		t.Fatalf("failed equality check %#v", dst)
   113  	}
   114  }
   115  
   116  type ComplexObject struct {
   117  	Basic BasicObject `json:"basic" api:"required"`
   118  	Enum  string      `json:"enum"`
   119  	paramObject
   120  }
   121  
   122  func (o *ComplexObject) UnmarshalJSON(data []byte) error { return apijson.UnmarshalRoot(data, o) }
   123  
   124  func init() {
   125  	apijson.RegisterFieldValidator[ComplexObject]("enum", "a", "b", "c")
   126  }
   127  
   128  func TestComplexObject(t *testing.T) {
   129  	raw := `{"basic":{"req_int":1,"req_float":1.3,"req_string":"test","req_bool":true,"opt_int":2,"opt_float":2.0,"opt_string":"test","opt_bool":false},"enum":"a"}`
   130  	var dst ComplexObject
   131  
   132  	target := ComplexObject{
   133  		Basic: BasicObject{
   134  			ReqInt:    1,
   135  			ReqFloat:  1.3,
   136  			ReqString: "test",
   137  			ReqBool:   true,
   138  			OptInt:    param.NewOpt[int64](2),
   139  			OptFloat:  param.NewOpt(2.0),
   140  			OptString: param.NewOpt("test"),
   141  			OptBool:   param.NewOpt(false),
   142  		},
   143  		Enum: "a",
   144  	}
   145  
   146  	err := json.Unmarshal([]byte(raw), &dst)
   147  	if err != nil {
   148  		t.Fatalf("failed unmarshal")
   149  	}
   150  
   151  	if !reflect.DeepEqual(dst, target) {
   152  		t.Fatalf("failed equality check %#v", dst)
   153  	}
   154  }
   155  
   156  type paramUnion = param.APIUnion
   157  
   158  type MemberA struct {
   159  	Name string `json:"name" api:"required"`
   160  	Age  int    `json:"age" api:"required"`
   161  }
   162  
   163  type MemberB struct {
   164  	Name string `json:"name" api:"required"`
   165  	Age  string `json:"age" api:"required"`
   166  }
   167  
   168  type MemberC struct {
   169  	Name   string `json:"name" api:"required"`
   170  	Age    int    `json:"age" api:"required"`
   171  	Status string `json:"status"`
   172  }
   173  
   174  type MemberD struct {
   175  	Cost   int    `json:"cost" api:"required"`
   176  	Status string `json:"status" api:"required"`
   177  }
   178  
   179  type MemberE struct {
   180  	Cost   int    `json:"cost" api:"required"`
   181  	Status string `json:"status" api:"required"`
   182  }
   183  
   184  type MemberF struct {
   185  	D int            `json:"d"`
   186  	E string         `json:"e"`
   187  	F float64        `json:"f"`
   188  	G param.Opt[int] `json:"g"`
   189  }
   190  
   191  type MemberG struct {
   192  	D int             `json:"d"`
   193  	E string          `json:"e"`
   194  	F float64         `json:"f"`
   195  	G param.Opt[bool] `json:"g"`
   196  }
   197  
   198  func init() {
   199  	apijson.RegisterFieldValidator[MemberD]("status", "good", "ok", "bad")
   200  	apijson.RegisterFieldValidator[MemberE]("status", "GOOD", "OK", "BAD")
   201  }
   202  
   203  type UnionStruct struct {
   204  	OfMemberA *MemberA          `json:",inline"`
   205  	OfMemberB *MemberB          `json:",inline"`
   206  	OfMemberC *MemberC          `json:",inline"`
   207  	OfMemberD *MemberD          `json:",inline"`
   208  	OfMemberE *MemberE          `json:",inline"`
   209  	OfMemberF *MemberF          `json:",inline"`
   210  	OfMemberG *MemberG          `json:",inline"`
   211  	OfString  param.Opt[string] `json:",inline"`
   212  
   213  	paramUnion
   214  }
   215  
   216  func (union *UnionStruct) UnmarshalJSON(data []byte) error {
   217  	return apijson.UnmarshalRoot(data, union)
   218  }
   219  
   220  func TestUnionStruct(t *testing.T) {
   221  	tests := map[string]struct {
   222  		raw        string
   223  		target     UnionStruct
   224  		shouldFail bool
   225  	}{
   226  		"fail": {
   227  			raw:        `1200`,
   228  			target:     UnionStruct{},
   229  			shouldFail: true,
   230  		},
   231  		"easy": {
   232  			raw:    `{"age":30}`,
   233  			target: UnionStruct{OfMemberA: &MemberA{Age: 30}},
   234  		},
   235  		"less-easy": {
   236  			raw:    `{"age":"thirty"}`,
   237  			target: UnionStruct{OfMemberB: &MemberB{Age: "thirty"}},
   238  		},
   239  		"even-less-easy": {
   240  			raw:    `{"age":"30"}`,
   241  			target: UnionStruct{OfMemberB: &MemberB{Age: "30"}},
   242  		},
   243  		"medium": {
   244  			raw: `{"name":"jacob","age":30}`,
   245  			target: UnionStruct{OfMemberA: &MemberA{
   246  				Age:  30,
   247  				Name: "jacob",
   248  			}},
   249  		},
   250  		"less-medium": {
   251  			raw: `{"name":"jacob","age":"thirty"}`,
   252  			target: UnionStruct{OfMemberB: &MemberB{
   253  				Age:  "thirty",
   254  				Name: "jacob",
   255  			}},
   256  		},
   257  		"even-less-medium": {
   258  			raw: `{"name":"jacob","age":"30"}`,
   259  			target: UnionStruct{OfMemberB: &MemberB{
   260  				Name: "jacob",
   261  				Age:  "30",
   262  			}},
   263  		},
   264  		"hard": {
   265  			raw: `{"name":"jacob","age":30,"status":"active"}`,
   266  			target: UnionStruct{OfMemberC: &MemberC{
   267  				Name:   "jacob",
   268  				Age:    30,
   269  				Status: "active",
   270  			}},
   271  		},
   272  		"inline-string": {
   273  			raw:    `"hello there"`,
   274  			target: UnionStruct{OfString: param.NewOpt("hello there")},
   275  		},
   276  		"enum-field": {
   277  			raw:    `{"cost":100,"status":"ok"}`,
   278  			target: UnionStruct{OfMemberD: &MemberD{Cost: 100, Status: "ok"}},
   279  		},
   280  		"other-enum-field": {
   281  			raw:    `{"cost":100,"status":"GOOD"}`,
   282  			target: UnionStruct{OfMemberE: &MemberE{Cost: 100, Status: "GOOD"}},
   283  		},
   284  		"tricky-extra-fields": {
   285  			raw:    `{"d":12,"e":"hello","f":1.00}`,
   286  			target: UnionStruct{OfMemberF: &MemberF{D: 12, E: "hello", F: 1.00}},
   287  		},
   288  		"optional-fields": {
   289  			raw:    `{"d":12,"e":"hello","f":1.00,"g":12}`,
   290  			target: UnionStruct{OfMemberF: &MemberF{D: 12, E: "hello", F: 1.00, G: param.NewOpt(12)}},
   291  		},
   292  		"optional-fields-2": {
   293  			raw:    `{"d":12,"e":"hello","f":1.00,"g":false}`,
   294  			target: UnionStruct{OfMemberG: &MemberG{D: 12, E: "hello", F: 1.00, G: param.NewOpt(false)}},
   295  		},
   296  	}
   297  
   298  	for name, test := range tests {
   299  		var dst UnionStruct
   300  		t.Run(name, func(t *testing.T) {
   301  			err := json.Unmarshal([]byte(test.raw), &dst)
   302  			if err != nil && !test.shouldFail {
   303  				t.Fatalf("failed unmarshal with err: %v %#v", err, dst)
   304  			}
   305  
   306  			if !reflect.DeepEqual(dst, test.target) {
   307  				if dst.OfMemberA != nil {
   308  					fmt.Printf("%#v", dst.OfMemberA)
   309  				}
   310  				t.Fatalf("failed equality, got %#v but expected %#v", dst, test.target)
   311  			}
   312  		})
   313  	}
   314  }
   315  
   316  type ConstantA string
   317  type ConstantB string
   318  type ConstantC string
   319  
   320  func (c ConstantA) Default() string { return "A" }
   321  func (c ConstantB) Default() string { return "B" }
   322  func (c ConstantC) Default() string { return "C" }
   323  
   324  type DiscVariantA struct {
   325  	Name string    `json:"name" api:"required"`
   326  	Age  int       `json:"age" api:"required"`
   327  	Type ConstantA `json:"type" api:"required"`
   328  }
   329  
   330  type DiscVariantB struct {
   331  	Name string    `json:"name" api:"required"`
   332  	Age  int       `json:"age" api:"required"`
   333  	Type ConstantB `json:"type" api:"required"`
   334  }
   335  
   336  type DiscVariantC struct {
   337  	Name string    `json:"name" api:"required"`
   338  	Age  float64   `json:"age" api:"required"`
   339  	Type ConstantC `json:"type" api:"required"`
   340  }
   341  
   342  type DiscriminatedUnion struct {
   343  	OfA *DiscVariantA `json:",inline"`
   344  	OfB *DiscVariantB `json:",inline"`
   345  	OfC *DiscVariantC `json:",inline"`
   346  
   347  	paramUnion
   348  }
   349  
   350  func init() {
   351  	apijson.RegisterDiscriminatedUnion[DiscriminatedUnion]("type", map[string]reflect.Type{
   352  		"A": reflect.TypeOf(DiscVariantA{}),
   353  		"B": reflect.TypeOf(DiscVariantB{}),
   354  		"C": reflect.TypeOf(DiscVariantC{}),
   355  	})
   356  }
   357  
   358  type FooVariant struct {
   359  	Type  string `json:"type" api:"required"`
   360  	Value string `json:"value" api:"required"`
   361  }
   362  
   363  type BarVariant struct {
   364  	Type   string `json:"type" api:"required"`
   365  	Enable bool   `json:"enable" api:"required"`
   366  }
   367  
   368  type MultiDiscriminatorUnion struct {
   369  	OfFoo *FooVariant `json:",inline"`
   370  	OfBar *BarVariant `json:",inline"`
   371  
   372  	paramUnion
   373  }
   374  
   375  func init() {
   376  	apijson.RegisterDiscriminatedUnion[MultiDiscriminatorUnion]("type", map[string]reflect.Type{
   377  		"foo":        reflect.TypeOf(FooVariant{}),
   378  		"foo_v2":     reflect.TypeOf(FooVariant{}),
   379  		"bar":        reflect.TypeOf(BarVariant{}),
   380  		"bar_legacy": reflect.TypeOf(BarVariant{}),
   381  	})
   382  }
   383  
   384  func (m *MultiDiscriminatorUnion) UnmarshalJSON(data []byte) error {
   385  	return apijson.UnmarshalRoot(data, m)
   386  }
   387  
   388  func (d *DiscriminatedUnion) UnmarshalJSON(data []byte) error {
   389  	return apijson.UnmarshalRoot(data, d)
   390  }
   391  
   392  func TestDiscriminatedUnion(t *testing.T) {
   393  	tests := map[string]struct {
   394  		raw        string
   395  		target     DiscriminatedUnion
   396  		shouldFail bool
   397  	}{
   398  		"variant_a": {
   399  			raw: `{"name":"Alice","age":25,"type":"A"}`,
   400  			target: DiscriminatedUnion{OfA: &DiscVariantA{
   401  				Name: "Alice",
   402  				Age:  25,
   403  				Type: "A",
   404  			}},
   405  		},
   406  		"variant_b": {
   407  			raw: `{"name":"Bob","age":30,"type":"B"}`,
   408  			target: DiscriminatedUnion{OfB: &DiscVariantB{
   409  				Name: "Bob",
   410  				Age:  30,
   411  				Type: "B",
   412  			}},
   413  		},
   414  		"variant_c": {
   415  			raw: `{"name":"Charlie","age":35.5,"type":"C"}`,
   416  			target: DiscriminatedUnion{OfC: &DiscVariantC{
   417  				Name: "Charlie",
   418  				Age:  35.5,
   419  				Type: "C",
   420  			}},
   421  		},
   422  		"invalid_type": {
   423  			raw:        `{"name":"Unknown","age":40,"type":"D"}`,
   424  			target:     DiscriminatedUnion{},
   425  			shouldFail: true,
   426  		},
   427  	}
   428  
   429  	for name, test := range tests {
   430  		t.Run(name, func(t *testing.T) {
   431  			var dst DiscriminatedUnion
   432  			err := json.Unmarshal([]byte(test.raw), &dst)
   433  			if err != nil && !test.shouldFail {
   434  				t.Fatalf("failed unmarshal with err: %v", err)
   435  			}
   436  			if err == nil && test.shouldFail {
   437  				t.Fatalf("expected unmarshal to fail but it succeeded")
   438  			}
   439  			if !reflect.DeepEqual(dst, test.target) {
   440  				t.Fatalf("failed equality, got %#v but expected %#v", dst, test.target)
   441  			}
   442  		})
   443  	}
   444  }
   445  
   446  func TestMultiDiscriminatorUnion(t *testing.T) {
   447  	tests := map[string]struct {
   448  		raw        string
   449  		target     MultiDiscriminatorUnion
   450  		shouldFail bool
   451  	}{
   452  		"foo_variant": {
   453  			raw: `{"type":"foo","value":"test"}`,
   454  			target: MultiDiscriminatorUnion{OfFoo: &FooVariant{
   455  				Type:  "foo",
   456  				Value: "test",
   457  			}},
   458  		},
   459  		"foo_v2_variant": {
   460  			raw: `{"type":"foo_v2","value":"test_v2"}`,
   461  			target: MultiDiscriminatorUnion{OfFoo: &FooVariant{
   462  				Type:  "foo_v2",
   463  				Value: "test_v2",
   464  			}},
   465  		},
   466  		"bar_variant": {
   467  			raw: `{"type":"bar","enable":true}`,
   468  			target: MultiDiscriminatorUnion{OfBar: &BarVariant{
   469  				Type:   "bar",
   470  				Enable: true,
   471  			}},
   472  		},
   473  		"bar_legacy_variant": {
   474  			raw: `{"type":"bar_legacy","enable":false}`,
   475  			target: MultiDiscriminatorUnion{OfBar: &BarVariant{
   476  				Type:   "bar_legacy",
   477  				Enable: false,
   478  			}},
   479  		},
   480  		"invalid_type": {
   481  			raw:        `{"type":"unknown","value":"test"}`,
   482  			target:     MultiDiscriminatorUnion{},
   483  			shouldFail: true,
   484  		},
   485  	}
   486  
   487  	for name, test := range tests {
   488  		t.Run(name, func(t *testing.T) {
   489  			var dst MultiDiscriminatorUnion
   490  			err := json.Unmarshal([]byte(test.raw), &dst)
   491  			if err != nil && !test.shouldFail {
   492  				t.Fatalf("failed unmarshal with err: %v", err)
   493  			}
   494  			if err == nil && test.shouldFail {
   495  				t.Fatalf("expected unmarshal to fail but it succeeded")
   496  			}
   497  			if !reflect.DeepEqual(dst, test.target) {
   498  				t.Fatalf("failed equality, got %#v but expected %#v", dst, test.target)
   499  			}
   500  		})
   501  	}
   502  }