Repository: Unleash/unleash-client-go
Pull request #70 (2020-03-13 23:55:21)

Added public API for listing all feature toggles


Patch:
diff --git a/.gitignore b/.gitignore
index 0d8f1ab..2f514f3 100644
--- a/.gitignore
+++ b/.gitignore
@@ -29,10 +29,13 @@ _testmain.go
 *.so
 *.dylib
 
+# Test data
+testdata/
+
 # Test binary, build with `go test -c`
 *.test
 
 # Output of the go coverage tool, specifically when used with LiteIDE
 *.out
 
-.idea/
\ No newline at end of file
+.idea/
diff --git a/internal/api/constraint.go b/api/constraint.go
similarity index 71%
rename from internal/api/constraint.go
rename to api/constraint.go
index dafb754..0d47f70 100644
--- a/internal/api/constraint.go
+++ b/api/constraint.go
@@ -13,10 +13,14 @@ const (
 	OperatorNotIn Operator = "NOT_IN"
 )
 
-// Constraint represents a constraint on a particular context
-// value.
+// Constraint represents a constraint on a particular context value.
 type Constraint struct {
+	// ContextName is the context name of the constraint.
 	ContextName string   `json:"contextName"`
+
+	// Operator is the operator of the constraint.
 	Operator    Operator `json:"operator"`
+
+	// Values is the values of the constraint.
 	Values      []string `json:"values"`
 }
diff --git a/internal/api/feature.go b/api/feature.go
similarity index 60%
rename from internal/api/feature.go
rename to api/feature.go
index 3bbdbcf..3a2c757 100644
--- a/internal/api/feature.go
+++ b/api/feature.go
@@ -1,6 +1,8 @@
 package api
 
-import "time"
+import (
+	"time"
+)
 
 type ParameterMap map[string]interface{}
 
@@ -10,12 +12,25 @@ type FeatureResponse struct {
 }
 
 type Feature struct {
+	// Name is the name of the feature toggle.
 	Name        string       `json:"name"`
+
+	// Description is a description of the feature toggle.
 	Description string       `json:"description"`
+
+	// Enabled indicates whether the feature was enabled or not.
 	Enabled     bool         `json:"enabled"`
+
+	// Strategies is a list of names of the strategies supported by the client.
 	Strategies  []Strategy   `json:"strategies"`
+
+	// CreatedAt is the creation time of the feature toggle.
 	CreatedAt   time.Time    `json:"createdAt"`
+
+	// Strategy is the strategy of the feature toggle.
 	Strategy    string       `json:"strategy"`
+
+	// Parameters is the parameters of the feature toggle.
 	Parameters  ParameterMap `json:"parameters"`
 }
 
