1 package lduser 2 3 import ( 4 "github.com/launchdarkly/go-sdk-common/v3/ldattr" 5 "github.com/launchdarkly/go-sdk-common/v3/ldcontext" 6 "github.com/launchdarkly/go-sdk-common/v3/ldvalue" 7 ) 8 9 // NewUser creates a new user context identified by the given key. 10 // 11 // This is exactly equivalent to [ldcontext.New](key). It is provided to ease migration of code 12 // that previously used lduser instead of ldcontext. 13 func NewUser(key string) ldcontext.Context { 14 return ldcontext.New(key) 15 } 16 17 // NewAnonymousUser creates a new anonymous user context identified by the given key. 18 // 19 // This is exactly equivalent to [ldcontext.NewBuilder](key).Anonymous(true).Build(). It is provided 20 // to ease migration of code that previously used lduser instead of ldcontext. 21 func NewAnonymousUser(key string) ldcontext.Context { 22 return ldcontext.NewBuilder(key).Anonymous(true).Build() 23 } 24 25 // UserBuilder is a mutable object that uses the Builder pattern to specify properties for a user 26 // context. 27 // 28 // This is a compatibility helper that has been retained to ease migration of code from the older 29 // "user" model to the newer "context" model. See the package description of lduser for more 30 // about this. 31 // 32 // After obtaining an instance of UserBuilder by calling [NewUserBuilder], call setter methods such as 33 // UserBuilder.Name to specify any additional user properties. Then, call UserBuilder.Build to 34 // construct the [ldcontext.Context]. All of the UserBuilder setters return a reference the same builder, 35 // so they can be chained together: 36 // 37 // context := NewUserBuilder("user-key").Name("Bob").Email("test@example.com").Build() 38 // 39 // Setters for attributes that can be designated private return the type 40 // [UserBuilderCanMakeAttributePrivate], so you can chain the AsPrivateAttribute method: 41 // 42 // context := NewUserBuilder("user-key").Name("Bob").AsPrivateAttribute().Build() // Name is now private 43 // 44 // A UserBuilder should not be accessed by multiple goroutines at once. 45 // 46 // This is defined as an interface rather than a concrete type only for syntactic convenience (see 47 // [UserBuilderCanMakeAttributePrivate]). Applications should not implement this interface. 48 type UserBuilder interface { 49 // Key changes the unique key for the user being built. 50 Key(value string) UserBuilder 51 52 // IP sets the IP address attribute for the user being built. 53 IP(value string) UserBuilderCanMakeAttributePrivate 54 55 // Country sets the country attribute for the user being built. 56 Country(value string) UserBuilderCanMakeAttributePrivate 57 58 // Email sets the email attribute for the user being built. 59 Email(value string) UserBuilderCanMakeAttributePrivate 60 61 // FirstName sets the first name attribute for the user being built. 62 FirstName(value string) UserBuilderCanMakeAttributePrivate 63 64 // LastName sets the last name attribute for the user being built. 65 LastName(value string) UserBuilderCanMakeAttributePrivate 66 67 // Avatar sets the avatar URL attribute for the user being built. 68 Avatar(value string) UserBuilderCanMakeAttributePrivate 69 70 // Name sets the full name attribute for the user being built. 71 Name(value string) UserBuilderCanMakeAttributePrivate 72 73 // Anonymous sets the Anonymous attribute for the user context being built. 74 // 75 // Anonymous means that the context will not be stored in the database that appears on your 76 // LaunchDarkly dashboard. It does not imply that the context has no name; you can still set 77 // Name or any other properties you want. 78 Anonymous(value bool) UserBuilder 79 80 // Custom sets a custom attribute for the user being built. 81 // 82 // user := NewUserBuilder("user-key"). 83 // Custom("custom-attr-name", ldvalue.String("some-string-value")).AsPrivateAttribute(). 84 // Build() 85 Custom(attribute string, value ldvalue.Value) UserBuilderCanMakeAttributePrivate 86 87 // CustomAll sets all of the user's custom attributes at once from a ValueMap. 88 // 89 // UserBuilder has copy-on-write behavior to make this method efficient: if you do not make any 90 // changes to custom attributes after this, it reuses the original map rather than allocating a 91 // new one. 92 CustomAll(ldvalue.ValueMap) UserBuilderCanMakeAttributePrivate 93 94 // SetAttribute sets any attribute of the user being built, specified as a UserAttribute, to a value 95 // of type ldvalue.Value. 96 // 97 // This method corresponds to the GetAttribute method of User. It is intended for cases where user 98 // properties are being constructed generically, such as from a list of key-value pairs. Since not 99 // all attributes have the same semantics, its behavior is as follows: 100 // 101 // 1. For built-in attributes, if the value is not of a type that is supported for that attribute, 102 // the method has no effect. For Key, the only supported type is string; for Anonymous, the 103 // supported types are boolean or null; and for all other built-ins, the supported types are 104 // string or null. Custom attributes may be of any type. 105 // 106 // 2. Setting an attribute to null (ldvalue.Null() or ldvalue.Value{}) is the same as the attribute 107 // not being set in the first place. 108 // 109 // 3. The method always returns the type UserBuilderCanMakeAttributePrivate, so that you can make 110 // the attribute private if that is appropriate by calling AsPrivateAttribute(). For attributes 111 // that cannot be made private (Key and Anonymous), calling AsPrivateAttribute() on this return 112 // value will have no effect. 113 SetAttribute(attribute UserAttribute, value ldvalue.Value) UserBuilderCanMakeAttributePrivate 114 115 // Build creates a Context from the current UserBuilder properties. 116 // 117 // The Context is independent of the UserBuilder once you have called Build(); modifying the UserBuilder 118 // will not affect an already-created Context. 119 Build() ldcontext.Context 120 } 121 122 // UserBuilderCanMakeAttributePrivate is an extension of [UserBuilder] that allows attributes to be 123 // made private via UserBuilder.AsPrivateAttribute. All UserBuilderCanMakeAttributePrivate setter 124 // methods are the same as UserBuilder, and apply to the original builder. 125 // 126 // UserBuilder setter methods for attributes that can be made private always return this interface. 127 // See UserBuilder.AsPrivateAttribute for details. 128 type UserBuilderCanMakeAttributePrivate interface { 129 UserBuilder 130 131 // AsPrivateAttribute marks the last attribute that was set on this builder as being a private 132 // attribute: that is, its value will not be sent to LaunchDarkly. 133 // 134 // This action only affects analytics events that are generated by this particular user object. To 135 // mark some (or all) user attributes as private for all users, use the Config properties 136 // PrivateAttributeName and AllAttributesPrivate. 137 // 138 // Most attributes can be made private, but Key and Anonymous cannot. This is enforced by the 139 // compiler, since the builder methods for attributes that can be made private are the only ones 140 // that return UserBuilderCanMakeAttributePrivate; therefore, you cannot write an expression like 141 // NewUserBuilder("user-key").AsPrivateAttribute(). 142 // 143 // In this example, FirstName and LastName are marked as private, but Country is not: 144 // 145 // user := NewUserBuilder("user-key"). 146 // FirstName("Pierre").AsPrivateAttribute(). 147 // LastName("Menard").AsPrivateAttribute(). 148 // Country("ES"). 149 // Build() 150 AsPrivateAttribute() UserBuilder 151 152 // AsNonPrivateAttribute marks the last attribute that was set on this builder as not being a 153 // private attribute: that is, its value will be sent to LaunchDarkly and can appear on the dashboard. 154 // 155 // This is the opposite of AsPrivateAttribute(), and has no effect unless you have previously called 156 // AsPrivateAttribute() for the same attribute on the same user builder. For more details, see 157 // AsPrivateAttribute(). 158 AsNonPrivateAttribute() UserBuilder 159 } 160 161 type userBuilderImpl struct { 162 builder ldcontext.Builder 163 lastAttributeCanMakePrivate string 164 } 165 166 // NewUserBuilder constructs a new UserBuilder, specifying the user key. 167 // 168 // For authenticated users, the key may be a username or e-mail address. For anonymous users, 169 // this could be an IP address or session ID. 170 func NewUserBuilder(key string) UserBuilder { 171 b := &userBuilderImpl{} 172 b.builder.Kind("user").Key(key) 173 return b 174 } 175 176 // NewUserBuilderFromUser constructs a new UserBuilder, copying all attributes from an existing user. You may 177 // then call setter methods on the new UserBuilder to modify those attributes. 178 // 179 // Custom attributes, and the set of attribute names that are private, are implemented internally as maps. 180 // Since the User struct does not expose these maps, they are in effect immutable and will be reused from the 181 // original User rather than copied whenever possible. The UserBuilder has copy-on-write behavior so that it 182 // only makes copies of these data structures if you actually modify them. 183 func NewUserBuilderFromUser(fromUser ldcontext.Context) UserBuilder { 184 return &userBuilderImpl{builder: *(ldcontext.NewBuilderFromContext(fromUser))} 185 } 186 187 func (b *userBuilderImpl) canMakeAttributePrivate(attribute string) UserBuilderCanMakeAttributePrivate { 188 b.lastAttributeCanMakePrivate = attribute 189 return b 190 } 191 192 func (b *userBuilderImpl) Key(value string) UserBuilder { 193 b.builder.Key(value) 194 return b 195 } 196 197 func (b *userBuilderImpl) IP(value string) UserBuilderCanMakeAttributePrivate { 198 b.builder.SetString("ip", value) 199 return b.canMakeAttributePrivate(string(IPAttribute)) 200 } 201 202 func (b *userBuilderImpl) Country(value string) UserBuilderCanMakeAttributePrivate { 203 b.builder.SetString("country", value) 204 return b.canMakeAttributePrivate(string(CountryAttribute)) 205 } 206 207 func (b *userBuilderImpl) Email(value string) UserBuilderCanMakeAttributePrivate { 208 b.builder.SetString("email", value) 209 return b.canMakeAttributePrivate(string(EmailAttribute)) 210 } 211 212 func (b *userBuilderImpl) FirstName(value string) UserBuilderCanMakeAttributePrivate { 213 b.builder.SetString("firstName", value) 214 return b.canMakeAttributePrivate(string(FirstNameAttribute)) 215 } 216 217 func (b *userBuilderImpl) LastName(value string) UserBuilderCanMakeAttributePrivate { 218 b.builder.SetString("lastName", value) 219 return b.canMakeAttributePrivate(string(LastNameAttribute)) 220 } 221 222 func (b *userBuilderImpl) Avatar(value string) UserBuilderCanMakeAttributePrivate { 223 b.builder.SetString("avatar", value) 224 return b.canMakeAttributePrivate(string(AvatarAttribute)) 225 } 226 227 func (b *userBuilderImpl) Name(value string) UserBuilderCanMakeAttributePrivate { 228 b.builder.SetString("name", value) 229 return b.canMakeAttributePrivate(string(NameAttribute)) 230 } 231 232 func (b *userBuilderImpl) Anonymous(value bool) UserBuilder { 233 b.builder.Anonymous(value) 234 return b 235 } 236 237 func (b *userBuilderImpl) Custom(attribute string, value ldvalue.Value) UserBuilderCanMakeAttributePrivate { 238 b.builder.SetValue(attribute, value) 239 return b.canMakeAttributePrivate(attribute) 240 } 241 242 func (b *userBuilderImpl) CustomAll(valueMap ldvalue.ValueMap) UserBuilderCanMakeAttributePrivate { 243 // CustomAll is defined as replacing all existing custom attributes. The context builder doesn't 244 // have a method that applies to "all custom attributes" because it has a different notion of 245 // what is custom than User does, so we need to use the following awkward logic. 246 c := b.builder.Build() 247 for _, name := range c.GetOptionalAttributeNames(nil) { 248 switch name { 249 case "secondary", "name", "firstName", "lastName", "email", "country", "avatar", "ip": 250 continue 251 default: 252 b.builder.SetValue(name, ldvalue.Null()) 253 } 254 } 255 keys := make([]string, 0, 50) // arbitrary size to preallocate on stack 256 for _, k := range valueMap.Keys(keys) { 257 b.builder.SetValue(k, valueMap.Get(k)) 258 } 259 b.lastAttributeCanMakePrivate = "" 260 return b 261 } 262 263 func (b *userBuilderImpl) SetAttribute( 264 attribute UserAttribute, 265 value ldvalue.Value, 266 ) UserBuilderCanMakeAttributePrivate { 267 // The defined behavior of SetAttribute is that if it's used with the name of a built-in attribute 268 // like key or name, it modifies that attribute if and only if the value is of a compatible type. 269 // That's the same as the behavior of ldcontext.Builder.SetValue, except that UserBuilder also 270 // supports setting Secondary by name-- and, UserBuilder enforces that formerly-built-in 271 // attributes like Email can only be a string or null. 272 switch attribute { 273 case AnonymousAttribute: 274 if value.IsBool() || value.IsNull() { 275 b.builder.Anonymous(value.BoolValue()) 276 } 277 case FirstNameAttribute, LastNameAttribute, EmailAttribute, CountryAttribute, AvatarAttribute, IPAttribute: 278 if value.IsString() || value.IsNull() { 279 b.builder.SetValue(string(attribute), value) 280 } 281 default: 282 b.builder.SetValue(string(attribute), value) 283 } 284 if attribute != KeyAttribute && attribute != AnonymousAttribute { 285 return b.canMakeAttributePrivate(string(attribute)) 286 } 287 b.lastAttributeCanMakePrivate = "" 288 return b 289 } 290 291 func (b *userBuilderImpl) Build() ldcontext.Context { 292 return b.builder.Build() 293 } 294 295 func (b *userBuilderImpl) AsPrivateAttribute() UserBuilder { 296 if b.lastAttributeCanMakePrivate != "" { 297 b.builder.PrivateRef(ldattr.NewLiteralRef(b.lastAttributeCanMakePrivate)) 298 } 299 return b 300 } 301 302 func (b *userBuilderImpl) AsNonPrivateAttribute() UserBuilder { 303 if b.lastAttributeCanMakePrivate != "" { 304 b.builder.RemovePrivateRef(ldattr.NewLiteralRef(b.lastAttributeCanMakePrivate)) 305 } 306 return b 307 } 308