Skip to content

Commit

Permalink
User model improvements related to custom/private attributes
Browse files Browse the repository at this point in the history
  • Loading branch information
eli-darkly committed Jan 29, 2020
1 parent 7a480b8 commit 4058727
Show file tree
Hide file tree
Showing 4 changed files with 52 additions and 50 deletions.
35 changes: 15 additions & 20 deletions lduser/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ type User struct {
avatar ldvalue.OptionalString
name ldvalue.OptionalString
anonymous ldvalue.Value
custom map[UserAttribute]ldvalue.Value
custom ldvalue.Value
privateAttributes map[UserAttribute]struct{}
}

Expand Down Expand Up @@ -92,7 +92,7 @@ func (u User) GetAttribute(attribute UserAttribute) ldvalue.Value {
case AnonymousAttribute:
return u.anonymous
default:
value, _ := u.GetCustom(attribute)
value, _ := u.GetCustom(string(attribute))
return value
}
}
Expand Down Expand Up @@ -167,20 +167,16 @@ func (u User) GetAnonymousOptional() (bool, bool) {
// boolean, number, string, array (slice), or object (map). Use Value methods to access the value as
// the desired type, rather than casting it. If the attribute did not exist, the value will be
// ldvalue.Null() and the second return value will be false.
func (u User) GetCustom(attribute UserAttribute) (ldvalue.Value, bool) {
value, found := u.custom[attribute]
return value, found
func (u User) GetCustom(attribute string) (ldvalue.Value, bool) {
return u.custom.TryGetByKey(attribute)
}

// ForCustom iterates through all custom attributes that have been set on this user.
// GetAllCustom returns all of the user's custom attributes.
//
// The specified function receives each custom attribute name and the corresponding value. This avoids
// the overhead of returning the attributes as a map, since to preserve immutability the map would
// have to be copied.
func (u User) ForCustom(fn func(string, ldvalue.Value)) {
for key, value := range u.custom {
fn(string(key), value)
}
// These are represented as a Value that is either an object (with a key-value pair for each attribute)
// or Null() if there are no custom attributes.
func (u User) GetAllCustom() ldvalue.Value {
return u.custom
}

// IsPrivateAttribute tests whether the given attribute is private for this user.
Expand All @@ -191,6 +187,11 @@ func (u User) IsPrivateAttribute(attribute UserAttribute) bool {
return ok
}

// HasPrivateAttributes returns true if any attribute were marked private or this user.
func (u User) HasPrivateAttributes() bool {
return len(u.privateAttributes) > 0
}

// Equal tests whether two users have equal attributes.
//
// Regular struct equality comparison is not allowed for User because it can contain slices and
Expand All @@ -209,15 +210,9 @@ func (u User) Equal(other User) bool {
!u.anonymous.Equal(other.anonymous) {
return false
}
if len(u.custom) != len(other.custom) {
if !u.custom.Equal(other.custom) {
return false
}
for k, v := range u.custom {
v1, ok := other.custom[k]
if !ok || !v.Equal(v1) {
return false
}
}
if len(u.privateAttributes) != len(other.privateAttributes) {
return false
}
Expand Down
25 changes: 11 additions & 14 deletions lduser/user_builder.go
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,7 @@ type userBuilderImpl struct {
avatar ldvalue.OptionalString
name ldvalue.OptionalString
anonymous ldvalue.Value
custom map[UserAttribute]ldvalue.Value
custom ldvalue.ObjectBuilder
privateAttrs map[UserAttribute]struct{}
}

Expand Down Expand Up @@ -162,11 +162,12 @@ func NewUserBuilderFromUser(fromUser User) UserBuilder {
name: fromUser.name,
anonymous: fromUser.anonymous,
}
if fromUser.custom != nil {
builder.custom = make(map[UserAttribute]ldvalue.Value, len(fromUser.custom))
for k, v := range fromUser.custom {
builder.custom[k] = v
}
if fromUser.custom.Count() > 0 {
builder.custom = ldvalue.ObjectBuildWithCapacity(fromUser.custom.Count())
fromUser.custom.Enumerate(func(index int, key string, value ldvalue.Value) bool {
builder.custom.Set(key, value)
return true
})
}
if len(fromUser.privateAttributes) > 0 {
builder.privateAttrs = make(map[UserAttribute]struct{}, len(fromUser.privateAttributes))
Expand Down Expand Up @@ -233,9 +234,9 @@ func (b *userBuilderImpl) Anonymous(value bool) UserBuilder {

func (b *userBuilderImpl) Custom(attribute string, value ldvalue.Value) UserBuilderCanMakeAttributePrivate {
if b.custom == nil {
b.custom = make(map[UserAttribute]ldvalue.Value)
b.custom = ldvalue.ObjectBuild()
}
b.custom[UserAttribute(attribute)] = value
b.custom.Set(attribute, value)
return b.canMakeAttributePrivate(UserAttribute(attribute))
}

Expand All @@ -252,12 +253,8 @@ func (b *userBuilderImpl) Build() User {
name: b.name,
anonymous: b.anonymous,
}
if len(b.custom) > 0 {
c := make(map[UserAttribute]ldvalue.Value, len(b.custom))
for k, v := range b.custom {
c[k] = v
}
u.custom = c
if b.custom != nil {
u.custom = b.custom.Build()
}
if len(b.privateAttrs) > 0 {
p := make(map[UserAttribute]struct{}, len(b.privateAttrs))
Expand Down
24 changes: 12 additions & 12 deletions lduser/user_serialization.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,18 +7,18 @@ import (
)

type userForSerialization struct {
Key string `json:"key"`
Secondary ldvalue.OptionalString `json:"secondary"`
IP ldvalue.OptionalString `json:"ip"`
Country ldvalue.OptionalString `json:"country"`
Email ldvalue.OptionalString `json:"email"`
FirstName ldvalue.OptionalString `json:"firstName"`
LastName ldvalue.OptionalString `json:"lastName"`
Avatar ldvalue.OptionalString `json:"avatar"`
Name ldvalue.OptionalString `json:"name"`
Anonymous ldvalue.Value `json:"anonymous"`
Custom map[UserAttribute]ldvalue.Value `json:"custom"`
PrivateAttributeNames []UserAttribute `json:"privateAttributeNames"`
Key string `json:"key"`
Secondary ldvalue.OptionalString `json:"secondary"`
IP ldvalue.OptionalString `json:"ip"`
Country ldvalue.OptionalString `json:"country"`
Email ldvalue.OptionalString `json:"email"`
FirstName ldvalue.OptionalString `json:"firstName"`
LastName ldvalue.OptionalString `json:"lastName"`
Avatar ldvalue.OptionalString `json:"avatar"`
Name ldvalue.OptionalString `json:"name"`
Anonymous ldvalue.Value `json:"anonymous"`
Custom ldvalue.Value `json:"custom"`
PrivateAttributeNames []UserAttribute `json:"privateAttributeNames"`
}

// String returns a simple string representation of a user.
Expand Down
18 changes: 14 additions & 4 deletions lduser/user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,8 +57,9 @@ func assertStringPropertiesNotSet(t *testing.T, user User) {

func getCustomAttrs(user User) []string {
var ret []string
user.ForCustom(func(a string, v ldvalue.Value) {
user.GetAllCustom().Enumerate(func(i int, a string, v ldvalue.Value) bool {
ret = append(ret, a)
return true
})
sort.Strings(ret)
return ret
Expand All @@ -71,10 +72,11 @@ func getPrivateAttrs(user User) []string {
ret = append(ret, string(a))
}
}
user.ForCustom(func(a string, v ldvalue.Value) {
user.GetAllCustom().Enumerate(func(i int, a string, v ldvalue.Value) bool {
if user.IsPrivateAttribute(UserAttribute(a)) {
ret = append(ret, a)
}
return true
})
sort.Strings(ret)
return ret
Expand All @@ -95,6 +97,7 @@ func TestNewUser(t *testing.T) {

assert.Nil(t, getCustomAttrs(user))
assert.Nil(t, getPrivateAttrs(user))
assert.False(t, user.HasPrivateAttributes())
}

func TestNewAnonymousUser(t *testing.T) {
Expand All @@ -112,6 +115,7 @@ func TestNewAnonymousUser(t *testing.T) {

assert.Nil(t, getCustomAttrs(user))
assert.Nil(t, getPrivateAttrs(user))
assert.False(t, user.HasPrivateAttributes())
}

func TestUserBuilderSetsOnlyKeyByDefault(t *testing.T) {
Expand All @@ -129,6 +133,7 @@ func TestUserBuilderSetsOnlyKeyByDefault(t *testing.T) {

assert.Nil(t, getCustomAttrs(user))
assert.Nil(t, getPrivateAttrs(user))
assert.False(t, user.HasPrivateAttributes())
}

func TestUserBuilderCanSetStringAttributes(t *testing.T) {
Expand Down Expand Up @@ -196,6 +201,7 @@ func TestUserBuilderCanSetPrivateStringAttributes(t *testing.T) {

assert.Nil(t, getCustomAttrs(user))
assert.Equal(t, []string{string(a)}, getPrivateAttrs(user))
assert.True(t, user.HasPrivateAttributes())
})
}
}
Expand All @@ -209,6 +215,7 @@ func TestUserBuilderCanMakeAttributeNonPrivate(t *testing.T) {
user := builder.Build()
assert.Equal(t, "f", user.GetEmail().StringValue())
assert.Equal(t, []string{"name"}, getPrivateAttrs(user))
assert.True(t, user.HasPrivateAttributes())
}

func TestUserBuilderCanSetCustomAttributes(t *testing.T) {
Expand Down Expand Up @@ -256,11 +263,12 @@ func TestUserBuilderCanSetAttributesAfterSettingAttributeThatCanBePrivate(t *tes
}
}

func TestIterateCustomAttributes(t *testing.T) {
func TestEnumerateCustomAttributes(t *testing.T) {
user := NewUserBuilder("some-key").Custom("first", ldvalue.Int(1)).Custom("second", ldvalue.String("two")).Build()
m := make(map[string]ldvalue.Value)
user.ForCustom(func(a string, v ldvalue.Value) {
user.GetAllCustom().Enumerate(func(i int, a string, v ldvalue.Value) bool {
m[a] = v
return true
})
assert.Equal(t, map[string]ldvalue.Value{"first": ldvalue.Int(1), "second": ldvalue.String("two")}, m)
}
Expand Down Expand Up @@ -296,6 +304,7 @@ func TestUserBuilderCanSetPrivateCustomAttributes(t *testing.T) {
assert.Equal(t, []string{"first", "second"}, getCustomAttrs(user))

assert.Equal(t, []string{"first"}, getPrivateAttrs(user))
assert.True(t, user.HasPrivateAttributes())
}

func TestUserBuilderCanCopyFromExistingUserWithOnlyKey(t *testing.T) {
Expand All @@ -307,6 +316,7 @@ func TestUserBuilderCanCopyFromExistingUserWithOnlyKey(t *testing.T) {
assertStringPropertiesNotSet(t, user1)
assert.Nil(t, getCustomAttrs(user1))
assert.Nil(t, getPrivateAttrs(user1))
assert.False(t, user1.HasPrivateAttributes())
}

func TestUserBuilderCanCopyFromExistingUserWithAllAttributes(t *testing.T) {
Expand Down

0 comments on commit 4058727

Please sign in to comment.