@@ -2,7 +2,7 @@ package ldcontext
22
33import (
44 "fmt"
5- "net/url "
5+ "strings "
66
77 "github.com/launchdarkly/go-sdk-common/v3/ldattr"
88 "github.com/launchdarkly/go-sdk-common/v3/lderrors"
@@ -279,8 +279,9 @@ func (b *Builder) SetString(attributeName string, value string) *Builder {
279279// SetValue sets the value of any attribute for the Context.
280280//
281281// This includes only attributes that are addressable in evaluations-- not metadata such as
282- // Secondary() or Private(). If attributeName is "secondary" or "privateAttributes", it is
283- // ignored and no attribute is set.
282+ // Secondary() or Private(). If attributeName is "secondary" or "privateAttributes", you will be
283+ // setting an attribute with that name which you can use in evaluations or to record data for
284+ // your own purposes, but it will be unrelated to Secondary() and Private().
284285//
285286// This method uses the ldvalue.Value type to represent a value of any JSON type: null, boolean,
286287// number, string, array, or object. For all attribute names that do not have special meaning
@@ -297,6 +298,9 @@ func (b *Builder) SetString(attributeName string, value string) *Builder {
297298//
298299// - "anonymous": Must be a boolean. See Builder.Anonymous().
299300//
301+ // The attribute name "_meta" is not allowed, because it has special meaning in the JSON
302+ // schema for contexts; any attempt to set an attribute with this name has no effect.
303+ //
300304// Values that are JSON arrays or objects have special behavior when referenced in flag/segment
301305// rules.
302306//
@@ -341,7 +345,7 @@ func (b *Builder) TrySetValue(attributeName string, value ldvalue.Value) bool {
341345 return false
342346 }
343347 b .Anonymous (value .BoolValue ())
344- case jsonPropPrivate , jsonPropSecondary :
348+ case jsonPropMeta :
345349 return false
346350 default :
347351 if value .IsNull () {
@@ -360,8 +364,10 @@ func (b *Builder) TrySetValue(attributeName string, value ldvalue.Value) bool {
360364// (https://docs.launchdarkly.com/home/flags/targeting-users#targeting-rules-based-on-user-attributes)
361365// as follows: if you have chosen to bucket users by a specific attribute, the secondary key (if set)
362366// is used to further distinguish between users who are otherwise identical according to that attribute.
363- // This value is not addressable as an attribute in evaluations: that is, a rule clause cannot use the
364- // attribute name "secondary".
367+ //
368+ // This is a metadata property, rather than an attribute that can be addressed in evaluations: that is,
369+ // a rule clause that references the attribute name "secondary" will not use this value, but instead will
370+ // use whatever value (if any) you have set for the name "secondary" with a method such as SetString.
365371//
366372// Setting this value to an empty string is not the same as leaving it unset. If you need to clear this
367373// attribute to a "no value" state, use OptSecondary().
@@ -374,9 +380,6 @@ func (b *Builder) Secondary(value string) *Builder {
374380// Calling b.OptSecondary(ldvalue.NewOptionalString("x")) is equivalent to b.Secondary("x"), but since it uses
375381// the OptionalString type, it also allows clearing a previously set name with
376382// b.OptSecondary(ldvalue.OptionalString{}).
377- //
378- // This value is not addressable as an attribute in evaluations: that is, a rule clause cannot use the
379- // attribute name "secondary".
380383func (b * Builder ) OptSecondary (value ldvalue.OptionalString ) * Builder {
381384 if b != nil {
382385 b .secondary = value
@@ -396,7 +399,7 @@ func (b *Builder) OptSecondary(value ldvalue.OptionalString) *Builder {
396399// other attributes may be included (so, for instance, Anonymous does not mean there is no Name).
397400//
398401// This value is also addressable in evaluations as the attribute name "anonymous". It is always treated as
399- // a boolean true or false in evaluations.
402+ // a boolean true or false in evaluations; it cannot be null/undefined .
400403func (b * Builder ) Anonymous (value bool ) * Builder {
401404 if b != nil {
402405 b .anonymous = value
@@ -407,9 +410,6 @@ func (b *Builder) Anonymous(value bool) *Builder {
407410// Private designates any number of Context attributes, or properties within them, as private: that is,
408411// their values will not be sent to LaunchDarkly.
409412//
410- // (TKTK: possibly move some of this conceptual information to a non-platform-specific docs page and/or
411- // have docs team copyedit it here)
412- //
413413// Each parameter can be a simple attribute name, such as "email". Or, if the first character is a slash,
414414// the parameter is interpreted as a slash-delimited path to a property within a JSON object, where the
415415// first path component is a Context attribute name and each following component is a nested property name:
@@ -434,6 +434,11 @@ func (b *Builder) Anonymous(value bool) *Builder {
434434// SetString("lastName", "Menard").
435435// Private("firstName").
436436// Build()
437+ //
438+ // This is a metadata property, rather than an attribute that can be addressed in evaluations: that is,
439+ // a rule clause that references the attribute name "private" (or "privateAttributes", as it appears
440+ // in JSON representations) will not use this value, but instead will use whatever value (if any) you
441+ // have set for that name with a method such as SetString.
437442func (b * Builder ) Private (attrRefStrings ... string ) * Builder {
438443 refs := make ([]ldattr.Ref , 0 , 20 ) // arbitrary capacity that's likely greater than needed, to preallocate on stack
439444 for _ , s := range attrRefStrings {
@@ -520,10 +525,12 @@ func (b *Builder) copyFrom(fromContext Context) {
520525func makeFullyQualifiedKeySingleKind (kind Kind , key string , omitDefaultKind bool ) string {
521526 // Per the users-to-contexts specification, the fully-qualified key for a single-kind context is:
522527 // - equal to the regular "key" property, if the kind is "user" (a.k.a. DefaultKind)
523- // - or, for any other kind, it's the kind plus ":" plus the result of URL-encoding the "key"
524- // property (the URL-encoding is to avoid ambiguity if the key contains colons).
528+ // - or, for any other kind, it's the kind plus ":" plus the result of partially URL-encoding the
529+ // "key" property ("partially URL-encoding" here means that ':' and '%' are percent-escaped; other
530+ // URL-encoding behaviors are inconsistent across platforms, so we do not use a library function).
525531 if omitDefaultKind && kind == DefaultKind {
526532 return key
527533 }
528- return fmt .Sprintf ("%s:%s" , kind , url .PathEscape (key ))
534+ escapedKey := strings .ReplaceAll (strings .ReplaceAll (key , "%" , "%25" ), ":" , "%3A" )
535+ return fmt .Sprintf ("%s:%s" , kind , escapedKey )
529536}
0 commit comments