Skip to content

Commit fa5afb5

Browse files
prepare 2.0.1 release (#12)
1 parent b6a90f4 commit fa5afb5

File tree

9 files changed

+47
-16
lines changed

9 files changed

+47
-16
lines changed

lduser/user_serialization.go

Lines changed: 18 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,17 @@ package lduser
22

33
import (
44
"encoding/json"
5-
"errors"
5+
"reflect"
66

77
"gopkg.in/launchdarkly/go-sdk-common.v2/jsonstream"
88
"gopkg.in/launchdarkly/go-sdk-common.v2/ldvalue"
99
)
1010

11-
var (
12-
errMissingKey = errors.New("")
13-
)
11+
type missingKeyError struct{}
12+
13+
func (e missingKeyError) Error() string {
14+
return "User must have a key property"
15+
}
1416

1517
// ErrMissingKey returns the standard error value that is used if you try to unmarshal a user from JSON
1618
// and the "key" property is either absent or null. This is distinguished from other kinds of unmarshaling
@@ -20,7 +22,7 @@ var (
2022
// LaunchDarkly does allow a user to have an empty string ("") as a key in some cases, but this is
2123
// discouraged since analytics events will not work properly without unique user keys.
2224
func ErrMissingKey() error {
23-
return errMissingKey
25+
return missingKeyError{}
2426
}
2527

2628
// This temporary struct allows us to do JSON unmarshalling as efficiently as possible while not requiring
@@ -76,13 +78,23 @@ func (u User) MarshalJSON() ([]byte, error) {
7678
// Any property that is either completely omitted or has a null value is ignored and left in an unset
7779
// state, except for "key". All users must have a key (even if it is ""), so an omitted or null "key"
7880
// property causes the error ErrMissingKey().
81+
//
82+
// Trying to unmarshal any non-struct value, including a JSON null, into a User will return a
83+
// json.UnmarshalTypeError. If you want to unmarshal optional user data that might be null, use *User
84+
// instead of User.
7985
func (u *User) UnmarshalJSON(data []byte) error {
86+
// Special handling here for a null value - json.Unmarshal will normally treat a null exactly like
87+
// "{}" when unmarshaling a struct. We don't want that, because it will produce a misleading
88+
// "missing key" error further down. Instead, just treat it as an invalid type.
89+
if string(data) == "null" {
90+
return &json.UnmarshalTypeError{Value: string(data), Type: reflect.TypeOf(u)}
91+
}
8092
var ufs userForDeserialization
8193
if err := json.Unmarshal(data, &ufs); err != nil {
8294
return err
8395
}
8496
if !ufs.Key.IsDefined() {
85-
return errMissingKey
97+
return ErrMissingKey()
8698
}
8799
*u = User{
88100
key: ufs.Key.StringValue(),

lduser/user_serialization_test.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -150,10 +150,19 @@ func TestJSONUnmarshalPrivateAttributes(t *testing.T) {
150150
assert.True(t, user2.IsPrivateAttribute("name"))
151151
}
152152

153-
func TestJSONUnmarshalMalformedData(t *testing.T) {
153+
func TestJSONUnmarshalDataWithWrongFieldType(t *testing.T) {
154154
var user User
155155
err := json.Unmarshal([]byte(`{"key":[1,2,3]}`), &user)
156-
assert.Error(t, err)
156+
assert.IsType(t, &json.UnmarshalTypeError{}, err)
157+
}
158+
159+
func TestJSONUnmarshalNonStructData(t *testing.T) {
160+
var user User
161+
assert.IsType(t, &json.UnmarshalTypeError{}, json.Unmarshal([]byte(`null`), &user))
162+
assert.IsType(t, &json.UnmarshalTypeError{}, json.Unmarshal([]byte(`true`), &user))
163+
assert.IsType(t, &json.UnmarshalTypeError{}, json.Unmarshal([]byte(`3`), &user))
164+
assert.IsType(t, &json.UnmarshalTypeError{}, json.Unmarshal([]byte(`"x"`), &user))
165+
assert.IsType(t, &json.UnmarshalTypeError{}, json.Unmarshal([]byte(`[]`), &user))
157166
}
158167

159168
func TestJSONUnmarshalUserKeyValidation(t *testing.T) {
@@ -177,3 +186,7 @@ func TestJSONUnmarshalUserKeyValidation(t *testing.T) {
177186
assert.Equal(t, "n", user.GetName().StringValue())
178187
})
179188
}
189+
190+
func TestMissingKeyHasErrorMessage(t *testing.T) {
191+
assert.NotEqual(t, "", ErrMissingKey().Error())
192+
}

ldvalue/optional_bool.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
package ldvalue
22

33
import (
4-
"errors"
4+
"encoding/json"
5+
"reflect"
56

67
"gopkg.in/launchdarkly/go-sdk-common.v2/jsonstream"
78
)
@@ -127,7 +128,7 @@ func (o *OptionalBool) UnmarshalJSON(data []byte) error {
127128
*o = NewOptionalBool(v.BoolValue())
128129
default:
129130
*o = OptionalBool{}
130-
return errors.New("expected boolean or null")
131+
return &json.UnmarshalTypeError{Value: string(data), Type: reflect.TypeOf(o)}
131132
}
132133
return nil
133134
}

ldvalue/optional_bool_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -113,9 +113,11 @@ func TestOptionalBoolJSONUnmarshalling(t *testing.T) {
113113

114114
err = json.Unmarshal([]byte(`3`), &o)
115115
assert.Error(t, err)
116+
assert.IsType(t, &json.UnmarshalTypeError{}, err)
116117

117118
err = json.Unmarshal([]byte(`x`), &o)
118119
assert.Error(t, err)
120+
assert.IsType(t, &json.SyntaxError{}, err)
119121

120122
var swos structWithOptionalBools
121123
err = json.Unmarshal([]byte(`{"b1":true,"b2":false,"b3":null}`), &swos)

ldvalue/optional_int.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
package ldvalue
22

33
import (
4-
"errors"
4+
"encoding/json"
5+
"reflect"
56
"strconv"
67

78
"gopkg.in/launchdarkly/go-sdk-common.v2/jsonstream"
@@ -125,7 +126,7 @@ func (o *OptionalInt) UnmarshalJSON(data []byte) error {
125126
*o = NewOptionalInt(v.IntValue())
126127
default:
127128
*o = OptionalInt{}
128-
return errors.New("expected integer or null")
129+
return &json.UnmarshalTypeError{Value: string(data), Type: reflect.TypeOf(o)}
129130
}
130131
return nil
131132
}

ldvalue/optional_int_test.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,9 +96,11 @@ func TestOptionalIntJSONUnmarshalling(t *testing.T) {
9696

9797
err = json.Unmarshal([]byte(`true`), &o)
9898
assert.Error(t, err)
99+
assert.IsType(t, &json.UnmarshalTypeError{}, err)
99100

100101
err = json.Unmarshal([]byte(`x`), &o)
101102
assert.Error(t, err)
103+
assert.IsType(t, &json.SyntaxError{}, err)
102104

103105
var swos structWithOptionalInts
104106
err = json.Unmarshal([]byte(`{"n1":3,"n3":null}`), &swos)

ldvalue/optional_string.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ package ldvalue
22

33
import (
44
"encoding/json"
5-
"errors"
5+
"reflect"
66

77
"gopkg.in/launchdarkly/go-sdk-common.v2/jsonstream"
88
)
@@ -147,7 +147,7 @@ func (o *OptionalString) UnmarshalJSON(data []byte) error {
147147
*o = NewOptionalString(v.StringValue())
148148
default:
149149
*o = OptionalString{}
150-
return errors.New("expected string or null")
150+
return &json.UnmarshalTypeError{Value: string(data), Type: reflect.TypeOf(o)}
151151
}
152152
return nil
153153
}

ldvalue/optional_string_test.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,7 @@ func TestOptionalStringJSONUnmarshalling(t *testing.T) {
104104

105105
err = json.Unmarshal([]byte("3"), &o)
106106
assert.Error(t, err)
107+
assert.IsType(t, &json.UnmarshalTypeError{}, err)
107108
assert.False(t, o.IsDefined())
108109

109110
var swos structWithOptionalStrings

ldvalue/value_serialization.go

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@ package ldvalue
33
import (
44
"encoding/json"
55
"errors"
6-
"fmt"
76
"strconv"
87

98
"gopkg.in/launchdarkly/go-sdk-common.v2/jsonstream"
@@ -144,7 +143,7 @@ func (v *Value) UnmarshalJSON(data []byte) error { //nolint:funlen // yes, we kn
144143
}
145144
return e
146145
}
147-
return fmt.Errorf("unknown JSON token: %s", data) // COVERAGE: never happens, parser rejects the token earlier
146+
return &json.SyntaxError{} // COVERAGE: never happens, parser rejects the token earlier
148147
}
149148

150149
// WriteToJSONBuffer provides JSON serialization for Value with the jsonstream API.

0 commit comments

Comments
 (0)