From ed988408b9b6990aa37b26a4757d07634d82b551 Mon Sep 17 00:00:00 2001 From: petruki <31597636+petruki@users.noreply.github.com> Date: Mon, 18 May 2026 13:38:57 -0700 Subject: [PATCH] feat: added isOn must-variant or default --- README.md | 23 ++++++++++++++++++ switcher.go | 26 +++++++++++++++++++++ switcher_test.go | 61 +++++++++++++++++++++++++++++++++++++++++++----- 3 files changed, 104 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 964f0ed..78ecb39 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,7 @@ A Go SDK for Switcher API - [Usage Examples](#usage-examples) - [Basic Feature Flag Checking](#basic-feature-flag-checking) - [Detailed Response Information](#detailed-response-information) + - [Must-variant with default](#must-variant-with-default-response) - [Strategy-Based Feature Flags](#strategy-based-feature-flags) - [Error Handling](#error-handling) - [Advanced Features](#advanced-features) @@ -268,6 +269,28 @@ fmt.Printf("Reason: %s\n", response.Reason) fmt.Printf("Metadata: %#v\n", response.Metadata) ``` +### Must-variant with default response: + +Simpified response handling with default values when errors occur: + +```go +feature := client.GetSwitcher("FEATURE_LOGIN_V2") + +enabled = feature.IsOnOrDefault(false) +response := feature.IsOnWithDetailsOrDefault(ResultDetail{ + Result: false, + Reason: "default", +}) +``` + +Use the async error channel for non-blocking error handling: + +```go +client.SubscribeNotifyError(func(err error) { + fmt.Printf("Switcher Error: %v\n", err) +}) +``` + ### Strategy-Based Feature Flags #### Method 1: Prepare and Execute diff --git a/switcher.go b/switcher.go index 0d30736..3d46869 100644 --- a/switcher.go +++ b/switcher.go @@ -149,6 +149,32 @@ func (s *Switcher) IsOnWithDetails() (ResultDetail, error) { return s.submit(true) } +// IsOnOrDefault evaluates the Switcher and returns the provided default boolean when an +// error occurs. The client's error callback is notified when available. +func (s *Switcher) IsOnOrDefault(def bool) bool { + got, err := s.IsOn() + if err != nil { + if s != nil && s.client != nil { + s.client.notifyError(err) + } + return def + } + return got +} + +// IsOnWithDetailsOrDefault evaluates the Switcher and returns the provided default ResultDetail +// when an error occurs. The client's error callback is notified when available. +func (s *Switcher) IsOnWithDetailsOrDefault(def ResultDetail) ResultDetail { + res, err := s.IsOnWithDetails() + if err != nil { + if s != nil && s.client != nil { + s.client.notifyError(err) + } + return def + } + return res +} + func (s *Switcher) submit(showDetails bool) (ResultDetail, error) { execution := s.snapshotForExecution() if cached, ok := s.tryCachedResult(execution, showDetails); ok { diff --git a/switcher_test.go b/switcher_test.go index fda3b4e..5b11637 100644 --- a/switcher_test.go +++ b/switcher_test.go @@ -3,6 +3,7 @@ package client import ( "net/http" "testing" + "time" "github.com/stretchr/testify/assert" ) @@ -40,6 +41,20 @@ func TestSwitcherValidate(t *testing.T) { err := client.GetSwitcher("FEATURE_LOGIN_V2").Validate() assert.NoError(t, err) }) + + t.Run("should return a validation error when the switcher is invalid", func(t *testing.T) { + client := NewClient(Context{ + Domain: "My Domain", + }) + + got, err := client.GetSwitcher("").IsOn() + assert.Equal(t, false, got) + assert.EqualError(t, err, "something went wrong: missing or empty required fields (url, component, api_key)") + + gotD, errD := client.GetSwitcher("").IsOnWithDetails() + assert.Equal(t, ResultDetail{}, gotD) + assert.EqualError(t, errD, "something went wrong: missing or empty required fields (url, component, api_key)") + }) } func TestSwitcherPrepare(t *testing.T) { @@ -76,15 +91,49 @@ func TestSwitcherPrepare(t *testing.T) { }) } -func TestSwitcherIsOnWithDetails(t *testing.T) { - t.Run("should return a validation error when the switcher is invalid", func(t *testing.T) { +func TestSwitcherMustOrDefault(t *testing.T) { + t.Run("should return the default value when the switcher is invalid", func(t *testing.T) { + client := NewClient(Context{Domain: "My Domain"}) + var errs []error + client.SubscribeNotifyError(func(err error) { + errs = append(errs, err) + }) + + defBool := true + got := client.GetSwitcher("").IsOnOrDefault(defBool) + assert.Equal(t, defBool, got) + if assert.Len(t, errs, 1) { + assert.EqualError(t, errs[0], "something went wrong: missing or empty required fields (url, component, api_key)") + } + + defs := ResultDetail{Result: true, Reason: "default"} + gotD := client.GetSwitcher("").IsOnWithDetailsOrDefault(defs) + assert.Equal(t, defs, gotD) + if assert.Len(t, errs, 2) { + assert.EqualError(t, errs[1], "something went wrong: missing or empty required fields (url, component, api_key)") + } + }) + + t.Run("should return without error when the switcher is valid", func(t *testing.T) { + server := newRemoteTestServer(t, remoteTestHandlers{ + authStatus: http.StatusOK, + authBody: map[string]any{"token": "[token]", "exp": time.Now().Add(time.Hour).Unix()}, + criteriaStatus: http.StatusOK, + criteriaBody: map[string]any{"result": true, "reason": "Success", "metadata": map[string]any{"env": "prod"}}, + }) + defer server.Close() + client := NewClient(Context{ - Domain: "My Domain", + Domain: "My Domain", + URL: server.URL, + APIKey: "[YOUR_API_KEY]", + Component: "MyApp", }) - got, err := client.GetSwitcher("").IsOnWithDetails() + got := client.GetSwitcher("MY_SWITCHER").IsOnOrDefault(false) + assert.Equal(t, true, got) - assert.Equal(t, ResultDetail{}, got) - assert.EqualError(t, err, "something went wrong: missing or empty required fields (url, component, api_key)") + gotD := client.GetSwitcher("MY_SWITCHER").IsOnWithDetailsOrDefault(ResultDetail{Result: false, Reason: "default"}) + assert.Equal(t, ResultDetail{Result: true, Reason: "Success", Metadata: map[string]any{"env": "prod"}}, gotD) }) }