...

Source file src/github.com/launchdarkly/go-sdk-common/v3/lduser/user_builder.go

Documentation: github.com/launchdarkly/go-sdk-common/v3/lduser

     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  

View as plain text