Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 27 additions & 2 deletions providers/launchdarkly/pkg/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,9 @@ var errKeyMissing = errors.New("key and targetingKey attributes are missing, at
// Scream at compile time if Provider does not implement FeatureProvider
var _ openfeature.FeatureProvider = (*Provider)(nil)

// Scream at compile time if Provider does not implement StateHandler
var _ openfeature.StateHandler = (*Provider)(nil)

// LDClient is the narrowed local interface for the parts of the
// `*ld.LDClient` LaunchDarkly client used by the provider.
type LDClient interface {
Expand All @@ -27,14 +30,16 @@ type LDClient interface {
Float64VariationDetail(key string, context ldcontext.Context, defaultVal float64) (float64, ldreason.EvaluationDetail, error)
StringVariationDetail(key string, context ldcontext.Context, defaultVal string) (string, ldreason.EvaluationDetail, error)
JSONVariationDetail(key string, context ldcontext.Context, defaultVal ldvalue.Value) (ldvalue.Value, ldreason.EvaluationDetail, error)
Close() error
}

type Option func(*options)

// options contains all the optional arguments supported by Provider.
type options struct {
kindAttr string
l Logger
kindAttr string
l Logger
closeOnShutdown bool
}

// WithLogger sets a logger implementation. By default a noop logger is used.
Expand All @@ -52,6 +57,14 @@ func WithKindAttr(name string) Option {
}
}

// WithCloseOnShutdown sets whether the LaunchDarkly client should be closed
// when the provider is shut down. By default, this is false.
func WithCloseOnShutdown(close bool) Option {
return func(o *options) {
o.closeOnShutdown = close
}
}

// Provider implements the FeatureProvider interface for LaunchDarkly.
type Provider struct {
options
Expand Down Expand Up @@ -372,3 +385,15 @@ func (p *Provider) ObjectEvaluation(ctx context.Context, flagKey string, default
func (p *Provider) Hooks() []openfeature.Hook {
return []openfeature.Hook{}
}

func (p *Provider) Init(evaluationContext openfeature.EvaluationContext) error {
return nil
}

func (p *Provider) Shutdown() {
if p.closeOnShutdown {
if err := p.client.Close(); err != nil {
p.l.Error("error during LaunchDarkly client shutdown: %s", err)
}
}
}
36 changes: 36 additions & 0 deletions providers/launchdarkly/pkg/provider_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -280,3 +280,39 @@ func TestContextCancellation(t *testing.T) {
_, err = client.ObjectValue(ctx, "rate_limit_config", nil, evalCtx)
assert.Equals(t, errors.New("GENERAL: context canceled"), errors.Unwrap(err))
}

// mockLDClient can be a struct that implements the LDClient interface for testing.
type mockLDClient struct {
ld.LDClient // Embedding the real client can be useful for mocking only specific methods
closeCalled bool
closeErr error
}

func (c *mockLDClient) Close() error {
c.closeCalled = true
return c.closeErr
}

func TestShutdown(t *testing.T) {
t.Run("should not call client close on shutdown", func(t *testing.T) {
mockClient := &mockLDClient{}
provider := NewProvider(mockClient)

err := openfeature.SetProvider(provider)
assert.Ok(t, err)

openfeature.Shutdown()
assert.Cond(t, !mockClient.closeCalled, "expected client.Close() to be called")
})

t.Run("should call client close on shutdown", func(t *testing.T) {
mockClient := &mockLDClient{}
provider := NewProvider(mockClient, WithCloseOnShutdown(true))

err := openfeature.SetProvider(provider)
assert.Ok(t, err)

openfeature.Shutdown()
assert.Cond(t, mockClient.closeCalled, "expected client.Close() to be called")
})
}
Loading