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
6 changes: 3 additions & 3 deletions api/feature.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import (
"github.com/Unleash/unleash-go-sdk/v5/internal/strategies"
)

type ParameterMap map[string]interface{}
type ParameterMap map[string]any

type FeatureResponse struct {
Response
Expand Down Expand Up @@ -67,8 +67,8 @@ type Dependency struct {
Enabled *bool `json:"enabled"`
}

func (fr FeatureResponse) FeatureMap() map[string]interface{} {
features := map[string]interface{}{}
func (fr FeatureResponse) FeatureMap() map[string]any {
features := map[string]any{}
for _, f := range fr.Features {
features[f.Name] = f
}
Expand Down
9 changes: 3 additions & 6 deletions api/variant.go
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package api

import "slices"

import "github.com/Unleash/unleash-go-sdk/v5/context"

var DISABLED_VARIANT = &Variant{
Expand Down Expand Up @@ -81,12 +83,7 @@ func (o Override) matchValue(ctx *context.Context) bool {
if len(o.Values) == 0 {
return false
}
for _, value := range o.Values {
if value == o.getIdentifier(ctx) {
return true
}
}
return false
return slices.Contains(o.Values, o.getIdentifier(ctx))
}

// Get default variant if feature is not found or if the feature is disabled.
Expand Down
49 changes: 45 additions & 4 deletions benchmark_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,25 +7,66 @@ import (
"time"

"github.com/Unleash/unleash-go-sdk/v5"
"github.com/Unleash/unleash-go-sdk/v5/api"
)

type mockStorage map[string]api.Feature

func (m mockStorage) Get(in string) (any, bool) {
out, found := m[in]
return out, found
}

func (m mockStorage) List() []any {
res := make([]any, 0, len(m))
for _, feature := range m {
res = append(res, feature)
}
return res

}

func (m mockStorage) Init(backupPath string, appName string) {}
func (m mockStorage) Load() error { return nil }
func (m mockStorage) Persist() error { return nil }
func (m mockStorage) Reset(data map[string]any, persist bool) error { return nil }

func ptr[T any](v T) *T {
return &v
}

func BenchmarkFeatureToggleEvaluation(b *testing.B) {
unleash.Initialize(
err := unleash.Initialize(
unleash.WithListener(&unleash.NoopListener{}),
unleash.WithAppName("go-benchmark"),
unleash.WithUrl("https://app.unleash-hosted.com/demo/api/"),
unleash.WithCustomHeaders(http.Header{"Authorization": {"Go-Benchmark:development.be6b5d318c8e77469efb58590022bb6416100261accf95a15046c04d"}}),
unleash.WithStorage(mockStorage{
"foo": api.Feature{
Name: "foo",
Enabled: true,
Dependencies: &[]api.Dependency{
{Feature: "bar", Enabled: ptr(true)},
},
},
"bar": api.Feature{
Name: "bar",
Enabled: true,
},
}),
)
if err != nil {
b.Fatal(err)
}

b.ResetTimer()
startTime := time.Now()
b.ReportAllocs()

for i := 0; i < b.N; i++ {
_ = unleash.IsEnabled("go-benchmark")
_ = unleash.IsEnabled("foo")
}

endTime := time.Now()
b.StopTimer()

// Calculate ns/op (nanoseconds per operation)
nsPerOp := float64(endTime.Sub(startTime).Nanoseconds()) / float64(b.N)
Expand Down
6 changes: 3 additions & 3 deletions bootstrap_storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,18 +38,18 @@ func (bs *BootstrapStorage) Init(backupPath string, appName string) {
}
}

func (bs *BootstrapStorage) Reset(data map[string]interface{}, persist bool) error {
func (bs *BootstrapStorage) Reset(data map[string]any, persist bool) error {
return bs.backingStore.Reset(data, persist)
}

func (bs *BootstrapStorage) Persist() error {
return bs.backingStore.Persist()
}

func (bs *BootstrapStorage) Get(key string) (interface{}, bool) {
func (bs *BootstrapStorage) Get(key string) (any, bool) {
return bs.backingStore.Get(key)
}

func (bs *BootstrapStorage) List() []interface{} {
func (bs *BootstrapStorage) List() []any {
return bs.backingStore.List()
}
11 changes: 6 additions & 5 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package unleash

import (
"fmt"
"slices"

"net/http"
"net/url"
Expand Down Expand Up @@ -368,14 +369,14 @@ func (uc *Client) isEnabled(feature string, options ...FeatureOption) (api.Strat
}, f
}

allConstraints := make([]api.Constraint, 0)
allConstraints := make([]api.Constraint, 0, len(segmentConstraints)+len(s.Constraints))
allConstraints = append(allConstraints, segmentConstraints...)
allConstraints = append(allConstraints, s.Constraints...)

if ok, err := constraints.Check(ctx, allConstraints); err != nil {
uc.errors <- err
} else if ok && foundStrategy.IsEnabled(s.Parameters, ctx) {
if s.Variants != nil && len(s.Variants) > 0 {
if len(s.Variants) > 0 {
groupIdValue := s.Parameters[strategy.ParamGroupId]
groupId, ok := groupIdValue.(string)
if !ok {
Expand Down Expand Up @@ -423,16 +424,16 @@ func (uc *Client) isParentDependencySatisfied(feature *api.Feature, context cont
// According to the schema, if the enabled property is absent we assume it's true.
if parent.Enabled == nil || *parent.Enabled {
if parent.Variants != nil && len(*parent.Variants) > 0 && enabledResult.Variant != nil {
return enabledResult.Enabled && contains(*parent.Variants, enabledResult.Variant.Name)
return enabledResult.Enabled && slices.Contains(*parent.Variants, enabledResult.Variant.Name)
}
return enabledResult.Enabled
}

return !enabledResult.Enabled
}

allDependenciesSatisfied := every(*feature.Dependencies, func(parent interface{}) bool {
return dependenciesSatisfied(parent.(api.Dependency))
allDependenciesSatisfied := every(*feature.Dependencies, func(parent api.Dependency) bool {
return dependenciesSatisfied(parent)
})

return allDependenciesSatisfied
Expand Down
34 changes: 17 additions & 17 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -172,12 +172,12 @@ func TestClient_ListFeatures(t *testing.T) {
Values: []string{"constraint-value-1", "constraint-value-2"},
},
},
Parameters: map[string]interface{}{
Parameters: map[string]any{
"strategy-param-1": "strategy-value-1",
},
},
},
Parameters: map[string]interface{}{
Parameters: map[string]any{
"feature-param-1": "feature-value-1",
},
},
Expand Down Expand Up @@ -302,12 +302,12 @@ func TestClientWithVariantContext(t *testing.T) {
Values: []string{"custom-ctx"},
},
},
Parameters: map[string]interface{}{
Parameters: map[string]any{
"strategy-param-1": "strategy-value-1",
},
},
},
Parameters: map[string]interface{}{
Parameters: map[string]any{
"feature-param-1": "feature-value-1",
},
Variants: []api.VariantInternal{
Expand Down Expand Up @@ -397,11 +397,11 @@ func TestClient_WithSegment(t *testing.T) {
Id: 1,
Name: "default",
Constraints: []api.Constraint{},
Parameters: map[string]interface{}{},
Parameters: map[string]any{},
Segments: []int{1},
},
},
Parameters: map[string]interface{}{
Parameters: map[string]any{
"feature-param-1": "feature-value-1",
},
},
Expand Down Expand Up @@ -482,11 +482,11 @@ func TestClient_WithNonExistingSegment(t *testing.T) {
Id: 1,
Name: "default",
Constraints: []api.Constraint{},
Parameters: map[string]interface{}{},
Parameters: map[string]any{},
Segments: []int{1},
},
},
Parameters: map[string]interface{}{
Parameters: map[string]any{
"feature-param-1": "feature-value-1",
},
},
Expand Down Expand Up @@ -561,11 +561,11 @@ func TestClient_WithMultipleSegments(t *testing.T) {
Id: 1,
Name: "default",
Constraints: []api.Constraint{},
Parameters: map[string]interface{}{},
Parameters: map[string]any{},
Segments: []int{1, 4, 6, 2},
},
},
Parameters: map[string]interface{}{
Parameters: map[string]any{
"feature-param-1": "feature-value-1",
},
},
Expand Down Expand Up @@ -664,11 +664,11 @@ func TestClient_VariantShouldRespectConstraint(t *testing.T) {
Id: 1,
Name: "default",
Constraints: []api.Constraint{},
Parameters: map[string]interface{}{},
Parameters: map[string]any{},
Segments: []int{1, 4, 6, 2},
},
},
Parameters: map[string]interface{}{
Parameters: map[string]any{
"feature-param-1": "feature-value-1",
},
Variants: []api.VariantInternal{
Expand Down Expand Up @@ -783,11 +783,11 @@ func TestClient_VariantShouldFailWhenSegmentConstraintsDontMatch(t *testing.T) {
Id: 1,
Name: "default",
Constraints: []api.Constraint{},
Parameters: map[string]interface{}{},
Parameters: map[string]any{},
Segments: []int{1, 4, 6, 2},
},
},
Parameters: map[string]interface{}{
Parameters: map[string]any{
"feature-param-1": "feature-value-1",
},
Variants: []api.VariantInternal{
Expand Down Expand Up @@ -899,7 +899,7 @@ func TestClient_ShouldFavorStrategyVariantOverFeatureVariant(t *testing.T) {
Id: 1,
Name: "default",
Constraints: []api.Constraint{},
Parameters: map[string]interface{}{
Parameters: map[string]any{
"groupId": "strategyVariantName",
},
Variants: []api.VariantInternal{
Expand Down Expand Up @@ -988,7 +988,7 @@ func TestClient_ShouldReturnOldVariantForNonMatchingStrategyVariant(t *testing.T
Id: 1,
Name: "flexibleRollout",
Constraints: []api.Constraint{},
Parameters: map[string]interface{}{
Parameters: map[string]any{
"rollout": 0,
"stickiness": "default",
},
Expand All @@ -1010,7 +1010,7 @@ func TestClient_ShouldReturnOldVariantForNonMatchingStrategyVariant(t *testing.T
Id: 2,
Name: "flexibleRollout",
Constraints: []api.Constraint{},
Parameters: map[string]interface{}{
Parameters: map[string]any{
"rollout": 100,
"stickiness": "default",
},
Expand Down
7 changes: 3 additions & 4 deletions config.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ type configOption struct {
disableMetrics bool
backupPath string
strategies []strategy.Strategy
listener interface{}
listener any
storage Storage
httpClient *http.Client
customHeaders http.Header
Expand All @@ -35,7 +35,7 @@ type ConfigOption func(*configOption)
// the listener interfaces. If no listener is registered then the user is responsible
// for draining the various channels on the client. Failure to do so will stop the client
// from working as the worker routines will be blocked.
func WithListener(listener interface{}) ConfigOption {
func WithListener(listener any) ConfigOption {
return func(o *configOption) {
o.listener = listener
}
Expand Down Expand Up @@ -266,7 +266,7 @@ type repositoryOptions struct {
backupPath string
refreshInterval time.Duration
storage Storage
httpClient *http.Client
httpClient *http.Client
headers http.Header
isStreaming bool
}
Expand All @@ -281,5 +281,4 @@ type metricsOptions struct {
disableMetrics bool
httpClient *http.Client
headers http.Header
started *time.Time
}
11 changes: 3 additions & 8 deletions example_custom_strategy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package unleash_test

import (
"fmt"
"slices"
"strings"
"time"

Expand All @@ -15,7 +16,7 @@ func (s ActiveForUserWithEmailStrategy) Name() string {
return "ActiveForUserWithEmail"
}

func (s ActiveForUserWithEmailStrategy) IsEnabled(params map[string]interface{}, ctx *context.Context) bool {
func (s ActiveForUserWithEmailStrategy) IsEnabled(params map[string]any, ctx *context.Context) bool {

if ctx == nil {
return false
Expand All @@ -30,13 +31,7 @@ func (s ActiveForUserWithEmailStrategy) IsEnabled(params map[string]interface{},
return false
}

for _, e := range strings.Split(emails, ",") {
if e == ctx.Properties["emails"] {
return true
}
}

return false
return slices.Contains(strings.Split(emails, ","), ctx.Properties["emails"])
}

// ExampleCustomStrategy demonstrates using a custom strategy.
Expand Down
Loading