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  }