...

Source file src/github.com/growthbook/growthbook-golang/context.go

Documentation: github.com/growthbook/growthbook-golang

     1  package growthbook
     2  
     3  import (
     4  	"encoding/json"
     5  	"net/url"
     6  	"regexp"
     7  	"time"
     8  )
     9  
    10  // ExperimentOverride provides the possibility to temporarily override
    11  // some experiment settings.
    12  type ExperimentOverride struct {
    13  	Condition Condition
    14  	Weights   []float64
    15  	Active    *bool
    16  	Status    *ExperimentStatus
    17  	Force     *int
    18  	Coverage  *float64
    19  	Groups    []string
    20  	Namespace *Namespace
    21  	URL       *regexp.Regexp
    22  }
    23  
    24  type ExperimentOverrides map[string]*ExperimentOverride
    25  
    26  // Context contains the options for creating a new GrowthBook
    27  // instance.
    28  type Context struct {
    29  	Enabled          bool
    30  	Attributes       Attributes
    31  	URL              *url.URL
    32  	Features         FeatureMap
    33  	ForcedVariations ForcedVariationsMap
    34  	QAMode           bool
    35  	DevMode          bool
    36  	TrackingCallback ExperimentCallback
    37  	OnFeatureUsage   FeatureUsageCallback
    38  	UserAttributes   Attributes
    39  	Groups           map[string]bool
    40  	APIHost          string
    41  	ClientKey        string
    42  	DecryptionKey    string
    43  	Overrides        ExperimentOverrides
    44  	CacheTTL         time.Duration
    45  }
    46  
    47  // ExperimentCallback is a callback function that is executed every
    48  // time a user is included in an Experiment. It is also the type used
    49  // for subscription functions, which are called whenever
    50  // Experiment.Run is called and the experiment result changes,
    51  // independent of whether a user is inncluded in the experiment or
    52  // not.
    53  type ExperimentCallback func(experiment *Experiment, result *Result)
    54  
    55  // FeatureUsageCallback is a callback function that is executed every
    56  // time a feature is evaluated.
    57  type FeatureUsageCallback func(key string, result *FeatureResult)
    58  
    59  // NewContext creates a context with default settings: enabled, but
    60  // all other fields empty.
    61  func NewContext() *Context {
    62  	return &Context{
    63  		Enabled:  true,
    64  		CacheTTL: 60 * time.Second,
    65  	}
    66  }
    67  
    68  // WithEnabled sets the enabled flag for a context.
    69  func (ctx *Context) WithEnabled(enabled bool) *Context {
    70  	ctx.Enabled = enabled
    71  	return ctx
    72  }
    73  
    74  // WithAttributes sets the attributes for a context.
    75  func (ctx *Context) WithAttributes(attributes Attributes) *Context {
    76  	savedAttributes := Attributes{}
    77  	for k, v := range attributes {
    78  		savedAttributes[k] = fixSliceTypes(v)
    79  	}
    80  	ctx.Attributes = savedAttributes
    81  	return ctx
    82  }
    83  
    84  // WithUserAttributes sets the user attributes for a context.
    85  func (ctx *Context) WithUserAttributes(attributes Attributes) *Context {
    86  	savedAttributes := Attributes{}
    87  	for k, v := range attributes {
    88  		savedAttributes[k] = fixSliceTypes(v)
    89  	}
    90  	ctx.UserAttributes = savedAttributes
    91  	return ctx
    92  }
    93  
    94  // WithURL sets the URL for a context.
    95  func (ctx *Context) WithURL(url *url.URL) *Context {
    96  	ctx.URL = url
    97  	return ctx
    98  }
    99  
   100  // WithFeatures sets the features for a context (as a value of type
   101  // FeatureMap, which is a map from feature names to *Feature values).
   102  func (ctx *Context) WithFeatures(features FeatureMap) *Context {
   103  	ctx.Features = features
   104  	return ctx
   105  }
   106  
   107  // WithForcedVariations sets the forced variations for a context (as a
   108  // value of type ForcedVariationsMap, which is a map from experiment
   109  // keys to variation indexes).
   110  func (ctx *Context) WithForcedVariations(forcedVariations ForcedVariationsMap) *Context {
   111  	ctx.ForcedVariations = forcedVariations
   112  	return ctx
   113  }
   114  
   115  func (ctx *Context) ForceVariation(key string, variation int) {
   116  	if ctx.ForcedVariations == nil {
   117  		ctx.ForcedVariations = ForcedVariationsMap{}
   118  	}
   119  	ctx.ForcedVariations[key] = variation
   120  }
   121  
   122  func (ctx *Context) UnforceVariation(key string) {
   123  	delete(ctx.ForcedVariations, key)
   124  }
   125  
   126  // WithQAMode can be used to enable or disable the QA mode for a
   127  // context.
   128  func (ctx *Context) WithQAMode(qaMode bool) *Context {
   129  	ctx.QAMode = qaMode
   130  	return ctx
   131  }
   132  
   133  // WithDevMode can be used to enable or disable the development mode
   134  // for a context.
   135  func (ctx *Context) WithDevMode(devMode bool) *Context {
   136  	ctx.DevMode = devMode
   137  	return ctx
   138  }
   139  
   140  // WithTrackingCallback is used to set a tracking callback for a
   141  // context.
   142  func (ctx *Context) WithTrackingCallback(callback ExperimentCallback) *Context {
   143  	ctx.TrackingCallback = callback
   144  	return ctx
   145  }
   146  
   147  // WithFeatureUsageCallback is used to set a feature usage callback
   148  // for a context.
   149  func (ctx *Context) WithFeatureUsageCallback(callback FeatureUsageCallback) *Context {
   150  	ctx.OnFeatureUsage = callback
   151  	return ctx
   152  }
   153  
   154  // WithGroups sets the groups map of a context.
   155  func (ctx *Context) WithGroups(groups map[string]bool) *Context {
   156  	ctx.Groups = groups
   157  	return ctx
   158  }
   159  
   160  // WithAPIHost sets the API host of a context.
   161  func (ctx *Context) WithAPIHost(host string) *Context {
   162  	ctx.APIHost = host
   163  	return ctx
   164  }
   165  
   166  // WithClientKey sets the API client key of a context.
   167  func (ctx *Context) WithClientKey(key string) *Context {
   168  	ctx.ClientKey = key
   169  	return ctx
   170  }
   171  
   172  // WithDecryptionKey sets the decryption key of a context.
   173  func (ctx *Context) WithDecryptionKey(key string) *Context {
   174  	ctx.DecryptionKey = key
   175  	return ctx
   176  }
   177  
   178  // WithOverrides sets the experiment overrides of a context.
   179  func (ctx *Context) WithOverrides(overrides ExperimentOverrides) *Context {
   180  	ctx.Overrides = overrides
   181  	return ctx
   182  }
   183  
   184  // WithCacheTTL sets the TTL for the feature cache.
   185  func (ctx *Context) WithCacheTTL(ttl time.Duration) *Context {
   186  	ctx.CacheTTL = ttl
   187  	return ctx
   188  }
   189  
   190  // ParseContext creates a Context value from raw JSON input.
   191  func ParseContext(data []byte) *Context {
   192  	dict := make(map[string]interface{})
   193  	err := json.Unmarshal(data, &dict)
   194  	if err != nil {
   195  		logError("Failed parsing JSON input", "Context")
   196  		return NewContext()
   197  	}
   198  	return BuildContext(dict)
   199  }
   200  
   201  // BuildContext creates a Context value from a JSON object represented
   202  // as a Go map.
   203  func BuildContext(dict map[string]interface{}) *Context {
   204  	context := NewContext()
   205  	for k, v := range dict {
   206  		switch k {
   207  		case "enabled":
   208  			enabled, ok := v.(bool)
   209  			if ok {
   210  				context = context.WithEnabled(enabled)
   211  			} else {
   212  				logWarn("Invalid 'enabled' field in JSON context data")
   213  			}
   214  		case "attributes":
   215  			attrs, ok := v.(map[string]interface{})
   216  			if ok {
   217  				context = context.WithAttributes(attrs)
   218  			} else {
   219  				logWarn("Invalid 'attributes' field in JSON context data")
   220  			}
   221  		case "url":
   222  			urlString, ok := v.(string)
   223  			if ok {
   224  				url, err := url.Parse(urlString)
   225  				if err != nil {
   226  					logError("Invalid URL in JSON context data", urlString)
   227  				} else {
   228  					context = context.WithURL(url)
   229  				}
   230  			} else {
   231  				logWarn("Invalid 'url' field in JSON context data")
   232  			}
   233  		case "features":
   234  			features, ok := v.(map[string]interface{})
   235  			if ok {
   236  				context.Features = BuildFeatureMap(features)
   237  			} else {
   238  				logWarn("Invalid 'features' field in JSON context data")
   239  			}
   240  		case "forcedVariations":
   241  			forcedVariations, ok := v.(map[string]interface{})
   242  			if ok {
   243  				vars := make(map[string]int)
   244  				allVOK := true
   245  				for k, vr := range forcedVariations {
   246  					v, vok := vr.(float64)
   247  					if !vok {
   248  						allVOK = false
   249  						break
   250  					}
   251  					vars[k] = int(v)
   252  				}
   253  				if allVOK {
   254  					context = context.WithForcedVariations(vars)
   255  				} else {
   256  					ok = false
   257  				}
   258  			}
   259  			if !ok {
   260  				logWarn("Invalid 'forcedVariations' field in JSON context data")
   261  			}
   262  		case "qaMode":
   263  			qaMode, ok := v.(bool)
   264  			if ok {
   265  				context = context.WithQAMode(qaMode)
   266  			} else {
   267  				logWarn("Invalid 'qaMode' field in JSON context data")
   268  			}
   269  		case "groups":
   270  			groups, ok := v.(map[string]bool)
   271  			if ok {
   272  				context = context.WithGroups(groups)
   273  			} else {
   274  				logWarn("Invalid 'groups' field in JSON context data")
   275  			}
   276  		case "apiHost":
   277  			apiHost, ok := v.(string)
   278  			if ok {
   279  				context = context.WithAPIHost(apiHost)
   280  			} else {
   281  				logWarn("Invalid 'apiHost' field in JSON context data")
   282  			}
   283  		case "clientKey":
   284  			clientKey, ok := v.(string)
   285  			if ok {
   286  				context = context.WithClientKey(clientKey)
   287  			} else {
   288  				logWarn("Invalid 'clientKey' field in JSON context data")
   289  			}
   290  		case "decryptionKey":
   291  			decryptionKey, ok := v.(string)
   292  			if ok {
   293  				context = context.WithDecryptionKey(decryptionKey)
   294  			} else {
   295  				logWarn("Invalid 'decryptionKey' field in JSON context data")
   296  			}
   297  		default:
   298  			logWarn("Unknown key in JSON data", "Context", k)
   299  		}
   300  	}
   301  	return context
   302  }
   303  

View as plain text