flipswitch

package module
v1.0.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Mar 7, 2026 License: MIT Imports: 16 Imported by: 0

README

Flipswitch Go SDK

CI Go Reference codecov

Flipswitch SDK for Go with real-time SSE support for OpenFeature.

This SDK provides an OpenFeature-compatible provider that wraps OFREP flag evaluation with automatic cache invalidation via Server-Sent Events (SSE). When flags change in your Flipswitch dashboard, connected clients receive updates in real-time.

Overview

  • OpenFeature Compatible: Works with the OpenFeature standard for feature flags
  • Real-Time Updates: SSE connection delivers instant flag changes
  • Polling Fallback: Automatic fallback when SSE connection fails
  • Context-based Cancellation: Proper Go idioms with context support

Requirements

  • Go 1.24+
  • github.com/open-feature/go-sdk
  • github.com/open-feature/go-sdk-contrib/providers/ofrep

Installation

go get github.com/flipswitch-io/go-sdk

Quick Start

package main

import (
    "context"
    "fmt"
    "log"

    "github.com/open-feature/go-sdk/openfeature"
    flipswitch "github.com/flipswitch-io/go-sdk"
)

func main() {
    // Create provider with API key
    provider, err := flipswitch.NewProvider("your-environment-api-key")
    if err != nil {
        log.Fatal(err)
    }
    defer provider.Shutdown()

    // Initialize the provider
    if err := provider.Init(openfeature.EvaluationContext{}); err != nil {
        log.Fatal(err)
    }

    // Register with OpenFeature
    openfeature.SetProvider(provider)
    client := openfeature.NewClient("my-app")

    // Evaluate flags
    ctx := context.Background()
    darkMode, _ := client.BooleanValue(ctx, "dark-mode", false, openfeature.EvaluationContext{})
    fmt.Printf("Dark mode: %v\n", darkMode)
}

Configuration Options

Option Type Default Description
apiKey string required Environment API key from dashboard
WithBaseURL string https://api.flipswitch.io Your Flipswitch server URL
WithRealtime bool true Enable SSE for real-time flag updates
WithHTTPClient *http.Client default Custom HTTP client
WithPollingFallback bool true Fall back to polling when SSE fails
WithPollingInterval time.Duration 30s Polling interval for fallback mode
WithMaxSseRetries int 5 Max SSE retries before polling fallback
provider, err := flipswitch.NewProvider(
    "your-api-key",
    flipswitch.WithBaseURL("https://custom.server.com"),
    flipswitch.WithRealtime(true),
    flipswitch.WithPollingFallback(true),
    flipswitch.WithPollingInterval(30 * time.Second),
    flipswitch.WithMaxSseRetries(5),
)

Usage Examples

Basic Flag Evaluation
ctx := context.Background()
client := openfeature.NewClient("my-app")

// Boolean flag
darkMode, _ := client.BooleanValue(ctx, "dark-mode", false, openfeature.EvaluationContext{})

// String flag
welcomeMsg, _ := client.StringValue(ctx, "welcome-message", "Hello!", openfeature.EvaluationContext{})

// Integer flag
maxItems, _ := client.IntValue(ctx, "max-items", 10, openfeature.EvaluationContext{})

// Float flag
discount, _ := client.FloatValue(ctx, "discount-rate", 0.0, openfeature.EvaluationContext{})

// Object flag
config, _ := client.ObjectValue(ctx, "feature-config", map[string]interface{}{}, openfeature.EvaluationContext{})
Evaluation Context

Target specific users or segments:

evalCtx := openfeature.NewEvaluationContext(
    "user-123",  // targeting key
    map[string]interface{}{
        "email":     "user@example.com",
        "plan":      "premium",
        "country":   "US",
        "beta_user": true,
    },
)

showFeature, _ := client.BooleanValue(ctx, "new-feature", false, evalCtx)
Real-Time Updates (SSE)

Listen for flag changes:

