json_test.go 4.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140
  1. // Copyright (c) Tailscale Inc & contributors
  2. // SPDX-License-Identifier: BSD-3-Clause
  3. package jsonx
  4. import (
  5. "errors"
  6. "testing"
  7. "github.com/go-json-experiment/json"
  8. "github.com/go-json-experiment/json/jsontext"
  9. "github.com/google/go-cmp/cmp"
  10. "tailscale.com/types/ptr"
  11. )
  12. type Interface interface {
  13. implementsInterface()
  14. }
  15. type Foo string
  16. func (Foo) implementsInterface() {}
  17. type Bar int
  18. func (Bar) implementsInterface() {}
  19. type Baz struct{ Fizz, Buzz string }
  20. func (*Baz) implementsInterface() {}
  21. var interfaceCoders = MakeInterfaceCoders(map[string]Interface{
  22. "Foo": Foo(""),
  23. "Bar": (*Bar)(nil),
  24. "Baz": (*Baz)(nil),
  25. })
  26. type InterfaceWrapper struct{ Interface }
  27. func (w InterfaceWrapper) MarshalJSONTo(enc *jsontext.Encoder) error {
  28. return interfaceCoders.Marshal(enc, &w.Interface)
  29. }
  30. func (w *InterfaceWrapper) UnmarshalJSONFrom(dec *jsontext.Decoder) error {
  31. return interfaceCoders.Unmarshal(dec, &w.Interface)
  32. }
  33. func TestInterfaceCoders(t *testing.T) {
  34. var opts json.Options = json.JoinOptions(
  35. json.WithMarshalers(json.MarshalToFunc(interfaceCoders.Marshal)),
  36. json.WithUnmarshalers(json.UnmarshalFromFunc(interfaceCoders.Unmarshal)),
  37. )
  38. errSkipMarshal := errors.New("skip marshal")
  39. makeFiller := func() InterfaceWrapper {
  40. return InterfaceWrapper{&Baz{"fizz", "buzz"}}
  41. }
  42. for _, tt := range []struct {
  43. label string
  44. wantVal InterfaceWrapper
  45. wantJSON string
  46. wantMarshalError error
  47. wantUnmarshalError error
  48. }{{
  49. label: "Null",
  50. wantVal: InterfaceWrapper{},
  51. wantJSON: `null`,
  52. }, {
  53. label: "Foo",
  54. wantVal: InterfaceWrapper{Foo("hello")},
  55. wantJSON: `{"Foo":"hello"}`,
  56. }, {
  57. label: "BarPointer",
  58. wantVal: InterfaceWrapper{ptr.To(Bar(5))},
  59. wantJSON: `{"Bar":5}`,
  60. }, {
  61. label: "BarValue",
  62. wantVal: InterfaceWrapper{Bar(5)},
  63. // NOTE: We could handle BarValue just like BarPointer,
  64. // but round-trip marshal/unmarshal would not be identical.
  65. wantMarshalError: errUnknownTypeName,
  66. }, {
  67. label: "Baz",
  68. wantVal: InterfaceWrapper{&Baz{"alpha", "omega"}},
  69. wantJSON: `{"Baz":{"Fizz":"alpha","Buzz":"omega"}}`,
  70. }, {
  71. label: "Unknown",
  72. wantVal: makeFiller(),
  73. wantJSON: `{"Unknown":[1,2,3]}`,
  74. wantMarshalError: errSkipMarshal,
  75. wantUnmarshalError: errUnknownTypeName,
  76. }, {
  77. label: "Empty",
  78. wantVal: makeFiller(),
  79. wantJSON: `{}`,
  80. wantMarshalError: errSkipMarshal,
  81. wantUnmarshalError: errNonSingularValue,
  82. }, {
  83. label: "Duplicate",
  84. wantVal: InterfaceWrapper{Foo("hello")}, // first entry wins
  85. wantJSON: `{"Foo":"hello","Bar":5}`,
  86. wantMarshalError: errSkipMarshal,
  87. wantUnmarshalError: errNonSingularValue,
  88. }} {
  89. t.Run(tt.label, func(t *testing.T) {
  90. if tt.wantMarshalError != errSkipMarshal {
  91. switch gotJSON, err := json.Marshal(&tt.wantVal); {
  92. case !errors.Is(err, tt.wantMarshalError):
  93. t.Fatalf("json.Marshal(%v) error = %v, want %v", tt.wantVal, err, tt.wantMarshalError)
  94. case string(gotJSON) != tt.wantJSON:
  95. t.Fatalf("json.Marshal(%v) = %s, want %s", tt.wantVal, gotJSON, tt.wantJSON)
  96. }
  97. switch gotJSON, err := json.Marshal(&tt.wantVal.Interface, opts); {
  98. case !errors.Is(err, tt.wantMarshalError):
  99. t.Fatalf("json.Marshal(%v) error = %v, want %v", tt.wantVal, err, tt.wantMarshalError)
  100. case string(gotJSON) != tt.wantJSON:
  101. t.Fatalf("json.Marshal(%v) = %s, want %s", tt.wantVal, gotJSON, tt.wantJSON)
  102. }
  103. }
  104. if tt.wantJSON != "" {
  105. gotVal := makeFiller()
  106. if err := json.Unmarshal([]byte(tt.wantJSON), &gotVal); !errors.Is(err, tt.wantUnmarshalError) {
  107. t.Fatalf("json.Unmarshal(%v) error = %v, want %v", tt.wantJSON, err, tt.wantUnmarshalError)
  108. }
  109. if d := cmp.Diff(gotVal, tt.wantVal); d != "" {
  110. t.Fatalf("json.Unmarshal(%v):\n%s", tt.wantJSON, d)
  111. }
  112. gotVal = makeFiller()
  113. if err := json.Unmarshal([]byte(tt.wantJSON), &gotVal.Interface, opts); !errors.Is(err, tt.wantUnmarshalError) {
  114. t.Fatalf("json.Unmarshal(%v) error = %v, want %v", tt.wantJSON, err, tt.wantUnmarshalError)
  115. }
  116. if d := cmp.Diff(gotVal, tt.wantVal); d != "" {
  117. t.Fatalf("json.Unmarshal(%v):\n%s", tt.wantJSON, d)
  118. }
  119. }
  120. })
  121. }
  122. }