github.com/anthropics/anthropic-sdk-go@v1.70.1/packages/respjson/decoder_test.go (about) 1 package respjson_test 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "github.com/anthropics/anthropic-sdk-go/internal/apijson" 7 rj "github.com/anthropics/anthropic-sdk-go/packages/respjson" 8 "reflect" 9 "testing" 10 ) 11 12 type UnionOfStringIntOrObject struct { 13 OfString string `json:",inline"` 14 OfInt int `json:",inline"` 15 Type string `json:"type"` 16 Function SubFields `json:"function"` 17 JSON struct { 18 OfString rj.Field 19 OfInt rj.Field 20 Type rj.Field 21 Function rj.Field 22 raw string 23 } `json:"-"` 24 } 25 26 func (u UnionOfStringIntOrObject) RawJSON() string { return u.JSON.raw } 27 func (r *UnionOfStringIntOrObject) UnmarshalJSON(data []byte) error { 28 return apijson.UnmarshalRoot(data, r) 29 } 30 31 type SubFields struct { 32 OfBool bool `json:",inline"` 33 Name string `json:"name" api:"required"` 34 JSON struct { 35 OfBool rj.Field 36 Name rj.Field 37 ExtraFields map[string]rj.Field 38 raw string 39 } `json:"-"` 40 } 41 42 func (r SubFields) RawJSON() string { return r.JSON.raw } 43 func (r *SubFields) UnmarshalJSON(data []byte) error { 44 return apijson.UnmarshalRoot(data, r) 45 } 46 47 func TestUnmarshalUnionString(t *testing.T) { 48 rawJSON := `"123"` 49 testUnmarshalUnion(t, rawJSON, func(res UnionOfStringIntOrObject) map[string]error { 50 return map[string]error{ 51 "rawJSON": checkEqual(res.RawJSON(), rawJSON), 52 53 "string": checkEqual(res.OfString, "123"), 54 "int": checkEqual(res.OfInt, 0), 55 "$.type": checkEqual(res.Type, ""), 56 "$.function.name": checkEqual(res.Function.Name, ""), 57 58 "string.meta": checkMeta(res.JSON.OfString, rawJSON, shouldBePresent), 59 "int.meta": checkMeta(res.JSON.OfInt, "", shouldBeNullish), 60 "$.type.meta": checkMeta(res.JSON.Type, "", shouldBeNullish), 61 "$.function.meta": checkMeta(res.Function.JSON.Name, "", shouldBeNullish), 62 "$.function.name.meta": checkMeta(res.Function.JSON.Name, "", shouldBeNullish), 63 } 64 }) 65 } 66 67 func TestUnmarshalUnionInt(t *testing.T) { 68 rawJSON := `123` 69 testUnmarshalUnion(t, rawJSON, func(res UnionOfStringIntOrObject) map[string]error { 70 return map[string]error{ 71 "rawJSON": checkEqual(res.RawJSON(), rawJSON), 72 73 "string": checkEqual(res.OfString, ""), 74 "int": checkEqual(res.OfInt, 123), 75 "$.type": checkEqual(res.Type, ""), 76 "$.function.name": checkEqual(res.Function.Name, ""), 77 "$.function.bool": checkEqual(res.Function.OfBool, false), 78 79 "string.meta": checkMeta(res.JSON.OfString, "", shouldBeNullish), 80 "int.meta": checkMeta(res.JSON.OfInt, rawJSON, shouldBePresent), 81 "$.type.meta": checkMeta(res.JSON.Type, "", shouldBeNullish), 82 "$.function.meta": checkMeta(res.Function.JSON.Name, "", shouldBeNullish), 83 "$.function.name.meta": checkMeta(res.Function.JSON.Name, "", shouldBeNullish), 84 } 85 }) 86 87 testUnmarshalUnion(t, `0`, func(res UnionOfStringIntOrObject) map[string]error { 88 return map[string]error{ 89 "rawJSON": checkEqual(res.RawJSON(), "0"), 90 "string": checkEqual(res.OfString, ""), 91 92 "int": checkEqual(res.OfInt, 0), 93 "int.meta": checkMeta(res.JSON.OfInt, "0", shouldBePresent), 94 "string.meta": checkMeta(res.JSON.OfString, "", shouldBeNullish), 95 } 96 }) 97 } 98 99 func TestUnmarshalUnionObject(t *testing.T) { 100 rawJSON := `{"type":"auto","function":{"name":"test_fn"}}` 101 testUnmarshalUnion(t, rawJSON, func(res UnionOfStringIntOrObject) map[string]error { 102 return map[string]error{ 103 "rawJSON": checkEqual(res.RawJSON(), rawJSON), 104 105 "string": checkEqual(res.OfString, ""), 106 "int": checkEqual(res.OfInt, 0), 107 "$.type": checkEqual(res.Type, "auto"), 108 "$.function.name": checkEqual(res.Function.Name, "test_fn"), 109 "$.function.bool": checkEqual(res.Function.OfBool, false), 110 111 "string.meta": checkMeta(res.JSON.OfString, "", shouldBeNullish), 112 "int.meta": checkMeta(res.JSON.OfInt, "", shouldBeNullish), 113 "$.type.meta": checkMeta(res.JSON.Type, `"auto"`, shouldBePresent), 114 "$.function.meta": checkMeta(res.JSON.Function, `{"name":"test_fn"}`, shouldBePresent), 115 "$.function.name.meta": checkMeta(res.Function.JSON.Name, `"test_fn"`, shouldBePresent), 116 "$.function.bool.meta": checkMeta(res.Function.JSON.OfBool, "", shouldBeNullish), 117 } 118 }) 119 } 120 121 func TestUnmarshalUnionObjectWithInlineSubUnion(t *testing.T) { 122 rawJSON := `{"type":"auto","function":true}` 123 testUnmarshalUnion(t, rawJSON, func(res UnionOfStringIntOrObject) map[string]error { 124 return map[string]error{ 125 "rawJSON": checkEqual(res.RawJSON(), rawJSON), 126 127 "string": checkEqual(res.OfString, ""), 128 "int": checkEqual(res.OfInt, 0), 129 "$.type": checkEqual(res.Type, "auto"), 130 "$.function": checkEqual(res.Function.OfBool, true), 131 132 "string.meta": checkMeta(res.JSON.OfString, "", shouldBeNullish), 133 "int.meta": checkMeta(res.JSON.OfInt, "", shouldBeNullish), 134 "$.type.meta": checkMeta(res.JSON.Type, `"auto"`, shouldBePresent), 135 "$.function.meta": checkMeta(res.JSON.Function, `true`, shouldBePresent), 136 "$.function.name.meta": checkMeta(res.Function.JSON.Name, "", shouldBeNullish), 137 "$.function.bool.meta": checkMeta(res.Function.JSON.OfBool, `true`, shouldBePresent), 138 } 139 }) 140 } 141 142 /*********/ 143 /* UTILS */ 144 /*********/ 145 146 func testUnmarshalUnion[T any](t *testing.T, raw string, check testChecks[T]) { 147 var res T 148 err := json.Unmarshal([]byte(raw), &res) 149 if err != nil { 150 t.Fatalf("failed to unmarshal %v", err.Error()) 151 } 152 153 for label, fail := range check(res) { 154 if fail != nil { 155 t.Errorf("failed check %v: %v", label, fail.Error()) 156 } 157 } 158 } 159 160 func checkEqual[T any](got, expected T) error { 161 if reflect.DeepEqual(got, expected) { 162 return nil 163 } 164 return fmt.Errorf("not equal: got %v, expected %v", got, expected) 165 } 166 167 type metaStatus int 168 169 const ( 170 shouldBePresent metaStatus = iota 171 shouldBeNullish 172 shouldBeInvalid 173 ) 174 175 type testChecks[T any] func(T) map[string]error 176 177 func checkMeta(got rj.Field, raw string, stat metaStatus) error { 178 switch stat { 179 case shouldBePresent: 180 if !got.Valid() { 181 return fmt.Errorf("expected field to be present, but got nullish") 182 } 183 if got.Raw() != raw { 184 return fmt.Errorf("expected field to be present with raw value %v, but got %v", raw, got.Raw()) 185 } 186 case shouldBeNullish: 187 if got.Valid() { 188 return fmt.Errorf("expected field to be nullish, but got %v", got.Raw()) 189 } 190 if got.Raw() != rj.Omitted && got.Raw() != rj.Null { 191 return fmt.Errorf("expected field to be nullish, but got %v", got.Raw()) 192 } 193 case shouldBeInvalid: 194 if !got.Valid() || got.Raw() == "" { 195 return fmt.Errorf("expected field to be invalid, but got valid value %v", got.Raw()) 196 } 197 if got.Raw() != raw { 198 return fmt.Errorf("expected field to be invalid, but got valid value %v", got.Raw()) 199 } 200 default: 201 return fmt.Errorf("unknown metaStatus: %v", stat) 202 } 203 return nil 204 }