// Listen for all flag changes (empty FlagKey means bulk invalidation)
provider.AddFlagChangeListener(func(event flipswitch.FlagChangeEvent) {
    fmt.Printf("Flag changed: %s\n", event.FlagKey)
})

// Listen for a specific flag (also fires on bulk invalidation)
cancel := provider.AddFlagKeyChangeListener("dark-mode", func(event flipswitch.FlagChangeEvent) {
    fmt.Println("dark-mode changed, re-evaluating...")
})
cancel() // stop listening

status := provider.GetSseStatus() // current status
provider.ReconnectSse()           // force reconnect
Bulk Flag Evaluation

Evaluate all flags at once:

flags := provider.EvaluateAllFlags(openfeature.FlattenedContext{
    "targetingKey": "user-123",
    "email":        "user@example.com",
})

for _, flag := range flags {
    fmt.Printf("%s (%s): %s\n", flag.Key, flag.ValueType, flag.GetValueAsString())
    fmt.Printf("  Reason: %s, Variant: %s\n", flag.Reason, flag.Variant)
}

// Single flag with full details
flag := provider.EvaluateFlag("dark-mode", openfeature.FlattenedContext{"targetingKey": "user-123"})
if flag != nil {
    fmt.Printf("Value: %v\n", flag.Value)
    fmt.Printf("Reason: %s\n", flag.Reason)
    fmt.Printf("Variant: %s\n", flag.Variant)
}

Advanced Features

Polling Fallback

When SSE connection fails repeatedly, the SDK falls back to polling:

provider, err := flipswitch.NewProvider(
    "your-api-key",
    flipswitch.WithPollingFallback(true),  // default: true
    flipswitch.WithPollingInterval(30 * time.Second),
    flipswitch.WithMaxSseRetries(5),
)

// Check if polling is active
if provider.IsPollingActive() {
    fmt.Println("Polling fallback is active")
}
Custom HTTP Client

Provide a custom HTTP client:

customClient := &http.Client{
    Timeout: 60 * time.Second,
    Transport: &http.Transport{
        MaxIdleConns:        100,
        IdleConnTimeout:     90 * time.Second,
    },
}

provider, err := flipswitch.NewProvider(
    "your-api-key",
    flipswitch.WithHTTPClient(customClient),
)
Context Cancellation

Use Go contexts for proper cancellation:

ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()

value, err := client.BooleanValue(ctx, "my-flag", false, openfeature.EvaluationContext{})
if err != nil {
    // Handle timeout or cancellation
}

Framework Integration

HTTP Handler
func myHandler(w http.ResponseWriter, r *http.Request) {
    client := openfeature.NewClient("web-app")

    userID := r.Header.Get("X-User-ID")
    evalCtx := openfeature.NewEvaluationContext(userID, nil)

    if darkMode, _ := client.BooleanValue(r.Context(), "dark-mode", false, evalCtx); darkMode {
        // Serve dark theme
    }
}
Gin Middleware
func FeatureFlagMiddleware() gin.HandlerFunc {
    client := openfeature.NewClient("gin-app")

    return func(c *gin.Context) {
        userID := c.GetHeader("X-User-ID")
        evalCtx := openfeature.NewEvaluationContext(userID, nil)

        maintenanceMode, _ := client.BooleanValue(c.Request.Context(), "maintenance-mode", false, evalCtx)
        if maintenanceMode {
            c.AbortWithStatus(http.StatusServiceUnavailable)
            return
        }

        c.Next()
    }
}

Error Handling

The SDK handles errors gracefully:

provider, err := flipswitch.NewProvider("your-api-key")
if err != nil {
    log.Fatalf("Failed to create provider: %v", err)
}

if err := provider.Init(openfeature.EvaluationContext{}); err != nil {
    log.Fatalf("Failed to initialize: %v", err)
}

// Flag evaluation returns default value on error
value, err := client.BooleanValue(ctx, "my-flag", false, evalCtx)
if err != nil {
    log.Printf("Evaluation error: %v", err)
    // value will be the default (false)
}

Logging

The SDK uses Go's standard log package:

