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 }