diff --git a/internal/api/response.go b/api/response.go
similarity index 100%
rename from internal/api/response.go
rename to api/response.go
diff --git a/internal/api/strategy.go b/api/strategy.go
similarity index 67%
rename from internal/api/strategy.go
rename to api/strategy.go
index 94e365d..e7f523c 100644
--- a/internal/api/strategy.go
+++ b/api/strategy.go
@@ -6,10 +6,17 @@ type StrategyResponse struct {
 }
 
 type Strategy struct {
-	Id          int          `json:"id"`
-	Name        string       `json:"name"`
+	// Id is the name of the strategy.
+	Id int `json:"id"`
+
+	// Name is the name of the strategy.
+	Name string `json:"name"`
+
+	// Constraints is the constraints of the strategy.
 	Constraints []Constraint `json:"constraints"`
-	Parameters  ParameterMap `json:"parameters"`
+
+	// Parameters is the parameters of the strategy.
+	Parameters ParameterMap `json:"parameters"`
 }
 
 type ParameterDescription struct {
diff --git a/client.go b/client.go
index 54a3460..6a38df4 100644
--- a/client.go
+++ b/client.go
@@ -7,6 +7,7 @@ import (
 	"strings"
 	"time"
 
+	"github.com/Unleash/unleash-client-go/v3/api"
 	"github.com/Unleash/unleash-client-go/v3/context"
 	"github.com/Unleash/unleash-client-go/v3/internal/constraints"
 	s "github.com/Unleash/unleash-client-go/v3/internal/strategies"
@@ -347,3 +348,8 @@ func (uc *Client) getStrategy(name string) strategy.Strategy {
 func (uc *Client) WaitForReady() {
 	<-uc.onReady
 }
+
+// ListFeatures returns all available features toggles.
+func (uc *Client) ListFeatures() []api.Feature {
+	return uc.repository.list()
+}
diff --git a/internal/api/event.go b/internal/api/event.go
index 9ad0d89..d0adf7d 100644
--- a/internal/api/event.go
+++ b/internal/api/event.go
@@ -1,9 +1,13 @@
 package api
 
-import "time"
+import (
+	"time"
+
+	"github.com/Unleash/unleash-client-go/v3/api"
+)
 
 type EventResponse struct {
-	Response
+	api.Response
 	Events []Event `json:"events"`
 }
 
@@ -20,7 +24,7 @@ type EventData struct {
 	Name        string     `json:"name"`
 	Description string     `json:"description"`
 	Enabled     bool       `json:"enabled"`
-	Strategies  []Strategy `json:"strategies"`
+	Strategies  []api.Strategy `json:"strategies"`
 	CreatedAt   time.Time  `json:"createdAt"`
 }
 
diff --git a/internal/constraints/checker.go b/internal/constraints/checker.go
index 2e21fe4..d915da0 100644
--- a/internal/constraints/checker.go
+++ b/internal/constraints/checker.go
@@ -1,8 +1,8 @@
 package constraints
 
 import (
+	"github.com/Unleash/unleash-client-go/v3/api"
 	"github.com/Unleash/unleash-client-go/v3/context"
-	"github.com/Unleash/unleash-client-go/v3/internal/api"
 )
 
 func checkConstraint(ctx *context.Context, constraint api.Constraint) bool {
diff --git a/repository.go b/repository.go
index 9780c58..af3fbcc 100644
--- a/repository.go
+++ b/repository.go
@@ -9,7 +9,7 @@ import (
 	"sync"
 	"time"
 
-	"github.com/Unleash/unleash-client-go/v3/internal/api"
+	"github.com/Unleash/unleash-client-go/v3/api"
 )
 
 type repository struct {
@@ -150,6 +150,17 @@ func (r *repository) getToggle(key string) *api.Feature {
 	return nil
 }
 
+func (r *repository) list() []api.Feature {
+	r.RLock()
+	defer r.RUnlock()
+
+	var features []api.Feature
+	for _, feature := range r.options.storage.List() {
+		features = append(features, feature.(api.Feature))
+	}
+	return features
+}
+
 func (r *repository) Close() error {
 	close(r.close)
 	r.cancel()
diff --git a/storage.go b/storage.go
index 509323a..0992d3d 100644
--- a/storage.go
+++ b/storage.go
@@ -29,6 +29,9 @@ type Storage interface {
 
 	// Get returns the data for the specified feature toggle.
 	Get(string) (interface{}, bool)
+
+	// List returns a list of all feature toggles.
+	List() []interface{}
 }
 
 type defaultStorage struct {
@@ -81,3 +84,11 @@ func (ds defaultStorage) Get(key string) (interface{}, bool) {
 	val, ok := ds.data[key]
 	return val, ok
 }
+
+func (ds *defaultStorage) List() []interface{} {
+	var features []interface{}
+	for _, val := range ds.data {
+		features = append(features, val)
+	}
+	return features
+}


Test patch:
diff --git a/client_test.go b/client_test.go
index c46fc94..15c295b 100644
--- a/client_test.go
+++ b/client_test.go
@@ -1,11 +1,15 @@
 package unleash
 
 import (
+	"time"
+
+	"github.com/Unleash/unleash-client-go/v3/api"
 	"github.com/Unleash/unleash-client-go/v3/context"
 	"github.com/stretchr/testify/mock"
+	"github.com/stretchr/testify/require"
+
 	"testing"
 
-	"github.com/Unleash/unleash-client-go/v3/internal/api"
 	"github.com/stretchr/testify/assert"
 	"gopkg.in/h2non/gock.v1"
 )
@@ -90,3 +94,65 @@ func TestClient_WithFallbackFunc(t *testing.T) {
 
 	assert.True(gock.IsDone(), "there should be no more mocks")
 }
+
+func TestClient_ListFeatures(t *testing.T) {
+	assert := assert.New(t)
+	defer gock.OffAll()
+
+	gock.New(mockerServer).
+		Post("/client/register").
+		Reply(200)
+
+	features := []api.Feature{
+		{
+			Name:        "feature-name",
+			Description: "feature-desc",
+			Enabled:     true,
+			CreatedAt:   time.Date(1974, time.May, 19, 1, 2, 3, 4, time.UTC),
+			Strategy:    "feature-strategy",
+			Strategies: []api.Strategy{
+				{
+					Id:   1,
+					Name: "strategy-name",
+					Constraints: []api.Constraint{
+						{
+							ContextName: "context-name",
+							Operator:    api.OperatorIn,
+							Values:      []string{"constraint-value-1", "constraint-value-2"},
+						},
+					},
+					Parameters: map[string]interface{}{
+						"strategy-param-1": "strategy-value-1",
+					},
+				},
+			},
+			Parameters: map[string]interface{}{
+				"feature-param-1": "feature-value-1",
+			},
+		},
+	}
+
+	gock.New(mockerServer).
+		Get("/client/features").
+		Reply(200).
+		JSON(api.FeatureResponse{
+			Features: features,
+		})
+
+	mockListener := &MockedListener{}
+	mockListener.On("OnReady").Return()
+	mockListener.On("OnRegistered", mock.AnythingOfType("ClientData"))
+
+	client, err := NewClient(
+		WithUrl(mockerServer),
+		WithAppName(mockAppName),
+		WithInstanceId(mockInstanceId),
+		WithListener(mockListener),
+	)
+
+	assert.NoError(err)
+
+	client.WaitForReady()
+
+	require.Equal(t, features, client.ListFeatures())
+}
diff --git a/internal/constraints/checker_test.go b/internal/constraints/checker_test.go
index 422a28e..bbe1543 100644
--- a/internal/constraints/checker_test.go
+++ b/internal/constraints/checker_test.go
@@ -1,8 +1,8 @@
 package constraints
 
 import (
+	"github.com/Unleash/unleash-client-go/v3/api"
 	"github.com/Unleash/unleash-client-go/v3/context"
-	"github.com/Unleash/unleash-client-go/v3/internal/api"
 	"github.com/stretchr/testify/assert"
 	"testing"
 )
diff --git a/metrics_test.go b/metrics_test.go
index cfbcea1..e84cc48 100644
--- a/metrics_test.go
+++ b/metrics_test.go
@@ -12,7 +12,8 @@ import (
 	"testing"
 	"time"
 
-	"github.com/Unleash/unleash-client-go/v3/internal/api"
+	"github.com/Unleash/unleash-client-go/v3/api"
+	internalapi "github.com/Unleash/unleash-client-go/v3/internal/api"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
 	"gopkg.in/h2non/gock.v1"
@@ -134,13 +135,13 @@ func TestMetrics_SendMetricsFail(t *testing.T) {
 
 	type metricsReq struct {
 		// toggles are the toggles sent to /client/metrics
-		toggles map[string]api.ToggleCount
+		toggles map[string]internalapi.ToggleCount
 
 		// status is the status code returned from /client/metrics
 		status int
 	}
 	metricsCalls := make(chan metricsReq, 10)
-	var prevToggles map[string]api.ToggleCount
+	var prevToggles map[string]internalapi.ToggleCount
 	var sendStatus200 int32
 	srv := httptest.NewServer(http.HandlerFunc(func(rw http.ResponseWriter, req *http.Request) {
 		switch req.Method + " " + req.URL.Path {
diff --git a/repository_test.go b/repository_test.go
index ff94b62..5991106 100644
--- a/repository_test.go
+++ b/repository_test.go
@@ -8,7 +8,7 @@ import (
 	"testing"
 	"time"
 
-	"github.com/Unleash/unleash-client-go/v3/internal/api"
+	"github.com/Unleash/unleash-client-go/v3/api"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
 )
diff --git a/spec_test.go b/spec_test.go
index a7362af..4d121c3 100644
--- a/spec_test.go
+++ b/spec_test.go
@@ -7,8 +7,8 @@ import (
 	"sync"
 	"testing"
 
+	"github.com/Unleash/unleash-client-go/v3/api"
 	"github.com/Unleash/unleash-client-go/v3/context"
-	"github.com/Unleash/unleash-client-go/v3/internal/api"
 	"github.com/stretchr/testify/assert"
 	"github.com/stretchr/testify/mock"
 	"github.com/stretchr/testify/suite"