// You'll see logs like:
// [Flipswitch] Provider initialized (realtime=true)
// [Flipswitch] SSE connection established
// [Flipswitch] SSE connection error, provider is stale
// [Flipswitch] Starting polling fallback

Testing

Mock the provider in your tests:

import "github.com/open-feature/go-sdk/openfeature/provider"

func TestWithMockFlags(t *testing.T) {
    // Use InMemoryProvider for testing
    flags := map[string]interface{}{
        "dark-mode": true,
        "max-items": 10,
    }
    openfeature.SetProvider(provider.NewInMemoryProvider(flags))

    client := openfeature.NewClient("test")
    value, _ := client.BooleanValue(context.Background(), "dark-mode", false, openfeature.EvaluationContext{})

    if value != true {
        t.Errorf("Expected true, got %v", value)
    }
}

API Reference

FlipswitchProvider
type FlipswitchProvider struct {
    // ...
}

// Constructor
func NewProvider(apiKey string, opts ...Option) (*FlipswitchProvider, error)

// OpenFeature Provider interface
func (p *FlipswitchProvider) Metadata() openfeature.Metadata
func (p *FlipswitchProvider) Init(evaluationContext openfeature.EvaluationContext) error
func (p *FlipswitchProvider) Shutdown()
func (p *FlipswitchProvider) BooleanEvaluation(...) openfeature.BoolResolutionDetail
func (p *FlipswitchProvider) StringEvaluation(...) openfeature.StringResolutionDetail
func (p *FlipswitchProvider) FloatEvaluation(...) openfeature.FloatResolutionDetail
func (p *FlipswitchProvider) IntEvaluation(...) openfeature.IntResolutionDetail
func (p *FlipswitchProvider) ObjectEvaluation(...) openfeature.InterfaceResolutionDetail
func (p *FlipswitchProvider) Hooks() []openfeature.Hook

// Flipswitch-specific methods
func (p *FlipswitchProvider) GetSseStatus() ConnectionStatus
func (p *FlipswitchProvider) ReconnectSse()
func (p *FlipswitchProvider) IsPollingActive() bool
func (p *FlipswitchProvider) AddFlagChangeListener(handler FlagChangeHandler)
func (p *FlipswitchProvider) RemoveFlagChangeListener(handler FlagChangeHandler)
func (p *FlipswitchProvider) EvaluateAllFlags(evalCtx openfeature.FlattenedContext) []FlagEvaluation
func (p *FlipswitchProvider) EvaluateFlag(flagKey string, evalCtx openfeature.FlattenedContext) *FlagEvaluation
Types
type ConnectionStatus string

const (
    StatusConnecting   ConnectionStatus = "connecting"
    StatusConnected    ConnectionStatus = "connected"
    StatusDisconnected ConnectionStatus = "disconnected"
    StatusError        ConnectionStatus = "error"
)

type FlagChangeEvent struct {
    FlagKey   string // empty for bulk invalidation
    Timestamp string
}

type FlagChangeHandler func(event FlagChangeEvent)

type FlagEvaluation struct {
    Key       string
    Value     interface{}
    ValueType string
    Reason    string
    Variant   string
}

Troubleshooting

SSE Connection Fails
  • Check that your API key is valid
  • Verify your server URL is correct
  • Check for network/firewall issues blocking SSE
  • The SDK will automatically fall back to polling
Flags Not Updating in Real-Time
  • Ensure WithRealtime(true) is set (default)
  • Check SSE status with provider.GetSseStatus()
  • Check logs for error messages
Provider Initialization Fails
  • Verify your API key is correct
  • Check network connectivity to the Flipswitch server
  • Review logs for detailed error messages

Demo

Run the included demo:

cd examples/demo
go run main.go <your-api-key>

The demo will connect, display all flags, and listen for real-time updates.

Contributing

See CONTRIBUTING.md for guidelines.

License

MIT - see LICENSE for details.

Documentation

Overview

Package flipswitch provides an OpenFeature provider for Flipswitch with real-time SSE support.

