From d74c78e9401de6e75898f7cf9213d0727def2a14 Mon Sep 17 00:00:00 2001 From: svetlanaradyuk Date: Thu, 14 Mar 2024 18:08:56 +0100 Subject: [PATCH] fix go style issues --- README.md | 6 +-- client.go | 56 +++++++++++----------- client_initialize_response_test.go | 2 +- concurrency_test.go | 2 +- data_adapter_example.go | 10 ++-- data_adapter_interface.go | 36 ++++++-------- data_adapter_test.go | 70 ++++++++++++++-------------- diagnostics.go | 23 +++------ error_boundary.go | 12 +++-- error_boundary_test.go | 10 ++-- evaluation_details_test.go | 2 +- evaluation_test.go | 38 +++++++-------- evaluator.go | 26 +++++------ exposure_logging_test.go | 4 +- global_state.go | 2 +- init_timeout_test.go | 8 ++-- layer_exposure_test.go | 4 +- logger.go | 4 +- statsig.go | 52 +++++++++++---------- statsig_metadata_test.go | 4 +- statsig_test.go | 14 +++--- store.go | 26 +++++------ store_sync_failure_log_test.go | 8 ++-- transport.go | 15 +++--- types.go | 14 +++--- user_persistent_storage_example.go | 4 +- user_persistent_storage_interface.go | 12 ++--- user_persistent_storage_test.go | 4 +- util.go | 2 +- 29 files changed, 226 insertions(+), 244 deletions(-) diff --git a/README.md b/README.md index 4c8c8ce..9dde751 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ [![tests](https://github.com/statsig-io/go-sdk/actions/workflows/test.yml/badge.svg)](https://github.com/statsig-io/go-sdk/actions/workflows/test.yml) -The Statsig Go SDK for multi-user, server side environments. If you need a SDK for another language or single user client environment, check out our [other SDKs](https://docs.statsig.com/#sdks). +The Statsig Go SDK for multi-user, server side environments. If you need an SDK for another language or single user client environment, check out our [other SDKs](https://docs.statsig.com/#sdks). Statsig helps you move faster with Feature Gates (Feature Flags) and Dynamic Configs. It also allows you to run A/B tests to validate your new features and understand their impact on your KPIs. If you're new to Statsig, create an account at [statsig.com](https://www.statsig.com). @@ -12,10 +12,10 @@ Check out our [SDK docs](https://docs.statsig.com/server/golangSDK) to get start ## Testing -Each server SDK is tested at multiple levels - from unit to integration and e2e tests. Our internal e2e test harness runs daily against each server SDK, while unit and integration tests can be seen in the respective github repos of each SDK. The `statsig_test.go` runs a validation test on local rule/condition evaluation for this SDK against the results in the statsig backend. +Each server SDK is tested at multiple levels - from unit to integration and e2e tests. Our internal e2e test harness runs daily against each server SDK, while unit and integration tests can be seen in the respective GitHub repos of each SDK. The `statsig_test.go` runs a validation test on local rule/condition evaluation for this SDK against the results in the statsig backend. ## Guidelines -- Pull requests are welcome! +- Pull requests are welcome! - If you encounter bugs, feel free to [file an issue](https://github.com/statsig-io/go-sdk/issues). - For integration questions/help, [join our slack community](https://join.slack.com/t/statsigcommunity/shared_invite/zt-pbp005hg-VFQOutZhMw5Vu9eWvCro9g). diff --git a/client.go b/client.go index a7939bd..94711e1 100644 --- a/client.go +++ b/client.go @@ -7,7 +7,7 @@ import ( "strings" ) -// An instance of a StatsigClient for interfacing with Statsig Feature Gates, Dynamic Configs, Experiments, and Event Logging +// Client is an instance of a StatsigClient for interfacing with Statsig Feature Gates, Dynamic Configs, Experiments, and Event Logging type Client struct { sdkKey string evaluator *evaluator @@ -18,12 +18,12 @@ type Client struct { diagnostics *diagnostics } -// Initializes a Statsig Client with the given sdkKey +// NewClient initializes a Statsig Client with the given sdkKey func NewClient(sdkKey string) *Client { return NewClientWithOptions(sdkKey, &Options{}) } -// Initializes a Statsig Client with the given sdkKey and options +// NewClientWithOptions initializes a Statsig Client with the given sdkKey and options func NewClientWithOptions(sdkKey string, options *Options) *Client { diagnostics := newDiagnostics(options) diagnostics.initialize().overall().start().mark() @@ -50,31 +50,31 @@ func NewClientWithOptions(sdkKey string, options *Options) *Client { } } -// Checks the value of a Feature Gate for the given user +// CheckGate checks the value of a Feature Gate for the given user func (c *Client) CheckGate(user User, gate string) bool { options := checkGateOptions{disableLogExposures: false} return c.checkGateImpl(user, gate, options).Value } -// Checks the value of a Feature Gate for the given user without logging an exposure event +// CheckGateWithExposureLoggingDisabled checks the value of a Feature Gate for the given user without logging an exposure event func (c *Client) CheckGateWithExposureLoggingDisabled(user User, gate string) bool { options := checkGateOptions{disableLogExposures: true} return c.checkGateImpl(user, gate, options).Value } -// Get the Feature Gate for the given user +// GetGate gets the Feature Gate for the given user func (c *Client) GetGate(user User, gate string) FeatureGate { options := checkGateOptions{disableLogExposures: false} return c.checkGateImpl(user, gate, options) } -// Checks the value of a Feature Gate for the given user without logging an exposure event +// GetGateWithExposureLoggingDisabled checks the value of a Feature Gate for the given user without logging an exposure event func (c *Client) GetGateWithExposureLoggingDisabled(user User, gate string) FeatureGate { options := checkGateOptions{disableLogExposures: true} return c.checkGateImpl(user, gate, options) } -// Logs an exposure event for the dynamic config +// ManuallyLogGateExposure logs an exposure event for the dynamic config func (c *Client) ManuallyLogGateExposure(user User, gate string) { c.errorBoundary.captureVoid(func() { if !c.verifyUser(user) { @@ -87,21 +87,21 @@ func (c *Client) ManuallyLogGateExposure(user User, gate string) { }) } -// Gets the DynamicConfig value for the given user +// GetConfig gets the DynamicConfig value for the given user func (c *Client) GetConfig(user User, config string) DynamicConfig { options := &getConfigOptions{disableLogExposures: false} context := getConfigImplContext{configOptions: options} return c.getConfigImpl(user, config, context) } -// Gets the DynamicConfig value for the given user without logging an exposure event +// GetConfigWithExposureLoggingDisabled gets the DynamicConfig value for the given user without logging an exposure event func (c *Client) GetConfigWithExposureLoggingDisabled(user User, config string) DynamicConfig { options := &getConfigOptions{disableLogExposures: true} context := getConfigImplContext{configOptions: options} return c.getConfigImpl(user, config, context) } -// Logs an exposure event for the config +// ManuallyLogConfigExposure logs an exposure event for the config func (c *Client) ManuallyLogConfigExposure(user User, config string) { c.errorBoundary.captureVoid(func() { if !c.verifyUser(user) { @@ -114,7 +114,7 @@ func (c *Client) ManuallyLogConfigExposure(user User, config string) { }) } -// Gets the DynamicConfig value of an Experiment for the given user +// GetExperiment gets the DynamicConfig value of an Experiment for the given user func (c *Client) GetExperiment(user User, experiment string) DynamicConfig { if !c.verifyUser(user) { return *NewConfig(experiment, nil, "", "", nil) @@ -124,7 +124,7 @@ func (c *Client) GetExperiment(user User, experiment string) DynamicConfig { return c.getConfigImpl(user, experiment, context) } -// Gets the DynamicConfig value of an Experiment for the given user without logging an exposure event +// GetExperimentWithExposureLoggingDisabled gets the DynamicConfig value of an Experiment for the given user without logging an exposure event func (c *Client) GetExperimentWithExposureLoggingDisabled(user User, experiment string) DynamicConfig { if !c.verifyUser(user) { return *NewConfig(experiment, nil, "", "", nil) @@ -134,7 +134,7 @@ func (c *Client) GetExperimentWithExposureLoggingDisabled(user User, experiment return c.getConfigImpl(user, experiment, context) } -// Gets the DynamicConfig value of an Experiment for the given user with configurable options +// GetExperimentWithOptions gets the DynamicConfig value of an Experiment for the given user with configurable options func (c *Client) GetExperimentWithOptions(user User, experiment string, options *GetExperimentOptions) DynamicConfig { if !c.verifyUser(user) { return *NewConfig(experiment, nil, "", "", nil) @@ -143,11 +143,12 @@ func (c *Client) GetExperimentWithOptions(user User, experiment string, options return c.getConfigImpl(user, experiment, context) } -// Logs an exposure event for the experiment +// ManuallyLogExperimentExposure logs an exposure event for the experiment func (c *Client) ManuallyLogExperimentExposure(user User, experiment string) { c.ManuallyLogConfigExposure(user, experiment) } +// GetUserPersistedValues gets the persisted values for the given user func (c *Client) GetUserPersistedValues(user User, idType string) UserPersistedValues { return c.errorBoundary.captureGetUserPersistedValues(func() UserPersistedValues { persistedValues := c.evaluator.persistentStorageUtils.getUserPersistedValues(user, idType) @@ -159,19 +160,19 @@ func (c *Client) GetUserPersistedValues(user User, idType string) UserPersistedV }) } -// Gets the Layer object for the given user +// GetLayer gets the Layer object for the given user func (c *Client) GetLayer(user User, layer string) Layer { options := getLayerOptions{disableLogExposures: false} return c.getLayerImpl(user, layer, options) } -// Gets the Layer object for the given user without logging an exposure event +// GetLayerWithExposureLoggingDisabled gets the Layer object for the given user without logging an exposure event func (c *Client) GetLayerWithExposureLoggingDisabled(user User, layer string) Layer { options := getLayerOptions{disableLogExposures: true} return c.getLayerImpl(user, layer, options) } -// Logs an exposure event for the parameter in the given layer +// ManuallyLogLayerParameterExposure logs an exposure event for the parameter in the given layer func (c *Client) ManuallyLogLayerParameterExposure(user User, layer string, parameter string) { c.errorBoundary.captureVoid(func() { if !c.verifyUser(user) { @@ -185,7 +186,7 @@ func (c *Client) ManuallyLogLayerParameterExposure(user User, layer string, para }) } -// Logs an event to Statsig for analysis in the Statsig Console +// LogEvent logs an event to Statsig for analysis in the Statsig Console func (c *Client) LogEvent(event Event) { c.errorBoundary.captureVoid(func() { event.User = normalizeUser(event.User, *c.options) @@ -196,38 +197,40 @@ func (c *Client) LogEvent(event Event) { }) } -// Override the value of a Feature Gate for the given user +// OverrideGate overrides the value of a Feature Gate for the given user func (c *Client) OverrideGate(gate string, val bool) { c.errorBoundary.captureVoid(func() { c.evaluator.OverrideGate(gate, val) }) } -// Override the DynamicConfig value for the given user +// OverrideConfig overrides the DynamicConfig value for the given user func (c *Client) OverrideConfig(config string, val map[string]interface{}) { c.errorBoundary.captureVoid(func() { c.evaluator.OverrideConfig(config, val) }) } -// Override the Layer value for the given user +// OverrideLayer overrides the Layer value for the given user func (c *Client) OverrideLayer(layer string, val map[string]interface{}) { c.errorBoundary.captureVoid(func() { c.evaluator.OverrideLayer(layer, val) }) } +// LogImmediate logs a batch of events to Statsig for analysis in the Statsig Console func (c *Client) LogImmediate(events []Event) (*http.Response, error) { if len(events) > 500 { err := errors.New(EventBatchSizeError) return nil, fmt.Errorf(err.Error()) } - events_processed := make([]interface{}, 0) + eventsProcessed := make([]interface{}, 0) for _, event := range events { event.User = normalizeUser(event.User, *c.options) - events_processed = append(events_processed, event) + eventsProcessed = append(eventsProcessed, event) } input := logEventInput{ - Events: events_processed, + Events: eventsProcessed, StatsigMetadata: c.transport.metadata, } return c.transport.post("/log_event", input, nil, RequestOptions{}) } +// GetClientInitializeResponse gets the ClientInitializeResponse for the given user func (c *Client) GetClientInitializeResponse(user User, clientKey string) ClientInitializeResponse { return c.errorBoundary.captureGetClientInitializeResponse(func() ClientInitializeResponse { if !c.verifyUser(user) { @@ -247,7 +250,7 @@ func (c *Client) verifyUser(user User) bool { return true } -// Cleans up Statsig, persisting any Event Logs and cleanup processes +// Shutdown cleans up Statsig, persisting any Event Logs and cleanup processes // Using any method is undefined after Shutdown() has been called func (c *Client) Shutdown() { c.errorBoundary.captureVoid(func() { @@ -264,6 +267,7 @@ type getConfigOptions struct { disableLogExposures bool } +// GetExperimentOptions is a set of options for fetching an experiment type GetExperimentOptions struct { DisableLogExposures bool PersistedValues UserPersistedValues diff --git a/client_initialize_response_test.go b/client_initialize_response_test.go index 8720bab..b79a09d 100644 --- a/client_initialize_response_test.go +++ b/client_initialize_response_test.go @@ -63,7 +63,7 @@ func TestInitializeResponseConsistency(t *testing.T) { InitializeWithOptions(secret, &Options{ API: api, OutputLoggerOptions: getOutputLoggerOptionsForTest(t), - StatsigLoggerOptions: getStatsigLoggerOptionsForTest(t), + StatsigLoggerOptions: getStatsigLoggerOptionsForTest(), }) defer ShutdownAndDangerouslyClearInstance() diff --git a/concurrency_test.go b/concurrency_test.go index a4b3864..c370057 100644 --- a/concurrency_test.go +++ b/concurrency_test.go @@ -56,7 +56,7 @@ func TestCallingAPIsConcurrently(t *testing.T) { Tier: "awesome_land", }, OutputLoggerOptions: getOutputLoggerOptionsForTest(t), - StatsigLoggerOptions: getStatsigLoggerOptionsForTest(t), + StatsigLoggerOptions: getStatsigLoggerOptionsForTest(), } InitializeWithOptions("secret-key", options) diff --git a/data_adapter_example.go b/data_adapter_example.go index 98f3e74..dcfdecf 100644 --- a/data_adapter_example.go +++ b/data_adapter_example.go @@ -26,17 +26,17 @@ func (d *dataAdapterExample) Initialize() {} func (d *dataAdapterExample) Shutdown() {} -func (d *dataAdapterExample) ShouldBeUsedForQueryingUpdates(key string) bool { +func (d *dataAdapterExample) ShouldBeUsedForQueryingUpdates(string) bool { return false } type brokenDataAdapterExample struct{} -func (d brokenDataAdapterExample) Get(key string) string { +func (d brokenDataAdapterExample) Get(string) string { panic(errors.New("invalid get function")) } -func (d brokenDataAdapterExample) Set(key string, value string) { +func (d brokenDataAdapterExample) Set(string, string) { panic(errors.New("invalid set function")) } @@ -44,7 +44,7 @@ func (d brokenDataAdapterExample) Initialize() {} func (d brokenDataAdapterExample) Shutdown() {} -func (d brokenDataAdapterExample) ShouldBeUsedForQueryingUpdates(key string) bool { +func (d brokenDataAdapterExample) ShouldBeUsedForQueryingUpdates(string) bool { return false } @@ -69,7 +69,7 @@ func (d *dataAdapterWithPollingExample) Initialize() {} func (d *dataAdapterWithPollingExample) Shutdown() {} -func (d *dataAdapterWithPollingExample) ShouldBeUsedForQueryingUpdates(key string) bool { +func (d *dataAdapterWithPollingExample) ShouldBeUsedForQueryingUpdates(string) bool { return true } func (d *dataAdapterWithPollingExample) clearStore(key string) { diff --git a/data_adapter_interface.go b/data_adapter_interface.go index 06bf1ad..2b69132 100644 --- a/data_adapter_interface.go +++ b/data_adapter_interface.go @@ -1,37 +1,27 @@ package statsig -const CONFIG_SPECS_KEY = "statsig.cache" -const ID_LISTS_KEY = "statsig.id_lists" +const ( + configSpecsKey = "statsig.cache" + idListsKey = "statsig.id_lists" +) -/** - * An adapter for implementing custom storage of config specs. - * Can be used to bootstrap Statsig (priority over bootstrapValues if both provided) - * Also useful for backing up cached data - */ +// IDataAdapter is an adapter for implementing custom storage of config specs. +// Can be used to bootstrap Statsig (priority over bootstrapValues if both provided) +// Also useful for backing up cached data type IDataAdapter interface { - /** - * Returns the data stored for a specific key - */ + // Get returns the data stored for a specific key Get(key string) string - /** - * Updates data stored for each key - */ + // Set updates data stored for each key Set(key string, value string) - /** - * Startup tasks to run before any get/set calls can be made - */ + // Initialize starts up tasks to run before any get/set calls can be made Initialize() - /** - * Cleanup tasks to run when statsig is shutdown - */ + // Shutdown cleans up tasks to run when statsig is shutdown Shutdown() - /** - * Determines whether the SDK should poll for updates from - * the data adapter (instead of Statsig network) for the given key - */ + // ShouldBeUsedForQueryingUpdates determines whether the SDK should poll for updates + // from the data adapter (instead of Statsig network) for the given key ShouldBeUsedForQueryingUpdates(key string) bool } diff --git a/data_adapter_test.go b/data_adapter_test.go index 46ea8cd..250d60a 100644 --- a/data_adapter_test.go +++ b/data_adapter_test.go @@ -14,10 +14,10 @@ import ( ) func TestBootstrapWithAdapter(t *testing.T) { - events := []Event{} - dcs_bytes, _ := os.ReadFile("download_config_specs.json") - idlists_bytes, _ := os.ReadFile("test_data/get_id_lists.json") - idlist_bytes, _ := os.ReadFile("test_data/list_1.txt") + var events []Event + dcsBytes, _ := os.ReadFile("download_config_specs.json") + idlistsBytes, _ := os.ReadFile("test_data/get_id_lists.json") + idlistBytes, _ := os.ReadFile("test_data/list_1.txt") testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { res.WriteHeader(http.StatusOK) if strings.Contains(req.URL.Path, "log_event") { @@ -37,15 +37,15 @@ func TestBootstrapWithAdapter(t *testing.T) { dataAdapter := dataAdapterExample{store: make(map[string]string)} dataAdapter.Initialize() defer dataAdapter.Shutdown() - dataAdapter.Set(CONFIG_SPECS_KEY, string(dcs_bytes)) - dataAdapter.Set(ID_LISTS_KEY, string(idlists_bytes)) - dataAdapter.Set(fmt.Sprintf("%s::%s", ID_LISTS_KEY, "list_1"), string(idlist_bytes)) + dataAdapter.Set(configSpecsKey, string(dcsBytes)) + dataAdapter.Set(idListsKey, string(idlistsBytes)) + dataAdapter.Set(fmt.Sprintf("%s::%s", idListsKey, "list_1"), string(idlistBytes)) options := &Options{ DataAdapter: &dataAdapter, API: testServer.URL, Environment: Environment{Tier: "test"}, OutputLoggerOptions: getOutputLoggerOptionsForTest(t), - StatsigLoggerOptions: getStatsigLoggerOptionsForTest(t), + StatsigLoggerOptions: getStatsigLoggerOptionsForTest(), } t.Run("able to fetch config spec data from adapter and populate store without network", func(t *testing.T) { @@ -110,7 +110,7 @@ func TestSaveToAdapter(t *testing.T) { API: testServer.URL, Environment: Environment{Tier: "test"}, OutputLoggerOptions: getOutputLoggerOptionsForTest(t), - StatsigLoggerOptions: getStatsigLoggerOptionsForTest(t), + StatsigLoggerOptions: getStatsigLoggerOptionsForTest(), IDListSyncInterval: 100 * time.Millisecond, ConfigSyncInterval: 100 * time.Millisecond, } @@ -119,31 +119,31 @@ func TestSaveToAdapter(t *testing.T) { t.Run("updates adapter with newer config spec values from network", func(t *testing.T) { waitForCondition(t, func() bool { - return dataAdapter.Get(CONFIG_SPECS_KEY) != "" + return dataAdapter.Get(configSpecsKey) != "" }) - specString := dataAdapter.Get(CONFIG_SPECS_KEY) + specString := dataAdapter.Get(configSpecsKey) specs := downloadConfigSpecResponse{} err := json.Unmarshal([]byte(specString), &specs) if err != nil { t.Errorf("Error parsing data adapter values") } - if !contains_spec(specs.FeatureGates, "always_on_gate", "feature_gate") { + if !containsSpec(specs.FeatureGates, "always_on_gate", "feature_gate") { t.Errorf("Expected data adapter to have downloaded gates") } - if !contains_spec(specs.DynamicConfigs, "test_config", "dynamic_config") { + if !containsSpec(specs.DynamicConfigs, "test_config", "dynamic_config") { t.Errorf("Expected data adapter to have downloaded configs") } - if !contains_spec(specs.LayerConfigs, "a_layer", "dynamic_config") { + if !containsSpec(specs.LayerConfigs, "a_layer", "dynamic_config") { t.Errorf("Expected data adapter to have downloaded layers") } }) t.Run("updates adapter with newer id list values from network", func(t *testing.T) { waitForCondition(t, func() bool { - return dataAdapter.Get(ID_LISTS_KEY) != "" + return dataAdapter.Get(idListsKey) != "" }) - idListsString := dataAdapter.Get(ID_LISTS_KEY) - list1String := dataAdapter.Get(fmt.Sprintf("%s::%s", ID_LISTS_KEY, "list_1")) + idListsString := dataAdapter.Get(idListsKey) + list1String := dataAdapter.Get(fmt.Sprintf("%s::%s", idListsKey, "list_1")) list1Bytes := []byte(list1String) var idLists map[string]idList err := json.Unmarshal([]byte(idListsString), &idLists) @@ -169,19 +169,19 @@ func TestSaveToAdapter(t *testing.T) { } func TestAdapterWithPolling(t *testing.T) { - dcs_bytes, _ := os.ReadFile("download_config_specs.json") - idlists_bytes, _ := os.ReadFile("test_data/get_id_lists.json") - idlist_bytes, _ := os.ReadFile("test_data/list_1.txt") + dcsBytes, _ := os.ReadFile("download_config_specs.json") + idlistsBytes, _ := os.ReadFile("test_data/get_id_lists.json") + idlistBytes, _ := os.ReadFile("test_data/list_1.txt") testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { res.WriteHeader(http.StatusOK) if strings.Contains(req.URL.Path, "download_config_specs") { - _, _ = res.Write(dcs_bytes) + _, _ = res.Write(dcsBytes) } })) dataAdapter := dataAdapterWithPollingExample{store: make(map[string]string)} - dataAdapter.Set(CONFIG_SPECS_KEY, string(dcs_bytes)) - dataAdapter.Set(ID_LISTS_KEY, string(idlists_bytes)) - dataAdapter.Set(fmt.Sprintf("%s::%s", ID_LISTS_KEY, "list_1"), string(idlist_bytes)) + dataAdapter.Set(configSpecsKey, string(dcsBytes)) + dataAdapter.Set(idListsKey, string(idlistsBytes)) + dataAdapter.Set(fmt.Sprintf("%s::%s", idListsKey, "list_1"), string(idlistBytes)) options := &Options{ DataAdapter: &dataAdapter, API: testServer.URL, @@ -189,7 +189,7 @@ func TestAdapterWithPolling(t *testing.T) { ConfigSyncInterval: 100 * time.Millisecond, IDListSyncInterval: 100 * time.Millisecond, OutputLoggerOptions: getOutputLoggerOptionsForTest(t), - StatsigLoggerOptions: getStatsigLoggerOptionsForTest(t), + StatsigLoggerOptions: getStatsigLoggerOptionsForTest(), } InitializeWithOptions("secret-key", options) defer ShutdownAndDangerouslyClearInstance() @@ -200,10 +200,10 @@ func TestAdapterWithPolling(t *testing.T) { if !value { t.Errorf("Expected on_for_id_list to return true") } - idlists_updated_bytes, _ := os.ReadFile("test_data/get_id_lists_updated.json") - idlist_updated_bytes, _ := os.ReadFile("test_data/list_1_updated.txt") - dataAdapter.Set(fmt.Sprintf("%s::%s", ID_LISTS_KEY, "list_1"), string(idlist_updated_bytes)) - dataAdapter.Set(ID_LISTS_KEY, string(idlists_updated_bytes)) + idlistsUpdatedBytes, _ := os.ReadFile("test_data/get_id_lists_updated.json") + idlistUpdatedBytes, _ := os.ReadFile("test_data/list_1_updated.txt") + dataAdapter.Set(fmt.Sprintf("%s::%s", idListsKey, "list_1"), string(idlistUpdatedBytes)) + dataAdapter.Set(idListsKey, string(idlistsUpdatedBytes)) waitForConditionWithMessage(t, func() bool { return !CheckGate(user, "on_for_id_list") }, "Expected on_for_id_list to return false") @@ -213,7 +213,7 @@ func TestAdapterWithPolling(t *testing.T) { if !value { t.Errorf("Expected always_on_gate to return true") } - dataAdapter.clearStore(CONFIG_SPECS_KEY) + dataAdapter.clearStore(configSpecsKey) waitForConditionWithMessage(t, func() bool { return !CheckGate(user, "always_on_gate") }, "Expected always_on_gate to return false") @@ -221,7 +221,7 @@ func TestAdapterWithPolling(t *testing.T) { } func TestIncorrectlyImplementedAdapter(t *testing.T) { - events := []Event{} + var events []Event testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { res.WriteHeader(http.StatusOK) if strings.Contains(req.URL.Path, "download_config_specs") { @@ -247,9 +247,9 @@ func TestIncorrectlyImplementedAdapter(t *testing.T) { API: testServer.URL, Environment: Environment{Tier: "test"}, OutputLoggerOptions: getOutputLoggerOptionsForTest(t), - StatsigLoggerOptions: getStatsigLoggerOptionsForTest(t), + StatsigLoggerOptions: getStatsigLoggerOptionsForTest(), } - stderrLogs := swallow_stderr(func() { + stderrLogs := swallowStderr(func() { InitializeWithOptions("secret-key", options) }) if stderrLogs == "" { @@ -282,7 +282,7 @@ func TestIncorrectlyImplementedAdapter(t *testing.T) { }) } -func swallow_stderr(task func()) string { +func swallowStderr(task func()) string { stderr := os.Stderr r, w, _ := os.Pipe() os.Stderr = w @@ -294,7 +294,7 @@ func swallow_stderr(task func()) string { return buf.String() } -func contains_spec(specs []configSpec, name string, specType string) bool { +func containsSpec(specs []configSpec, name string, specType string) bool { for _, e := range specs { if e.Name == name && e.Type == specType { return true diff --git a/diagnostics.go b/diagnostics.go index 284d286..024fd8d 100644 --- a/diagnostics.go +++ b/diagnostics.go @@ -7,17 +7,16 @@ import ( "time" ) +type DiagnosticsAction string +type DiagnosticsStep string +type DiagnosticsKey string type DiagnosticsContext string const ( InitializeContext DiagnosticsContext = "initialize" ConfigSyncContext DiagnosticsContext = "config_sync" ApiCallContext DiagnosticsContext = "api_call" -) -type DiagnosticsKey string - -const ( DownloadConfigSpecsKey DiagnosticsKey = "download_config_specs" BootstrapKey DiagnosticsKey = "bootstrap" GetIDListSourcesKey DiagnosticsKey = "get_id_list_sources" @@ -29,24 +28,16 @@ const ( CheckGateApiKey DiagnosticsKey = "check_gate" GetConfigApiKey DiagnosticsKey = "get_config" GetLayerApiKey DiagnosticsKey = "get_layer" -) -type DiagnosticsStep string - -const ( NetworkRequestStep DiagnosticsStep = "network_request" FetchStep DiagnosticsStep = "fetch" ProcessStep DiagnosticsStep = "process" -) - -type DiagnosticsAction string -const ( StartAction DiagnosticsAction = "start" EndAction DiagnosticsAction = "end" -) -const MaxMarkerSize = 50 + MaxMarkerSize = 50 +) type diagnosticsBase struct { context DiagnosticsContext @@ -142,9 +133,9 @@ func (d *diagnosticsBase) updateSamplingRates(samplingRates map[string]int) { d.samplingRates = samplingRates } -func sample(rate_over_ten_thousand int) bool { +func sample(rateOverTenThousand int) bool { rand.Seed(time.Now().UnixNano()) - return int(math.Floor(rand.Float64()*10_000)) < rate_over_ten_thousand + return int(math.Floor(rand.Float64()*10_000)) < rateOverTenThousand } func (d *diagnosticsBase) clearMarkers() { diff --git a/error_boundary.go b/error_boundary.go index dcbc93a..e247dc8 100644 --- a/error_boundary.go +++ b/error_boundary.go @@ -31,13 +31,15 @@ type logExceptionResponse struct { Success bool } -var ErrorBoundaryAPI = "https://statsigapi.net/v1" -var ErrorBoundaryEndpoint = "/sdk_exception" +var ( + ErrorBoundaryAPI = "https://statsigapi.net/v1" + ErrorBoundaryEndpoint = "/sdk_exception" +) const ( - InvalidSDKKeyError string = "Must provide a valid SDK key." - EmptyUserError string = "A non-empty StatsigUser.UserID or StatsigUser.CustomIDs is required. See https://docs.statsig.com/messages/serverRequiredUserID" - EventBatchSizeError string = "The max number of events supported in one batch is 500. Please reduce the slice size and try again." + InvalidSDKKeyError string = "must provide a valid SDK key" + EmptyUserError string = "a non-empty StatsigUser.UserID or StatsigUser.CustomIDs is required. See https://docs.statsig.com/messages/serverRequiredUserID" + EventBatchSizeError string = "the max number of events supported in one batch is 500. Please reduce the slice size and try again" ) func newErrorBoundary(sdkKey string, options *Options, diagnostics *diagnostics) *errorBoundary { diff --git a/error_boundary_test.go b/error_boundary_test.go index 3a6757f..0f2b09c 100644 --- a/error_boundary_test.go +++ b/error_boundary_test.go @@ -9,7 +9,7 @@ import ( "testing" ) -func mock_server(t *testing.T, expectedError error, hit *bool) *httptest.Server { +func mockServer(t *testing.T, expectedError error, hit *bool) *httptest.Server { return httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { if strings.Contains(req.URL.Path, "/download_config_specs") { res.WriteHeader(500) @@ -34,7 +34,7 @@ func mock_server(t *testing.T, expectedError error, hit *bool) *httptest.Server func TestLogException(t *testing.T) { err := errors.New("test error boundary log exception") hit := false - testServer := mock_server(t, err, &hit) + testServer := mockServer(t, err, &hit) defer testServer.Close() opt := &Options{ API: testServer.URL, @@ -49,12 +49,12 @@ func TestLogException(t *testing.T) { func TestDCSError(t *testing.T) { hit := false - testServer := mock_server(t, nil, &hit) + testServer := mockServer(t, nil, &hit) defer testServer.Close() opt := &Options{ API: testServer.URL, OutputLoggerOptions: getOutputLoggerOptionsForTest(t), - StatsigLoggerOptions: getStatsigLoggerOptionsForTest(t), + StatsigLoggerOptions: getStatsigLoggerOptionsForTest(), } InitializeWithOptions("secret-key", opt) defer ShutdownAndDangerouslyClearInstance() @@ -66,7 +66,7 @@ func TestDCSError(t *testing.T) { func TestRepeatedError(t *testing.T) { err := errors.New("common error") hit := false - testServer := mock_server(t, err, &hit) + testServer := mockServer(t, err, &hit) defer testServer.Close() opt := &Options{ API: testServer.URL, diff --git a/evaluation_details_test.go b/evaluation_details_test.go index 204c73c..67e9420 100644 --- a/evaluation_details_test.go +++ b/evaluation_details_test.go @@ -55,7 +55,7 @@ func TestEvaluationDetails(t *testing.T) { API: getTestServer(true).URL, Environment: Environment{Tier: "test"}, OutputLoggerOptions: getOutputLoggerOptionsForTest(t), - StatsigLoggerOptions: getStatsigLoggerOptionsForTest(t), + StatsigLoggerOptions: getStatsigLoggerOptionsForTest(), EvaluationCallbacks: evaluationCallbacks, } user = User{UserID: "some_user_id"} diff --git a/evaluation_test.go b/evaluation_test.go index fedcdcb..346cc8f 100644 --- a/evaluation_test.go +++ b/evaluation_test.go @@ -77,7 +77,7 @@ func getOutputLoggerOptionsForTest(t *testing.T) OutputLoggerOptions { } } -func getStatsigLoggerOptionsForTest(t *testing.T) StatsigLoggerOptions { +func getStatsigLoggerOptionsForTest() StatsigLoggerOptions { return StatsigLoggerOptions{ DisableInitDiagnostics: true, DisableSyncDiagnostics: true, @@ -96,7 +96,7 @@ func TestMain(m *testing.M) { secret = string(bytes) } os.Remove(debugLogFile) - swallow_stderr(func() { + swallowStderr(func() { os.Exit(m.Run()) }) ShutdownAndDangerouslyClearInstance() @@ -104,11 +104,11 @@ func TestMain(m *testing.M) { func TestEvaluation(t *testing.T) { for _, api := range testAPIs { - test_helper(api, t) + testHelper(api, t) } } -func test_helper(apiOverride string, t *testing.T) { +func testHelper(apiOverride string, t *testing.T) { t.Logf("Testing for " + apiOverride) InitializeGlobalOutputLogger(getOutputLoggerOptionsForTest(t)) c := NewClientWithOptions(secret, &Options{API: apiOverride}) @@ -139,7 +139,7 @@ func test_helper(apiOverride string, t *testing.T) { gate, sdkResult.RuleID, serverResult.RuleID, u) } - if !compare_secondary_exp(t, sdkResult.SecondaryExposures, serverResult.SecondaryExposures) { + if !compareSecondaryExp(sdkResult.SecondaryExposures, serverResult.SecondaryExposures) { t.Errorf("Secondary exposures are different for gate %s. SDK got %s but server is %s", gate, sdkResult.SecondaryExposures, serverResult.SecondaryExposures) } @@ -163,7 +163,7 @@ func test_helper(apiOverride string, t *testing.T) { config, sdkResult.ConfigValue.GroupName, serverResult.GroupName, u) } - if !compare_secondary_exp(t, sdkResult.SecondaryExposures, serverResult.SecondaryExposures) { + if !compareSecondaryExp(sdkResult.SecondaryExposures, serverResult.SecondaryExposures) { t.Errorf("Secondary exposures are different for config %s. SDK got %s but server is %s", config, sdkResult.SecondaryExposures, serverResult.SecondaryExposures) } @@ -187,12 +187,12 @@ func test_helper(apiOverride string, t *testing.T) { layer, sdkResult.ConfigValue.GroupName, serverResult.GroupName, u) } - if !compare_secondary_exp(t, sdkResult.SecondaryExposures, serverResult.SecondaryExposures) { + if !compareSecondaryExp(sdkResult.SecondaryExposures, serverResult.SecondaryExposures) { t.Errorf("Secondary exposures are different for layer %s. SDK got %s but server is %s", layer, sdkResult.SecondaryExposures, serverResult.SecondaryExposures) } - if !compare_secondary_exp(t, sdkResult.UndelegatedSecondaryExposures, serverResult.UndelegatedSecondaryExposures) { + if !compareSecondaryExp(sdkResult.UndelegatedSecondaryExposures, serverResult.UndelegatedSecondaryExposures) { t.Errorf("Undelegated Secondary exposures are different for layer %s. SDK got %s but server is %s", layer, sdkResult.UndelegatedSecondaryExposures, serverResult.UndelegatedSecondaryExposures) } @@ -210,29 +210,29 @@ func TestStatsigLocalMode(t *testing.T) { LocalMode: true, } InitializeGlobalOutputLogger(getOutputLoggerOptionsForTest(t)) - local_c := NewClientWithOptions("", local) + localC := NewClientWithOptions("", local) network := &Options{} - net_c := NewClientWithOptions(secret, network) - local_gate := local_c.CheckGate(*user, "test_public") - if local_gate { + netC := NewClientWithOptions(secret, network) + localGate := localC.CheckGate(*user, "test_public") + if localGate { t.Errorf("Local mode should always return false for gate checks") } - net_gate := net_c.CheckGate(*user, "test_public") - if !net_gate { + netGate := netC.CheckGate(*user, "test_public") + if !netGate { t.Errorf("Network mode should work") } - local_config := local_c.GetConfig(*user, "test_custom_config") - net_config := net_c.GetConfig(*user, "test_custom_config") - if len(local_config.Value) != 0 { + localConfig := localC.GetConfig(*user, "test_custom_config") + netConfig := netC.GetConfig(*user, "test_custom_config") + if len(localConfig.Value) != 0 { t.Errorf("Local mode should always return false for gate checks") } - if len(net_config.Value) == 0 { + if len(netConfig.Value) == 0 { t.Errorf("Network mode should fetch configs") } } -func compare_secondary_exp(t *testing.T, v1 []map[string]string, v2 []map[string]string) bool { +func compareSecondaryExp(v1 []map[string]string, v2 []map[string]string) bool { if (v1 == nil && v2 != nil) || (v2 == nil && v1 != nil) { return false } diff --git a/evaluator.go b/evaluator.go index 2aa78d3..fe101c5 100644 --- a/evaluator.go +++ b/evaluator.go @@ -31,8 +31,8 @@ type evalResult struct { IsExperimentGroup *bool } -func newEvalResultFromUserPersistedValues(configName string, persitedValues UserPersistedValues) *evalResult { - if stickyValues, ok := persitedValues[configName]; ok { +func newEvalResultFromUserPersistedValues(configName string, persistedValues UserPersistedValues) *evalResult { + if stickyValues, ok := persistedValues[configName]; ok { newEvalResult := newEvalResultFromMap(stickyValues) return newEvalResult } @@ -44,8 +44,8 @@ func newEvalResultFromMap(evalMap map[string]interface{}) *evalResult { var secondaryExposures []map[string]string evaluationDetails := newEvaluationDetails( reasonPersisted, - safeParseJSONint64(evalMap["configSyncTime"]), - safeParseJSONint64(evalMap["initTime"]), + safeParseJSONInt64(evalMap["configSyncTime"]), + safeParseJSONInt64(evalMap["initTime"]), ) configValue := evalMap["ConfigValue"].(map[string]interface{}) if secondaryExposures, ok = evalMap["SecondaryExposures"].([]map[string]string); !ok { @@ -246,21 +246,21 @@ func (e *evaluator) getLayerOverride(name string) (map[string]interface{}, bool) return layer, ok } -// Override the value of a Feature Gate for the given user +// OverrideGate overrides the value of a Feature Gate for the given user func (e *evaluator) OverrideGate(gate string, val bool) { e.mu.Lock() defer e.mu.Unlock() e.gateOverrides[gate] = val } -// Override the DynamicConfig value for the given user +// OverrideConfig overrides the DynamicConfig value for the given user func (e *evaluator) OverrideConfig(config string, val map[string]interface{}) { e.mu.Lock() defer e.mu.Unlock() e.configOverrides[config] = val } -// Override the Layer value for the given user +// OverrideLayer overrides the Layer value for the given user func (e *evaluator) OverrideLayer(layer string, val map[string]interface{}) { e.mu.Lock() defer e.mu.Unlock() @@ -820,11 +820,11 @@ func arrayAny(arr interface{}, val interface{}, fun func(x, y interface{}) bool) func getTime(a interface{}) time.Time { switch v := a.(type) { case float64, int64, int32, int: - t_sec := time.Unix(getUnixTimestamp(v), 0) - if t_sec.Year() > time.Now().Year()+100 { + tSec := time.Unix(getUnixTimestamp(v), 0) + if tSec.Year() > time.Now().Year()+100 { return time.Unix(getUnixTimestamp(v)/1000, 0) } - return t_sec + return tSec case string: t, err := time.Parse(time.RFC3339, v) if err == nil { @@ -834,11 +834,11 @@ func getTime(a interface{}) time.Time { if err != nil { return time.Time{} } - t_sec := time.Unix(getUnixTimestamp(vInt), 0) - if t_sec.Year() > time.Now().Year()+100 { + tSec := time.Unix(getUnixTimestamp(vInt), 0) + if tSec.Year() > time.Now().Year()+100 { return time.Unix(getUnixTimestamp(vInt)/1000, 0) } - return t_sec + return tSec } return time.Time{} } diff --git a/exposure_logging_test.go b/exposure_logging_test.go index 37128d6..a5a4e98 100644 --- a/exposure_logging_test.go +++ b/exposure_logging_test.go @@ -11,7 +11,7 @@ import ( ) func TestExposureLogging(t *testing.T) { - events := []Event{} + var events []Event testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { res.WriteHeader(http.StatusOK) @@ -37,7 +37,7 @@ func TestExposureLogging(t *testing.T) { API: testServer.URL, Environment: Environment{Tier: "test"}, OutputLoggerOptions: getOutputLoggerOptionsForTest(t), - StatsigLoggerOptions: getStatsigLoggerOptionsForTest(t), + StatsigLoggerOptions: getStatsigLoggerOptionsForTest(), } user := User{UserID: "some_user_id", Email: "someuser@statsig.com"} diff --git a/global_state.go b/global_state.go index a6d949f..da8f273 100644 --- a/global_state.go +++ b/global_state.go @@ -6,7 +6,7 @@ import ( "github.com/google/uuid" ) -// Using global state variables directly will lead to race conditions +// GlobalState direct usage will lead to race conditions // Instead, define an accessor below using the Mutex lock type GlobalState struct { logger *OutputLogger diff --git a/init_timeout_test.go b/init_timeout_test.go index ef28673..4c1e357 100644 --- a/init_timeout_test.go +++ b/init_timeout_test.go @@ -24,7 +24,7 @@ func TestInitTimeout(t *testing.T) { options := &Options{ API: testServer.URL, OutputLoggerOptions: getOutputLoggerOptionsForTest(t), - StatsigLoggerOptions: getStatsigLoggerOptionsForTest(t), + StatsigLoggerOptions: getStatsigLoggerOptionsForTest(), } start := time.Now() InitializeWithOptions("secret-key", options) @@ -41,12 +41,12 @@ func TestInitTimeout(t *testing.T) { ShutdownAndDangerouslyClearInstance() }) - t.Run("Initalize finish before timeout", func(t *testing.T) { + t.Run("Initialize finish before timeout", func(t *testing.T) { options := &Options{ API: testServer.URL, InitTimeout: 5 * time.Second, OutputLoggerOptions: getOutputLoggerOptionsForTest(t), - StatsigLoggerOptions: getStatsigLoggerOptionsForTest(t), + StatsigLoggerOptions: getStatsigLoggerOptionsForTest(), } start := time.Now() InitializeWithOptions("secret-key", options) @@ -71,7 +71,7 @@ func TestInitTimeout(t *testing.T) { API: testServer.URL, InitTimeout: 100 * time.Millisecond, OutputLoggerOptions: getOutputLoggerOptionsForTest(t), - StatsigLoggerOptions: getStatsigLoggerOptionsForTest(t), + StatsigLoggerOptions: getStatsigLoggerOptionsForTest(), } start := time.Now() InitializeWithOptions("secret-key", options) diff --git a/layer_exposure_test.go b/layer_exposure_test.go index ffaae57..575ee08 100644 --- a/layer_exposure_test.go +++ b/layer_exposure_test.go @@ -11,7 +11,7 @@ import ( ) func TestLayerExposure(t *testing.T) { - events := []Event{} + var events []Event testServer := httptest.NewServer(http.HandlerFunc(func(res http.ResponseWriter, req *http.Request) { res.WriteHeader(http.StatusOK) @@ -37,7 +37,7 @@ func TestLayerExposure(t *testing.T) { API: testServer.URL, Environment: Environment{Tier: "test"}, OutputLoggerOptions: getOutputLoggerOptionsForTest(t), - StatsigLoggerOptions: getStatsigLoggerOptionsForTest(t), + StatsigLoggerOptions: getStatsigLoggerOptionsForTest(), } user := User{UserID: "some_user_id"} diff --git a/logger.go b/logger.go index 1b2c7f4..5c2fc49 100644 --- a/logger.go +++ b/logger.go @@ -13,6 +13,8 @@ const ( GateExposureEventName ExposureEventName = "statsig::gate_exposure" ConfigExposureEventName ExposureEventName = "statsig::config_exposure" LayerExposureEventName ExposureEventName = "statsig::layer_exposure" + + diagnosticsEventName = "statsig::diagnostics" ) type ExposureEvent struct { @@ -24,8 +26,6 @@ type ExposureEvent struct { Time int64 `json:"time"` } -const diagnosticsEventName = "statsig::diagnostics" - type diagnosticsEvent struct { EventName string `json:"eventName"` Metadata map[string]interface{} `json:"metadata"` diff --git a/statsig.go b/statsig.go index 13c4561..2923703 100644 --- a/statsig.go +++ b/statsig.go @@ -9,7 +9,7 @@ import ( var instance *Client -// Initializes the global Statsig instance with the given sdkKey +// Initialize initializes the global Statsig instance with the given sdkKey func Initialize(sdkKey string) { InitializeGlobalOutputLogger(OutputLoggerOptions{}) InitializeGlobalSessionID() @@ -21,7 +21,7 @@ func Initialize(sdkKey string) { instance = NewClient(sdkKey) } -// Advanced options for configuring the Statsig SDK +// Options are an advanced options for configuring the Statsig SDK type Options struct { API string `json:"api"` Environment Environment `json:"environment"` @@ -62,6 +62,7 @@ type StatsigLoggerOptions struct { DisableAllLogging bool } +// Environment is a struct that represents the environment option // See https://docs.statsig.com/guides/usingEnvironments type Environment struct { Tier string `json:"tier"` @@ -73,7 +74,7 @@ func IsInitialized() bool { return instance != nil } -// Initializes the global Statsig instance with the given sdkKey and options +// InitializeWithOptions initializes the global Statsig instance with the given sdkKey and options func InitializeWithOptions(sdkKey string, options *Options) { InitializeGlobalOutputLogger(options.OutputLoggerOptions) InitializeGlobalSessionID() @@ -101,7 +102,7 @@ func InitializeWithOptions(sdkKey string, options *Options) { } } -// Checks the value of a Feature Gate for the given user +// CheckGate checks the value of a Feature Gate for the given user func CheckGate(user User, gate string) bool { if !IsInitialized() { panic(fmt.Errorf("must Initialize() statsig before calling CheckGate")) @@ -109,7 +110,7 @@ func CheckGate(user User, gate string) bool { return instance.CheckGate(user, gate) } -// Checks the value of a Feature Gate for the given user without logging an exposure event +// CheckGateWithExposureLoggingDisabled checks the value of a Feature Gate for the given user without logging an exposure event func CheckGateWithExposureLoggingDisabled(user User, gate string) bool { if !IsInitialized() { panic(fmt.Errorf("must Initialize() statsig before calling CheckGateWithExposureLoggingDisabled")) @@ -117,7 +118,7 @@ func CheckGateWithExposureLoggingDisabled(user User, gate string) bool { return instance.CheckGateWithExposureLoggingDisabled(user, gate) } -// Get the Feature Gate for the given user +// GetGate gets the Feature Gate for the given user func GetGate(user User, gate string) FeatureGate { if !IsInitialized() { panic(fmt.Errorf("must Initialize() statsig before calling GetGate")) @@ -125,7 +126,7 @@ func GetGate(user User, gate string) FeatureGate { return instance.GetGate(user, gate) } -// Get the Feature Gate for the given user without logging an exposure event +// GetGateWithExposureLoggingDisabled gets the Feature Gate for the given user without logging an exposure event func GetGateWithExposureLoggingDisabled(user User, gate string) FeatureGate { if !IsInitialized() { panic(fmt.Errorf("must Initialize() statsig before calling GetGateWithExposureLoggingDisabled")) @@ -133,7 +134,7 @@ func GetGateWithExposureLoggingDisabled(user User, gate string) FeatureGate { return instance.GetGateWithExposureLoggingDisabled(user, gate) } -// Logs an exposure event for the gate +// ManuallyLogGateExposure logs an exposure event for the gate func ManuallyLogGateExposure(user User, config string) { if !IsInitialized() { panic(fmt.Errorf("must Initialize() statsig before calling ManuallyLogGateExposure")) @@ -141,7 +142,7 @@ func ManuallyLogGateExposure(user User, config string) { instance.ManuallyLogGateExposure(user, config) } -// Gets the DynamicConfig value for the given user +// GetConfig gets the DynamicConfig value for the given user func GetConfig(user User, config string) DynamicConfig { if !IsInitialized() { panic(fmt.Errorf("must Initialize() statsig before calling GetConfig")) @@ -149,7 +150,7 @@ func GetConfig(user User, config string) DynamicConfig { return instance.GetConfig(user, config) } -// Gets the DynamicConfig value for the given user without logging an exposure event +// GetConfigWithExposureLoggingDisabled gets the DynamicConfig value for the given user without logging an exposure event func GetConfigWithExposureLoggingDisabled(user User, config string) DynamicConfig { if !IsInitialized() { panic(fmt.Errorf("must Initialize() statsig before calling GetConfigWithExposureLoggingDisabled")) @@ -157,7 +158,7 @@ func GetConfigWithExposureLoggingDisabled(user User, config string) DynamicConfi return instance.GetConfigWithExposureLoggingDisabled(user, config) } -// Logs an exposure event for the dynamic config +// ManuallyLogConfigExposure logs an exposure event for the dynamic config func ManuallyLogConfigExposure(user User, config string) { if !IsInitialized() { panic(fmt.Errorf("must Initialize() statsig before calling ManuallyLogConfigExposure")) @@ -165,7 +166,7 @@ func ManuallyLogConfigExposure(user User, config string) { instance.ManuallyLogConfigExposure(user, config) } -// Override the value of a Feature Gate for the given user +// OverrideGate overrides the value of a Feature Gate for the given user func OverrideGate(gate string, val bool) { if !IsInitialized() { panic(fmt.Errorf("must Initialize() statsig before calling OverrideGate")) @@ -173,7 +174,7 @@ func OverrideGate(gate string, val bool) { instance.OverrideGate(gate, val) } -// Override the DynamicConfig value for the given user +// OverrideConfig overrides the DynamicConfig value for the given user func OverrideConfig(config string, val map[string]interface{}) { if !IsInitialized() { panic(fmt.Errorf("must Initialize() statsig before calling OverrideConfig")) @@ -181,7 +182,7 @@ func OverrideConfig(config string, val map[string]interface{}) { instance.OverrideConfig(config, val) } -// Override the Layer value for the given user +// OverrideLayer overrides the Layer value for the given user func OverrideLayer(layer string, val map[string]interface{}) { if !IsInitialized() { panic(fmt.Errorf("must Initialize() statsig before calling OverrideLayer")) @@ -189,7 +190,7 @@ func OverrideLayer(layer string, val map[string]interface{}) { instance.OverrideLayer(layer, val) } -// Gets the DynamicConfig value of an Experiment for the given user +// GetExperiment gets the DynamicConfig value of an Experiment for the given user func GetExperiment(user User, experiment string) DynamicConfig { if !IsInitialized() { panic(fmt.Errorf("must Initialize() statsig before calling GetExperiment")) @@ -197,7 +198,7 @@ func GetExperiment(user User, experiment string) DynamicConfig { return instance.GetExperiment(user, experiment) } -// Gets the DynamicConfig value of an Experiment for the given user without logging an exposure event +// GetExperimentWithExposureLoggingDisabled gets the DynamicConfig value of an Experiment for the given user without logging an exposure event func GetExperimentWithExposureLoggingDisabled(user User, experiment string) DynamicConfig { if !IsInitialized() { panic(fmt.Errorf("must Initialize() statsig before calling GetExperimentWithExposureLoggingDisabled")) @@ -205,7 +206,7 @@ func GetExperimentWithExposureLoggingDisabled(user User, experiment string) Dyna return instance.GetExperimentWithExposureLoggingDisabled(user, experiment) } -// Gets the DynamicConfig value of an Experiment for the given user with configurable options +// GetExperimentWithOptions gets the DynamicConfig value of an Experiment for the given user with configurable options func GetExperimentWithOptions(user User, experiment string, options *GetExperimentOptions) DynamicConfig { if !IsInitialized() { panic(fmt.Errorf("must Initialize() statsig before calling GetExperimentWithOptions")) @@ -213,7 +214,7 @@ func GetExperimentWithOptions(user User, experiment string, options *GetExperime return instance.GetExperimentWithOptions(user, experiment, options) } -// Logs an exposure event for the experiment +// ManuallyLogExperimentExposure logs an exposure event for the experiment func ManuallyLogExperimentExposure(user User, experiment string) { if !IsInitialized() { panic(fmt.Errorf("must Initialize() statsig before calling ManuallyLogExperimentExposure")) @@ -221,6 +222,7 @@ func ManuallyLogExperimentExposure(user User, experiment string) { instance.ManuallyLogExperimentExposure(user, experiment) } +// GetUserPersistedValues gets the PersistedValues for the given user func GetUserPersistedValues(user User, idType string) UserPersistedValues { if !IsInitialized() { panic(fmt.Errorf("must Initialize() statsig before calling GetUserPersistedValues")) @@ -228,7 +230,7 @@ func GetUserPersistedValues(user User, idType string) UserPersistedValues { return instance.GetUserPersistedValues(user, idType) } -// Gets the Layer object for the given user +// GetLayer gets the Layer object for the given user func GetLayer(user User, layer string) Layer { if !IsInitialized() { panic(fmt.Errorf("must Initialize() statsig before calling GetLayer")) @@ -236,7 +238,7 @@ func GetLayer(user User, layer string) Layer { return instance.GetLayer(user, layer) } -// Gets the Layer object for the given user without logging an exposure event +// GetLayerWithExposureLoggingDisabled gets the Layer object for the given user without logging an exposure event func GetLayerWithExposureLoggingDisabled(user User, layer string) Layer { if !IsInitialized() { panic(fmt.Errorf("must Initialize() statsig before calling GetLayerWithExposureLoggingDisabled")) @@ -244,7 +246,7 @@ func GetLayerWithExposureLoggingDisabled(user User, layer string) Layer { return instance.GetLayerWithExposureLoggingDisabled(user, layer) } -// Logs an exposure event for the parameter in the given layer +// ManuallyLogLayerParameterExposure logs an exposure event for the parameter in the given layer func ManuallyLogLayerParameterExposure(user User, layer string, parameter string) { if !IsInitialized() { panic(fmt.Errorf("must Initialize() statsig before calling ManuallyLogLayerParameterExposure")) @@ -252,7 +254,7 @@ func ManuallyLogLayerParameterExposure(user User, layer string, parameter string instance.ManuallyLogLayerParameterExposure(user, layer, parameter) } -// Logs an event to the Statsig console +// LogEvent logs an event to the Statsig console func LogEvent(event Event) { if !IsInitialized() { panic(fmt.Errorf("must Initialize() statsig before calling LogEvent")) @@ -260,7 +262,7 @@ func LogEvent(event Event) { instance.LogEvent(event) } -// Logs a slice of events to Statsig server immediately +// LogImmediate logs a slice of events to Statsig server immediately func LogImmediate(events []Event) (*http.Response, error) { if !IsInitialized() { panic(fmt.Errorf("must Initialize() statsig before calling LogImmediate")) @@ -282,7 +284,7 @@ func GetClientInitializeResponseForTargetApp(user User, clientKey string) Client return instance.GetClientInitializeResponse(user, clientKey) } -// Cleans up Statsig, persisting any Event Logs and cleanup processes +// Shutdown cleans up Statsig, persisting any Event Logs and cleanup processes // Using any method is undefined after Shutdown() has been called func Shutdown() { if !IsInitialized() { @@ -291,7 +293,7 @@ func Shutdown() { instance.Shutdown() } -// For test only so we can clear the shared instance. Not thread safe. +// ShutdownAndDangerouslyClearInstance is used for test only so we can clear the shared instance. Not thread safe. func ShutdownAndDangerouslyClearInstance() { Shutdown() instance = nil diff --git a/statsig_metadata_test.go b/statsig_metadata_test.go index 122dced..a522c21 100644 --- a/statsig_metadata_test.go +++ b/statsig_metadata_test.go @@ -29,7 +29,7 @@ func TestStatsigMetadata(t *testing.T) { } }).URL, OutputLoggerOptions: getOutputLoggerOptionsForTest(t), - StatsigLoggerOptions: getStatsigLoggerOptionsForTest(t), + StatsigLoggerOptions: getStatsigLoggerOptionsForTest(), LoggingMaxBufferSize: 1, }) if sessionID == "" { @@ -47,7 +47,7 @@ func TestStatsigMetadata(t *testing.T) { } }).URL, OutputLoggerOptions: getOutputLoggerOptionsForTest(t), - StatsigLoggerOptions: getStatsigLoggerOptionsForTest(t), + StatsigLoggerOptions: getStatsigLoggerOptionsForTest(), }) ShutdownAndDangerouslyClearInstance() } diff --git a/statsig_test.go b/statsig_test.go index 9a18bd6..9237ebe 100644 --- a/statsig_test.go +++ b/statsig_test.go @@ -17,7 +17,7 @@ func TestBootstrap(t *testing.T) { bytes, _ := os.ReadFile("download_config_specs.json") InitializeWithOptions("secret-key", &Options{ OutputLoggerOptions: getOutputLoggerOptionsForTest(t), - StatsigLoggerOptions: getStatsigLoggerOptionsForTest(t), + StatsigLoggerOptions: getStatsigLoggerOptionsForTest(), }) if !IsInitialized() { t.Errorf("expected statsig to be initialized") @@ -30,7 +30,7 @@ func TestBootstrap(t *testing.T) { opt := &Options{ BootstrapValues: string(bytes[:]), OutputLoggerOptions: getOutputLoggerOptionsForTest(t), - StatsigLoggerOptions: getStatsigLoggerOptionsForTest(t), + StatsigLoggerOptions: getStatsigLoggerOptionsForTest(), } InitializeWithOptions("secret-key", opt) @@ -60,7 +60,7 @@ func TestRulesUpdatedCallback(t *testing.T) { } }, OutputLoggerOptions: getOutputLoggerOptionsForTest(t), - StatsigLoggerOptions: getStatsigLoggerOptionsForTest(t), + StatsigLoggerOptions: getStatsigLoggerOptionsForTest(), } InitializeWithOptions("secret-key", opt) @@ -76,13 +76,13 @@ func TestRulesUpdatedCallback(t *testing.T) { ShutdownAndDangerouslyClearInstance() // Now use rules from the previous update callback to bootstrap, and validate values - opt_bootstrap := &Options{ + optBootstrap := &Options{ BootstrapValues: rules, LocalMode: true, OutputLoggerOptions: getOutputLoggerOptionsForTest(t), - StatsigLoggerOptions: getStatsigLoggerOptionsForTest(t), + StatsigLoggerOptions: getStatsigLoggerOptionsForTest(), } - InitializeWithOptions("secret-key", opt_bootstrap) + InitializeWithOptions("secret-key", optBootstrap) if !CheckGate(User{UserID: "123"}, "always_on_gate") { t.Errorf("always_on_gate should return true bootstrap value is provided") @@ -118,7 +118,7 @@ func TestLogImmediate(t *testing.T) { API: testServer.URL, Environment: Environment{Tier: "test"}, OutputLoggerOptions: getOutputLoggerOptionsForTest(t), - StatsigLoggerOptions: getStatsigLoggerOptionsForTest(t), + StatsigLoggerOptions: getStatsigLoggerOptionsForTest(), } InitializeWithOptions("secret-key", opt) event := Event{EventName: "test_event", User: User{UserID: "123"}} diff --git a/store.go b/store.go index 3430e2d..faee91c 100644 --- a/store.go +++ b/store.go @@ -255,7 +255,7 @@ func (s *store) fetchConfigSpecsFromAdapter() { Logger().LogError(fmt.Sprintf("Error calling data adapter get: %s\n", toError(err).Error())) } }() - specString := s.dataAdapter.Get(CONFIG_SPECS_KEY) + specString := s.dataAdapter.Get(configSpecsKey) s.addDiagnostics().dataStoreConfigSpecs().fetch().end().success(true).mark() if _, updated := s.processConfigSpecs(specString, s.addDiagnostics().dataStoreConfigSpecs()); updated { s.mu.Lock() @@ -275,7 +275,7 @@ func (s *store) saveConfigSpecsToAdapter(specs downloadConfigSpecResponse) { } }() if err == nil { - s.dataAdapter.Set(CONFIG_SPECS_KEY, string(specString)) + s.dataAdapter.Set(configSpecsKey, string(specString)) } } @@ -298,7 +298,7 @@ func (s *store) handleSyncError(err error, isColdStart bool) { func (s *store) fetchConfigSpecsFromServer(isColdStart bool) { s.addDiagnostics().downloadConfigSpecs().networkRequest().start().mark() var specs downloadConfigSpecResponse - res, err := s.transport.download_config_specs(s.lastSyncTime, &specs) + res, err := s.transport.downloadConfigSpecs(s.lastSyncTime, &specs) if res == nil || err != nil { marker := s.addDiagnostics().downloadConfigSpecs().networkRequest().end().success(false) if res != nil { @@ -418,7 +418,7 @@ func (s *store) setIDList(name string, list *idList) { func (s *store) fetchIDListsFromServer() { var serverLists map[string]idList s.addDiagnostics().getIdListSources().networkRequest().start().mark() - res, err := s.transport.get_id_lists(&serverLists) + res, err := s.transport.getIdLists(&serverLists) if res == nil || err != nil { marker := s.addDiagnostics().getIdListSources().networkRequest().end().success(false) if res != nil { @@ -441,7 +441,7 @@ func (s *store) fetchIDListsFromAdapter() { Logger().LogError(fmt.Sprintf("Error calling data adapter get: %s\n", toError(err).Error())) } }() - idListsString := s.dataAdapter.Get(ID_LISTS_KEY) + idListsString := s.dataAdapter.Get(idListsKey) var idLists map[string]idList err := json.Unmarshal([]byte(idListsString), &idLists) if err != nil { @@ -470,9 +470,9 @@ func (s *store) saveIDListsToAdapter(idLists map[string]*idList) { buf.WriteString(fmt.Sprintf("+%s\n", key)) return true }) - s.dataAdapter.Set(fmt.Sprintf("%s::%s", ID_LISTS_KEY, list.Name), buf.String()) + s.dataAdapter.Set(fmt.Sprintf("%s::%s", idListsKey, list.Name), buf.String()) } - s.dataAdapter.Set(ID_LISTS_KEY, string(idListsJSON)) + s.dataAdapter.Set(idListsKey, string(idListsJSON)) } } @@ -528,7 +528,7 @@ func (s *store) processIDLists(idLists map[string]idList, source DataSource) { } else if source == AdapterDataSource { s.getSingleIDListFromAdapter(l) } else { - s.errorBoundary.logException(errors.New("Invalid ID list data source")) + s.errorBoundary.logException(errors.New("invalid ID list data source")) } }(name, localList) } @@ -542,7 +542,7 @@ func (s *store) processIDLists(idLists map[string]idList, source DataSource) { func (s *store) downloadSingleIDListFromServer(list *idList) { s.addDiagnostics().getIdList().networkRequest().start().url(list.URL).mark() - res, err := s.transport.get_id_list(list.URL, map[string]string{"Range": fmt.Sprintf("bytes=%d-", list.Size)}) + res, err := s.transport.getIdList(list.URL, map[string]string{"Range": fmt.Sprintf("bytes=%d-", list.Size)}) if err != nil || res == nil { marker := s.addDiagnostics().getIdList().networkRequest().end().url(list.URL).success(false) if res != nil { @@ -565,7 +565,7 @@ func (s *store) getSingleIDListFromAdapter(list *idList) { Logger().LogError(fmt.Sprintf("Error calling data adapter get: %s\n", toError(err).Error())) } }() - content := s.dataAdapter.Get(fmt.Sprintf("%s::%s", ID_LISTS_KEY, list.Name)) + content := s.dataAdapter.Get(fmt.Sprintf("%s::%s", idListsKey, list.Name)) contentBytes := []byte(content) content = string(contentBytes[list.Size:]) s.addDiagnostics().dataStoreIDList().fetch().end().success(true).mark() @@ -619,7 +619,7 @@ func (s *store) processSingleIDList(list *idList, content string, length int) { list.ids.Delete(id) } } - atomic.AddInt64((&list.Size), int64(length)) + atomic.AddInt64(&list.Size, int64(length)) } func (s *store) pollForIDListChanges() { @@ -633,7 +633,7 @@ func (s *store) pollForIDListChanges() { if stop { break } - if s.dataAdapter != nil && s.dataAdapter.ShouldBeUsedForQueryingUpdates(ID_LISTS_KEY) { + if s.dataAdapter != nil && s.dataAdapter.ShouldBeUsedForQueryingUpdates(idListsKey) { s.fetchIDListsFromAdapter() } else { s.fetchIDListsFromServer() @@ -652,7 +652,7 @@ func (s *store) pollForRulesetChanges() { if stop { break } - if s.dataAdapter != nil && s.dataAdapter.ShouldBeUsedForQueryingUpdates(CONFIG_SPECS_KEY) { + if s.dataAdapter != nil && s.dataAdapter.ShouldBeUsedForQueryingUpdates(configSpecsKey) { s.fetchConfigSpecsFromAdapter() } else { s.fetchConfigSpecsFromServer(false) diff --git a/store_sync_failure_log_test.go b/store_sync_failure_log_test.go index fb764a6..4495399 100644 --- a/store_sync_failure_log_test.go +++ b/store_sync_failure_log_test.go @@ -24,23 +24,23 @@ func TestStoreSyncFailure(t *testing.T) { Environment: Environment{Tier: "test"}, ConfigSyncInterval: 100 * time.Millisecond, OutputLoggerOptions: getOutputLoggerOptionsForTest(t), - StatsigLoggerOptions: getStatsigLoggerOptionsForTest(t), + StatsigLoggerOptions: getStatsigLoggerOptionsForTest(), } syncOutdatedMax = 200 * time.Millisecond - stderrLogs := swallow_stderr(func() { + stderrLogs := swallowStderr(func() { InitializeWithOptions("secret-key", opt) }) if stderrLogs == "" { t.Error("Expected error message in stderr") } - stderrLogs = swallow_stderr(func() { + stderrLogs = swallowStderr(func() { time.Sleep(100 * time.Millisecond) }) if stderrLogs != "" { t.Error("Expected no output to stderr") } - stderrLogs = swallow_stderr(func() { + stderrLogs = swallowStderr(func() { time.Sleep(100 * time.Millisecond) }) if stderrLogs == "" { diff --git a/transport.go b/transport.go index fc411bf..813fc78 100644 --- a/transport.go +++ b/transport.go @@ -5,7 +5,6 @@ import ( "encoding/json" "fmt" "io" - "io/ioutil" "net/http" "strconv" "strings" @@ -56,13 +55,13 @@ type RequestOptions struct { backoff time.Duration } -func (opts *RequestOptions) fill_defaults() { +func (opts *RequestOptions) fillDefaults() { if opts.backoff == 0 { opts.backoff = time.Second } } -func (transport *transport) download_config_specs(sinceTime int64, responseBody interface{}) (*http.Response, error) { +func (transport *transport) downloadConfigSpecs(sinceTime int64, responseBody interface{}) (*http.Response, error) { var endpoint string if transport.options.DisableCDN { endpoint = fmt.Sprintf("/download_config_specs?sinceTime=%d", sinceTime) @@ -72,11 +71,11 @@ func (transport *transport) download_config_specs(sinceTime int64, responseBody return transport.get(endpoint, responseBody, RequestOptions{}) } -func (transport *transport) get_id_lists(responseBody interface{}) (*http.Response, error) { +func (transport *transport) getIdLists(responseBody interface{}) (*http.Response, error) { return transport.post("/get_id_lists", nil, responseBody, RequestOptions{}) } -func (transport *transport) get_id_list(url string, headers map[string]string) (*http.Response, error) { +func (transport *transport) getIdList(url string, headers map[string]string) (*http.Response, error) { req, err := http.NewRequest("GET", url, nil) if err != nil { return nil, err @@ -143,8 +142,8 @@ func (transport *transport) doRequest( if request == nil || err != nil { return nil, err } - options.fill_defaults() - return retry(options.retries, time.Duration(options.backoff), func() (*http.Response, bool, error) { + options.fillDefaults() + return retry(options.retries, options.backoff, func() (*http.Response, bool, error) { response, err := transport.client.Do(request) if err != nil { return response, response != nil, err @@ -152,7 +151,7 @@ func (transport *transport) doRequest( drainAndCloseBody := func() { if response.Body != nil { // Drain body to re-use the same connection - _, _ = io.Copy(ioutil.Discard, response.Body) + _, _ = io.Copy(io.Discard, response.Body) response.Body.Close() } } diff --git a/types.go b/types.go index 8219400..4390a51 100644 --- a/types.go +++ b/types.go @@ -4,7 +4,7 @@ package statsig // // NOTE: UserID is **required** - see https://docs.statsig.com/messages/serverRequiredUserID\ // PrivateAttributes are only used for user targeting/grouping in feature gates, dynamic configs, -// experiments and etc; they are omitted in logs. +// experiments, etc.; they are omitted in logs. type User struct { UserID string `json:"userID"` Email string `json:"email"` @@ -19,7 +19,7 @@ type User struct { CustomIDs map[string]string `json:"customIDs"` } -// an event to be sent to Statsig for logging and analysis +// Event is an event to be sent to Statsig for logging and analysis type Event struct { EventName string `json:"eventName"` User User `json:"user"` @@ -45,7 +45,7 @@ type FeatureGate struct { LogExposure *func(configBase, string) } -// A json blob configured in the Statsig Console +// DynamicConfig is a json blob configured in the Statsig Console type DynamicConfig struct { configBase } @@ -93,7 +93,7 @@ func NewLayer(name string, value map[string]interface{}, ruleID string, groupNam } } -// Gets the string value at the given key in the DynamicConfig +// GetString gets the string value at the given key in the DynamicConfig // Returns the fallback string if the item at the given key is not found or not of type string func (d *configBase) GetString(key string, fallback string) string { if v, ok := d.Value[key]; ok { @@ -107,7 +107,7 @@ func (d *configBase) GetString(key string, fallback string) string { return fallback } -// Gets the float64 value at the given key in the DynamicConfig +// GetNumber gets the float64 value at the given key in the DynamicConfig // Returns the fallback float64 if the item at the given key is not found or not of type float64 func (d *configBase) GetNumber(key string, fallback float64) float64 { if v, ok := d.Value[key]; ok { @@ -120,7 +120,7 @@ func (d *configBase) GetNumber(key string, fallback float64) float64 { return fallback } -// Gets the boolean value at the given key in the DynamicConfig +// GetBool gets the boolean value at the given key in the DynamicConfig // Returns the fallback boolean if the item at the given key is not found or not of type boolean func (d *configBase) GetBool(key string, fallback bool) bool { if v, ok := d.Value[key]; ok { @@ -133,7 +133,7 @@ func (d *configBase) GetBool(key string, fallback bool) bool { return fallback } -// Gets the slice value at the given key in the DynamicConfig +// GetSlice gets the slice value at the given key in the DynamicConfig // Returns the fallback slice if the item at the given key is not found or not of type slice func (d *configBase) GetSlice(key string, fallback []interface{}) []interface{} { if v, ok := d.Value[key]; ok { diff --git a/user_persistent_storage_example.go b/user_persistent_storage_example.go index 36fd240..5d3d6b2 100644 --- a/user_persistent_storage_example.go +++ b/user_persistent_storage_example.go @@ -23,10 +23,10 @@ func (d *userPersistentStorageExample) Save(key string, value string) { type brokenUserPersistentStorageExample struct{} -func (d *brokenUserPersistentStorageExample) Load(key string) (string, bool) { +func (d *brokenUserPersistentStorageExample) Load(string) (string, bool) { panic(errors.New("invalid Load function")) } -func (d *brokenUserPersistentStorageExample) Save(key string, value string) { +func (d *brokenUserPersistentStorageExample) Save(string, string) { panic(errors.New("invalid Save function")) } diff --git a/user_persistent_storage_interface.go b/user_persistent_storage_interface.go index aa6161a..adab289 100644 --- a/user_persistent_storage_interface.go +++ b/user_persistent_storage_interface.go @@ -1,16 +1,10 @@ package statsig -/** - * A storage adapter for persisted values. Can be used for sticky bucketing users in experiments. - */ +// IUserPersistentStorage is a storage adapter for persisted values. Can be used for sticky bucketing users in experiments. type IUserPersistentStorage interface { - /** - * Returns the data stored for a specific key - */ + // Load returns the data stored for a specific key Load(key string) (string, bool) - /** - * Updates data stored for a specific key - */ + // Save updates data stored for a specific key Save(key string, data string) } diff --git a/user_persistent_storage_test.go b/user_persistent_storage_test.go index 1baca0c..6514f5e 100644 --- a/user_persistent_storage_test.go +++ b/user_persistent_storage_test.go @@ -12,7 +12,7 @@ func TestUserPersistentStorage(t *testing.T) { bytes, _ := os.ReadFile("download_config_specs_sticky_experiments.json") opts := &Options{ OutputLoggerOptions: getOutputLoggerOptionsForTest(t), - StatsigLoggerOptions: getStatsigLoggerOptionsForTest(t), + StatsigLoggerOptions: getStatsigLoggerOptionsForTest(), BootstrapValues: string(bytes), UserPersistentStorage: persistentStorage, } @@ -202,7 +202,7 @@ func TestInvalidUserPersistentStorage(t *testing.T) { bytes, _ := os.ReadFile("download_config_specs_sticky_experiments.json") opts := &Options{ OutputLoggerOptions: getOutputLoggerOptionsForTest(t), - StatsigLoggerOptions: getStatsigLoggerOptionsForTest(t), + StatsigLoggerOptions: getStatsigLoggerOptionsForTest(), BootstrapValues: string(bytes), UserPersistentStorage: persistentStorage, } diff --git a/util.go b/util.go index e30e73e..9a867e1 100644 --- a/util.go +++ b/util.go @@ -52,7 +52,7 @@ func safeGetFirst(slice []string) string { return "" } -func safeParseJSONint64(val interface{}) int64 { +func safeParseJSONInt64(val interface{}) int64 { if num, ok := val.(json.Number); ok { i64, _ := strconv.ParseInt(string(num), 10, 64) return i64