...

Source file src/github.com/launchdarkly/go-server-sdk/v6/interfaces/flagstate/flags_state.go

Documentation: github.com/launchdarkly/go-server-sdk/v6/interfaces/flagstate

     1  package flagstate
     2  
     3  import (
     4  	"fmt"
     5  
     6  	"github.com/launchdarkly/go-jsonstream/v3/jwriter"
     7  	"github.com/launchdarkly/go-sdk-common/v3/ldreason"
     8  	"github.com/launchdarkly/go-sdk-common/v3/ldtime"
     9  	"github.com/launchdarkly/go-sdk-common/v3/ldvalue"
    10  
    11  	"golang.org/x/exp/maps"
    12  )
    13  
    14  // AllFlags is a snapshot of the state of multiple feature flags with regard to a specific evaluation
    15  // context. This is the return type of LDClient.AllFlagsState().
    16  //
    17  // Serializing this object to JSON using json.Marshal() will produce the appropriate data structure for
    18  // bootstrapping the LaunchDarkly JavaScript client.
    19  type AllFlags struct {
    20  	flags map[string]FlagState
    21  	valid bool
    22  }
    23  
    24  // AllFlagsBuilder is a builder that creates AllFlags instances. This is normally done only by the SDK, but
    25  // it may also be used in test code.
    26  //
    27  // AllFlagsBuilder methods should not be used concurrently from multiple goroutines.
    28  type AllFlagsBuilder struct {
    29  	state   AllFlags
    30  	options allFlagsOptions
    31  }
    32  
    33  type allFlagsOptions struct {
    34  	withReasons          bool
    35  	detailsOnlyIfTracked bool
    36  }
    37  
    38  // FlagState represents the state of an individual feature flag, with regard to a specific evaluation
    39  // context, at the time when LDClient.AllFlagsState() was called.
    40  type FlagState struct {
    41  	// Value is the result of evaluating the flag for the specified evaluation context.
    42  	Value ldvalue.Value
    43  
    44  	// Variation is the variation index that was selected for the specified evaluation context.
    45  	Variation ldvalue.OptionalInt
    46  
    47  	// Version is the flag's version number when it was evaluated. This is an int rather than an OptionalInt
    48  	// because a flag always has a version and nonexistent flag keys are not included in AllFlags.
    49  	Version int
    50  
    51  	// Reason is the evaluation reason from evaluating the flag.
    52  	Reason ldreason.EvaluationReason
    53  
    54  	// TrackEvents is true if a full feature event must be sent whenever evaluating this flag. This will be
    55  	// true if tracking was explicitly enabled for this flag for data export, or if the evaluation involved
    56  	// an experiment, or both.
    57  	TrackEvents bool
    58  
    59  	// TrackReason is true if the evaluation reason should always be included in any full feature event
    60  	// created for this flag, regardless of whether variationDetail was called. This will be true if the
    61  	// evaluation involved an experiment.
    62  	TrackReason bool
    63  
    64  	// DebugEventsUntilDate is non-zero if event debugging is enabled for this flag until the specified time.
    65  	DebugEventsUntilDate ldtime.UnixMillisecondTime
    66  
    67  	// OmitDetails is true if, based on the options passed to AllFlagsState and the flag state, some of the
    68  	// metadata can be left out of the JSON representation.
    69  	OmitDetails bool
    70  }
    71  
    72  // Option is the interface for optional parameters that can be passed to LDClient.AllFlagsState.
    73  type Option interface {
    74  	fmt.Stringer
    75  	apply(*allFlagsOptions)
    76  }
    77  
    78  type clientSideOnlyOption struct{}
    79  type withReasonsOption struct{}
    80  type detailsOnlyForTrackedFlagsOption struct{}
    81  
    82  // OptionClientSideOnly is an option that can be passed to LDClient.AllFlagsState().
    83  //
    84  // It specifies that only flags marked for use with the client-side SDK should be included in the state
    85  // object. By default, all flags are included.
    86  func OptionClientSideOnly() Option {
    87  	return clientSideOnlyOption{}
    88  }
    89  
    90  // OptionWithReasons is an option that can be passed to LDClient.AllFlagsState(). It specifies that
    91  // evaluation reasons should be included in the state object. By default, they are not.
    92  func OptionWithReasons() Option {
    93  	return withReasonsOption{}
    94  }
    95  
    96  // OptionDetailsOnlyForTrackedFlags is an option that can be passed to LDClient.AllFlagsState(). It
    97  // specifies that any flag metadata that is normally only used for event generation - such as flag versions
    98  // and evaluation reasons - should be omitted for any flag that does not have event tracking or debugging
    99  // turned on. This reduces the size of the JSON data if you are passing the flag state to the front end.
   100  func OptionDetailsOnlyForTrackedFlags() Option {
   101  	return detailsOnlyForTrackedFlagsOption{}
   102  }
   103  
   104  // IsValid returns true if the call to LDClient.AllFlagsState() succeeded. It returns false if there was an
   105  // error (such as the data store not being available), in which case no flag data is in this object.
   106  func (a AllFlags) IsValid() bool {
   107  	return a.valid
   108  }
   109  
   110  // GetFlag looks up information for a specific flag by key. The returned FlagState struct contains the flag
   111  // flag evaluation result and flag metadata that was recorded when LDClient.AllFlagsState() was called. The
   112  // second return value is true if successful, or false if there was no such flag.
   113  func (a AllFlags) GetFlag(flagKey string) (FlagState, bool) {
   114  	f, ok := a.flags[flagKey]
   115  	return f, ok
   116  }
   117  
   118  // GetValue returns the value of an individual feature flag at the time the state was recorded. The return
   119  // value will be ldvalue.Null() if the flag returned the default value, or if there was no such flag.
   120  //
   121  // This is equivalent to calling GetFlag for the flag and then getting the Value property.
   122  func (a AllFlags) GetValue(flagKey string) ldvalue.Value {
   123  	return a.flags[flagKey].Value
   124  }
   125  
   126  // ToValuesMap returns a map of flag keys to flag values. If a flag would have evaluated to the default
   127  // value, its value will be ldvalue.Null().
   128  //
   129  // Do not use this method if you are passing data to the front end to "bootstrap" the JavaScript client.
   130  // Instead, convert the state object to JSON using json.Marshal.
   131  func (a AllFlags) ToValuesMap() map[string]ldvalue.Value {
   132  	ret := make(map[string]ldvalue.Value, len(a.flags))
   133  	for k, v := range a.flags {
   134  		ret[k] = v.Value
   135  	}
   136  	return ret
   137  }
   138  
   139  // MarshalJSON implements a custom JSON serialization for AllFlags, to produce the correct data structure
   140  // for "bootstrapping" the LaunchDarkly JavaScript client.
   141  func (a AllFlags) MarshalJSON() ([]byte, error) {
   142  	w := jwriter.NewWriter()
   143  	obj := w.Object()
   144  	obj.Name("$valid").Bool(a.valid)
   145  	for key, flag := range a.flags {
   146  		flag.Value.WriteToJSONWriter(obj.Name(key))
   147  	}
   148  	stateObj := obj.Name("$flagsState").Object()
   149  	for key, flag := range a.flags {
   150  		flagObj := stateObj.Name(key).Object()
   151  		flagObj.Maybe("variation", flag.Variation.IsDefined()).Int(flag.Variation.IntValue())
   152  		flagObj.Maybe("version", !flag.OmitDetails).Int(flag.Version)
   153  		if flag.Reason.IsDefined() && !flag.OmitDetails {
   154  			flag.Reason.WriteToJSONWriter(flagObj.Name("reason"))
   155  		}
   156  		flagObj.Maybe("trackEvents", flag.TrackEvents).Bool(flag.TrackEvents)
   157  		flagObj.Maybe("trackReason", flag.TrackReason).Bool(flag.TrackReason)
   158  		flagObj.Maybe("debugEventsUntilDate", flag.DebugEventsUntilDate > 0).Float64(float64(flag.DebugEventsUntilDate))
   159  		flagObj.End()
   160  	}
   161  	stateObj.End()
   162  	obj.End()
   163  	return w.Bytes(), w.Error()
   164  }
   165  
   166  // NewAllFlagsBuilder creates a builder for constructing an AllFlags instance. This is normally done only by
   167  // the SDK, but it may also be used in test code.
   168  func NewAllFlagsBuilder(options ...Option) *AllFlagsBuilder {
   169  	b := &AllFlagsBuilder{
   170  		state: AllFlags{
   171  			flags: make(map[string]FlagState),
   172  			valid: true,
   173  		},
   174  	}
   175  	for _, o := range options {
   176  		o.apply(&b.options)
   177  	}
   178  	return b
   179  }
   180  
   181  // Build returns an immutable State instance copied from the current builder data.
   182  func (b *AllFlagsBuilder) Build() AllFlags {
   183  	return AllFlags{valid: b.state.valid, flags: maps.Clone(b.state.flags)}
   184  }
   185  
   186  // AddFlag adds information about a flag.
   187  //
   188  // The Reason property in the FlagState may or may not be recorded in the State, depending on the builder
   189  // options.
   190  func (b *AllFlagsBuilder) AddFlag(flagKey string, flag FlagState) *AllFlagsBuilder {
   191  	// To save bandwidth, we include evaluation reasons only if 1. the application explicitly said to
   192  	// include them or 2. they must be included because of experimentation
   193  	if b.options.detailsOnlyIfTracked {
   194  		if !flag.TrackEvents && !flag.TrackReason &&
   195  			!(flag.DebugEventsUntilDate != 0 && flag.DebugEventsUntilDate > ldtime.UnixMillisNow()) {
   196  			flag.OmitDetails = true
   197  		}
   198  	}
   199  	if !b.options.withReasons && !flag.TrackReason {
   200  		flag.Reason = ldreason.EvaluationReason{}
   201  	}
   202  	b.state.flags[flagKey] = flag
   203  	return b
   204  }
   205  
   206  func (o clientSideOnlyOption) String() string {
   207  	return "ClientSideOnly"
   208  }
   209  
   210  func (o clientSideOnlyOption) apply(options *allFlagsOptions) {
   211  }
   212  
   213  func (o withReasonsOption) String() string {
   214  	return "WithReasons"
   215  }
   216  
   217  func (o withReasonsOption) apply(options *allFlagsOptions) {
   218  	options.withReasons = true
   219  }
   220  
   221  func (o detailsOnlyForTrackedFlagsOption) String() string {
   222  	return "DetailsOnlyForTrackedFlags"
   223  }
   224  
   225  func (o detailsOnlyForTrackedFlagsOption) apply(options *allFlagsOptions) {
   226  	options.detailsOnlyIfTracked = true
   227  }
   228  

View as plain text