This package wraps the OFREP provider for flag evaluation and adds real-time updates via Server-Sent Events (SSE).

Example:

provider, err := flipswitch.NewProvider("your-api-key")
if err != nil {
    log.Fatal(err)
}
defer provider.Shutdown()

openfeature.SetProvider(provider)
client := openfeature.NewClient("my-app")

darkMode, _ := client.BooleanValue(ctx, "dark-mode", false, nil)

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ApiKeyRotatedEvent added in v0.1.2

type ApiKeyRotatedEvent struct {
	// ValidUntil is the ISO timestamp when the current key expires.
	// Empty if the rotation was aborted.
	ValidUntil string `json:"validUntil"`

	// Timestamp is the ISO timestamp of when the event occurred.
	Timestamp string `json:"timestamp"`
}

ApiKeyRotatedEvent represents an API key rotation event received via SSE. If ValidUntil is empty, it indicates the rotation was aborted.

type CancelFunc added in v0.3.0

type CancelFunc func()

CancelFunc is returned by listener registration methods. Calling it removes the corresponding listener.

type ConfigUpdatedEvent added in v0.1.1

type ConfigUpdatedEvent struct {
	// Timestamp is the ISO timestamp of when the change occurred.
	Timestamp string `json:"timestamp"`
}

ConfigUpdatedEvent represents a configuration update event received via SSE.

type ConnectionStatus

type ConnectionStatus string

ConnectionStatus represents the SSE connection status.

const (
	// StatusConnecting indicates the client is connecting.
	StatusConnecting ConnectionStatus = "connecting"
	// StatusConnected indicates the client is connected.
	StatusConnected ConnectionStatus = "connected"
	// StatusDisconnected indicates the client is disconnected.
	StatusDisconnected ConnectionStatus = "disconnected"
	// StatusError indicates there was a connection error.
	StatusError ConnectionStatus = "error"
)

type ConnectionStatusHandler

type ConnectionStatusHandler func(status ConnectionStatus)

ConnectionStatusHandler is called when the SSE connection status changes.

type FlagChangeEvent

type FlagChangeEvent struct {
	// FlagKey is the key of the flag that changed, or empty for bulk invalidation.
	FlagKey string `json:"flagKey,omitempty"`

	// Timestamp is the ISO timestamp of when the change occurred.
	Timestamp string `json:"timestamp"`
}

FlagChangeEvent represents a flag change event received via SSE (legacy/internal format).

func (*FlagChangeEvent) GetTimestampAsTime

func (e *FlagChangeEvent) GetTimestampAsTime() (time.Time, error)

GetTimestampAsTime returns the timestamp as a time.Time object.

type FlagChangeHandler

type FlagChangeHandler func(event FlagChangeEvent)

FlagChangeHandler is called when a flag changes.

type FlagEvaluation

type FlagEvaluation struct {
	// Key is the flag key.
	Key string

	// Value is the evaluated value.
	Value interface{}

	// ValueType is the type of the value (boolean, string, number, etc.).
	ValueType string

	// Reason is the reason for this evaluation result.
	Reason string

	// Variant is the variant that matched, if applicable.
	Variant string
}

FlagEvaluation represents the result of evaluating a single flag.

func (*FlagEvaluation) AsBoolean

func (e *FlagEvaluation) AsBoolean() bool

AsBoolean returns the value as a boolean.

func (*FlagEvaluation) AsFloat

func (e *FlagEvaluation) AsFloat() float64

AsFloat returns the value as a float64.

func (*FlagEvaluation) AsInt

func (e *FlagEvaluation) AsInt() int

AsInt returns the value as an integer.

func (*FlagEvaluation) AsString

func (e *FlagEvaluation) AsString() string

AsString returns the value as a string.

func (*FlagEvaluation) GetValueAsString

func (e *FlagEvaluation) GetValueAsString() string

GetValueAsString returns the value formatted for display.

type FlagUpdatedEvent added in v0.1.1

