Skip to content

Commit a6231ac

Browse files
prepare 2.1.0 release (#13)
1 parent cb4476b commit a6231ac

18 files changed

+1940
-666
lines changed

ldreason/reason.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ type EvaluationReason struct {
6464
errorKind EvalErrorKind
6565
}
6666

67+
// IsDefined returns true if this EvaluationReason has a non-empty GetKind(). It is false for a
68+
// zero value of EvaluationReason{}.
69+
func (r EvaluationReason) IsDefined() bool {
70+
return r.kind != ""
71+
}
72+
6773
// String returns a concise string representation of the reason. Examples: "OFF", "ERROR(WRONG_TYPE)".
6874
func (r EvaluationReason) String() string {
6975
switch r.kind {

ldreason/reason_test.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,16 @@ import (
99
"gopkg.in/launchdarkly/go-sdk-common.v2/jsonstream"
1010
)
1111

12+
func TestReasonIsDefined(t *testing.T) {
13+
assert.False(t, EvaluationReason{}.IsDefined())
14+
assert.True(t, NewEvalReasonOff().IsDefined())
15+
assert.True(t, NewEvalReasonFallthrough().IsDefined())
16+
assert.True(t, NewEvalReasonTargetMatch().IsDefined())
17+
assert.True(t, NewEvalReasonRuleMatch(0, "").IsDefined())
18+
assert.True(t, NewEvalReasonPrerequisiteFailed("").IsDefined())
19+
assert.True(t, NewEvalReasonError(EvalErrorFlagNotFound).IsDefined())
20+
}
21+
1222
func TestReasonKind(t *testing.T) {
1323
assert.Equal(t, EvalReasonOff, NewEvalReasonOff().GetKind())
1424
assert.Equal(t, EvalReasonFallthrough, NewEvalReasonFallthrough().GetKind())

ldtime/unix_millis.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,11 @@ func UnixMillisFromTime(t time.Time) UnixMillisecondTime {
1515
func UnixMillisNow() UnixMillisecondTime {
1616
return UnixMillisFromTime(time.Now())
1717
}
18+
19+
// IsDefined returns true if the time value is non-zero.
20+
//
21+
// This can be used to treat a zero value as "undefined" as an alternative to using a pointer,
22+
// assuming that the exact beginning of the Unix epoch itself is not a valid time in this context.
23+
func (t UnixMillisecondTime) IsDefined() bool {
24+
return t > 0
25+
}

ldtime/unix_millis_test.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,3 +18,8 @@ func TestUnixMillisNow(t *testing.T) {
1818
un := UnixMillisNow()
1919
assert.GreaterOrEqual(t, uint64(un), uint64(UnixMillisFromTime(tn)))
2020
}
21+
22+
func TestUnixMillisIsDefined(t *testing.T) {
23+
assert.False(t, UnixMillisecondTime(0).IsDefined())
24+
assert.True(t, UnixMillisecondTime(1).IsDefined())
25+
}

lduser/user.go

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ type User struct {
5858
avatar ldvalue.OptionalString
5959
name ldvalue.OptionalString
6060
anonymous ldvalue.OptionalBool
61-
custom ldvalue.Value
61+
custom ldvalue.ValueMap
6262
privateAttributes map[UserAttribute]struct{}
6363
}
6464

@@ -168,14 +168,19 @@ func (u User) GetAnonymousOptional() (bool, bool) {
168168
// the desired type, rather than casting it. If the attribute did not exist, the value will be
169169
// ldvalue.Null() and the second return value will be false.
170170
func (u User) GetCustom(attribute string) (ldvalue.Value, bool) {
171-
return u.custom.TryGetByKey(attribute)
171+
return u.custom.TryGet(attribute)
172172
}
173173

174174
// GetAllCustom returns all of the user's custom attributes.
175175
//
176176
// These are represented as a Value that is either an object (with a key-value pair for each attribute)
177177
// or Null() if there are no custom attributes.
178178
func (u User) GetAllCustom() ldvalue.Value {
179+
return u.custom.AsValue()
180+
}
181+
182+
// GetAllCustomMap returns all of the user's custom attributes as an immutable ValueMap.
183+
func (u User) GetAllCustomMap() ldvalue.ValueMap {
179184
return u.custom
180185
}
181186

lduser/user_builder.go

Lines changed: 61 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ func NewAnonymousUser(key string) User {
2828
// user := NewUserBuilder("user-key").Name("Bob").AsPrivateAttribute().Build() // Name is now private
2929
//
3030
// A UserBuilder should not be accessed by multiple goroutines at once.
31+
//
32+
// This is defined as an interface rather than a concrete type only for syntactic convenience (see
33+
// UserBuilderCanMakeAttributePrivate). Applications should not implement this interface since the package
34+
// may add methods to it in the future.
3135
type UserBuilder interface {
3236
// Key changes the unique key for the user being built.
3337
Key(value string) UserBuilder
@@ -73,6 +77,13 @@ type UserBuilder interface {
7377
// Build()
7478
Custom(attribute string, value ldvalue.Value) UserBuilderCanMakeAttributePrivate
7579

80+
// CustomAll sets all of the user's custom attributes at once from a ValueMap.
81+
//
82+
// UserBuilder has copy-on-write behavior to make this method efficient: if you do not make any
83+
// changes to custom attributes after this, it reuses the original map rather than allocating a
84+
// new one.
85+
CustomAll(ldvalue.ValueMap) UserBuilderCanMakeAttributePrivate
86+
7687
// SetAttribute sets any attribute of the user being built, specified as a UserAttribute, to a value
7788
// of type ldvalue.Value.
7889
//
@@ -151,8 +162,9 @@ type userBuilderImpl struct {
151162
avatar ldvalue.OptionalString
152163
name ldvalue.OptionalString
153164
anonymous ldvalue.OptionalBool
154-
custom ldvalue.ObjectBuilder
165+
custom ldvalue.ValueMapBuilder
155166
privateAttrs map[UserAttribute]struct{}
167+
privateAttrsCopyOnWrite bool
156168
lastAttributeCanMakePrivate UserAttribute
157169
}
158170

@@ -166,31 +178,28 @@ func NewUserBuilder(key string) UserBuilder {
166178

167179
// NewUserBuilderFromUser constructs a new UserBuilder, copying all attributes from an existing user. You may
168180
// then call setter methods on the new UserBuilder to modify those attributes.
181+
//
182+
// Custom attributes, and the set of attribute names that are private, are implemented internally as maps.
183+
// Since the User struct does not expose these maps, they are in effect immutable and will be reused from the
184+
// original User rather than copied whenever possible. The UserBuilder has copy-on-write behavior so that it
185+
// only makes copies of these data structures if you actually modify them.
169186
func NewUserBuilderFromUser(fromUser User) UserBuilder {
170187
builder := &userBuilderImpl{
171-
key: fromUser.key,
172-
secondary: fromUser.secondary,
173-
ip: fromUser.ip,
174-
country: fromUser.country,
175-
email: fromUser.email,
176-
firstName: fromUser.firstName,
177-
lastName: fromUser.lastName,
178-
avatar: fromUser.avatar,
179-
name: fromUser.name,
180-
anonymous: fromUser.anonymous,
188+
key: fromUser.key,
189+
secondary: fromUser.secondary,
190+
ip: fromUser.ip,
191+
country: fromUser.country,
192+
email: fromUser.email,
193+
firstName: fromUser.firstName,
194+
lastName: fromUser.lastName,
195+
avatar: fromUser.avatar,
196+
name: fromUser.name,
197+
anonymous: fromUser.anonymous,
198+
privateAttrs: fromUser.privateAttributes,
199+
privateAttrsCopyOnWrite: true,
181200
}
182201
if fromUser.custom.Count() > 0 {
183-
builder.custom = ldvalue.ObjectBuildWithCapacity(fromUser.custom.Count())
184-
fromUser.custom.Enumerate(func(index int, key string, value ldvalue.Value) bool {
185-
builder.custom.Set(key, value)
186-
return true
187-
})
188-
}
189-
if len(fromUser.privateAttributes) > 0 {
190-
builder.privateAttrs = make(map[UserAttribute]struct{}, len(fromUser.privateAttributes))
191-
for name := range fromUser.privateAttributes {
192-
builder.privateAttrs[name] = struct{}{}
193-
}
202+
builder.custom = ldvalue.ValueMapBuildFromMap(fromUser.custom)
194203
}
195204
return builder
196205
}
@@ -252,12 +261,22 @@ func (b *userBuilderImpl) Anonymous(value bool) UserBuilder {
252261

253262
func (b *userBuilderImpl) Custom(attribute string, value ldvalue.Value) UserBuilderCanMakeAttributePrivate {
254263
if b.custom == nil {
255-
b.custom = ldvalue.ObjectBuild()
264+
b.custom = ldvalue.ValueMapBuild()
256265
}
257266
b.custom.Set(attribute, value)
258267
return b.canMakeAttributePrivate(UserAttribute(attribute))
259268
}
260269

270+
func (b *userBuilderImpl) CustomAll(valueMap ldvalue.ValueMap) UserBuilderCanMakeAttributePrivate {
271+
if valueMap.Count() == 0 {
272+
b.custom = nil
273+
} else {
274+
b.custom = ldvalue.ValueMapBuildFromMap(valueMap)
275+
}
276+
b.lastAttributeCanMakePrivate = ""
277+
return b
278+
}
279+
261280
func (b *userBuilderImpl) SetAttribute(
262281
attribute UserAttribute,
263282
value ldvalue.Value,
@@ -312,36 +331,38 @@ func (b *userBuilderImpl) SetAttribute(
312331

313332
func (b *userBuilderImpl) Build() User {
314333
u := User{
315-
key: b.key,
316-
secondary: b.secondary,
317-
ip: b.ip,
318-
country: b.country,
319-
email: b.email,
320-
firstName: b.firstName,
321-
lastName: b.lastName,
322-
avatar: b.avatar,
323-
name: b.name,
324-
anonymous: b.anonymous,
334+
key: b.key,
335+
secondary: b.secondary,
336+
ip: b.ip,
337+
country: b.country,
338+
email: b.email,
339+
firstName: b.firstName,
340+
lastName: b.lastName,
341+
avatar: b.avatar,
342+
name: b.name,
343+
anonymous: b.anonymous,
344+
privateAttributes: b.privateAttrs,
325345
}
326346
if b.custom != nil {
327347
u.custom = b.custom.Build()
328348
}
329-
if len(b.privateAttrs) > 0 {
330-
p := make(map[UserAttribute]struct{}, len(b.privateAttrs))
331-
for key := range b.privateAttrs {
332-
p[key] = struct{}{}
333-
}
334-
u.privateAttributes = p
335-
}
349+
b.privateAttrsCopyOnWrite = true
336350
return u
337351
}
338352

339353
func (b *userBuilderImpl) AsPrivateAttribute() UserBuilder {
340354
if b.lastAttributeCanMakePrivate != "" {
341355
if b.privateAttrs == nil {
342356
b.privateAttrs = make(map[UserAttribute]struct{})
357+
} else if b.privateAttrsCopyOnWrite {
358+
copied := make(map[UserAttribute]struct{}, len(b.privateAttrs))
359+
for name := range b.privateAttrs {
360+
copied[name] = struct{}{}
361+
}
362+
b.privateAttrs = copied
343363
}
344364
b.privateAttrs[b.lastAttributeCanMakePrivate] = struct{}{}
365+
b.privateAttrsCopyOnWrite = false
345366
}
346367
return b
347368
}

0 commit comments

Comments
 (0)