type FlagUpdatedEvent struct {
	// FlagKey is the key of the flag that changed.
	FlagKey string `json:"flagKey"`

	// Timestamp is the ISO timestamp of when the change occurred.
	Timestamp string `json:"timestamp"`
}

FlagUpdatedEvent represents a single flag update event received via SSE.

type FlipswitchOptions

type FlipswitchOptions struct {
	// APIKey is the environment API key (required).
	APIKey string

	// BaseURL is the Flipswitch server URL.
	// Default: "https://api.flipswitch.io"
	BaseURL string

	// EnableRealtime enables SSE for real-time flag updates.
	// Default: true
	EnableRealtime bool
}

FlipswitchOptions contains configuration options for the Flipswitch provider.

type FlipswitchProvider

type FlipswitchProvider struct {
	// contains filtered or unexported fields
}

FlipswitchProvider is an OpenFeature provider for Flipswitch with real-time SSE support.

func NewProvider

func NewProvider(apiKey string, opts ...Option) (*FlipswitchProvider, error)

NewProvider creates a new FlipswitchProvider with the given API key. Returns an error if the API key is empty.

func (*FlipswitchProvider) AddFlagChangeListener

func (p *FlipswitchProvider) AddFlagChangeListener(handler FlagChangeHandler) CancelFunc

AddFlagChangeListener adds a listener for all flag change events. Returns a CancelFunc that removes the listener when called.

func (*FlipswitchProvider) AddFlagKeyChangeListener added in v0.3.0

func (p *FlipswitchProvider) AddFlagKeyChangeListener(flagKey string, handler FlagChangeHandler) CancelFunc

AddFlagKeyChangeListener adds a listener for changes to a specific flag key. The listener fires on targeted changes matching the key AND on bulk invalidations (events with empty FlagKey). Returns a CancelFunc that removes the listener when called.

func (*FlipswitchProvider) BooleanEvaluation

func (p *FlipswitchProvider) BooleanEvaluation(
	ctx context.Context,
	flag string,
	defaultValue bool,
	evalCtx openfeature.FlattenedContext,
) openfeature.BoolResolutionDetail

BooleanEvaluation evaluates a boolean flag.

func (*FlipswitchProvider) EvaluateAllFlags

func (p *FlipswitchProvider) EvaluateAllFlags(evalCtx openfeature.FlattenedContext) []FlagEvaluation

EvaluateAllFlags evaluates all flags for the given context. Returns a list of all flag evaluations with their keys, values, types, and reasons.

Note: This method makes direct HTTP calls since OFREP providers don't expose the bulk evaluation API.

func (*FlipswitchProvider) EvaluateFlag

func (p *FlipswitchProvider) EvaluateFlag(flagKey string, evalCtx openfeature.FlattenedContext) *FlagEvaluation

EvaluateFlag evaluates a single flag and returns its evaluation result. Returns nil if the flag doesn't exist.

Note: This method makes direct HTTP calls for demo purposes. For standard flag evaluation, use the OpenFeature client methods.

func (*FlipswitchProvider) EventChannel added in v0.3.0

func (p *FlipswitchProvider) EventChannel() <-chan openfeature.Event

EventChannel returns the channel for OpenFeature provider events. Implements the openfeature.EventHandler interface.

func (*FlipswitchProvider) FloatEvaluation

func (p *FlipswitchProvider) FloatEvaluation(
	ctx context.Context,
	flag string,
	defaultValue float64,
	evalCtx openfeature.FlattenedContext,
) openfeature.FloatResolutionDetail

FloatEvaluation evaluates a float flag.

func (*FlipswitchProvider) GetSseStatus

func (p *FlipswitchProvider) GetSseStatus() ConnectionStatus

GetSseStatus returns the current SSE connection status.

func (*FlipswitchProvider) Hooks

func (p *FlipswitchProvider) Hooks() []openfeature.Hook

Hooks returns any hooks the provider implements.

func (*FlipswitchProvider) Init

func (p *FlipswitchProvider) Init(evaluationContext openfeature.EvaluationContext) error

Init initializes the provider. Validates the API key and starts SSE connection if real-time is enabled.

func (*FlipswitchProvider) IntEvaluation

func (p *FlipswitchProvider) IntEvaluation(
	ctx context.Context,
	flag string,
	defaultValue int64,
	evalCtx openfeature.FlattenedContext,
) openfeature.IntResolutionDetail

IntEvaluation evaluates an integer flag.

func (*FlipswitchProvider) IsPollingActive added in v0.1.2

func (p *FlipswitchProvider) IsPollingActive() bool

IsPollingActive returns whether polling fallback is active.

func (*FlipswitchProvider) Metadata

func (p *FlipswitchProvider) Metadata() openfeature.Metadata

Metadata returns the provider metadata.

func (*FlipswitchProvider) ObjectEvaluation

func (p *FlipswitchProvider) ObjectEvaluation(
	ctx context.Context,
	flag string,
	defaultValue interface{},
	evalCtx openfeature.FlattenedContext,
) openfeature.InterfaceResolutionDetail

ObjectEvaluation evaluates an object flag.

func (*FlipswitchProvider) ReconnectSse

func (p *FlipswitchProvider) ReconnectSse()

ReconnectSse forces a reconnection of the SSE client.

func (*FlipswitchProvider) RemoveFlagChangeListener deprecated

func (p *FlipswitchProvider) RemoveFlagChangeListener(handler FlagChangeHandler)

RemoveFlagChangeListener is deprecated. Use the CancelFunc returned by AddFlagChangeListener or AddFlagKeyChangeListener instead.

Deprecated: Function pointer comparison is unreliable in Go.

func (*FlipswitchProvider) Shutdown

func (p *FlipswitchProvider) Shutdown()

Shutdown shuts down the provider and closes all connections.

func (*FlipswitchProvider) StringEvaluation

func (p *FlipswitchProvider) StringEvaluation(
	ctx context.Context,
	flag string,
	defaultValue string,
	evalCtx openfeature.FlattenedContext,
) openfeature.StringResolutionDetail

StringEvaluation evaluates a string flag.

type Option

type Option func(*FlipswitchProvider)

Option is a functional option for configuring the provider.

func WithBaseURL

func WithBaseURL(url string) Option

WithBaseURL sets the Flipswitch server base URL.

func WithHTTPClient

func WithHTTPClient(client *http.Client) Option

WithHTTPClient sets a custom HTTP client.

func WithMaxSseRetries added in v0.1.2

func WithMaxSseRetries(retries int) Option

WithMaxSseRetries sets the maximum SSE retry attempts before falling back to polling.

func WithPollingFallback added in v0.1.2

func WithPollingFallback(enabled bool) Option

WithPollingFallback enables or disables polling fallback when SSE fails.

func WithPollingInterval added in v0.1.2

func WithPollingInterval(interval time.Duration) Option

WithPollingInterval sets the polling interval for fallback mode.

func WithRealtime

func WithRealtime(enabled bool) Option

WithRealtime enables or disables real-time SSE updates.

type SseClient

type SseClient struct {
	// contains filtered or unexported fields
}

SseClient handles SSE connections for real-time flag change notifications.

func NewSseClient

func NewSseClient(
	baseURL string,
	apiKey string,
	telemetryHeaders map[string]string,
	onFlagChange FlagChangeHandler,
	onStatusChange ConnectionStatusHandler,
) *SseClient

NewSseClient creates a new SSE client.

func (*SseClient) Close

func (c *SseClient) Close()

Close closes the SSE connection and stops reconnection attempts.

func (*SseClient) Connect

func (c *SseClient) Connect()

Connect starts the SSE connection in a background goroutine.

func (*SseClient) GetStatus

func (c *SseClient) GetStatus() ConnectionStatus

GetStatus returns the current connection status.

Directories

Path Synopsis
examples
demo command
Sample application demonstrating Flipswitch integration with real-time SSE support.
Sample application demonstrating Flipswitch integration with real-time SSE support.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL