Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(configcat-provider): Add ConfigCat provider #242

Merged
merged 14 commits into from
Jun 19, 2023
1 change: 1 addition & 0 deletions .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"hooks/open-telemetry": "0.2.5",
"hooks/validator": "0.1.3",
"providers/configcat": "0.1.0",
"providers/flagd": "0.1.13",
"providers/from-env": "0.1.2",
"providers/go-feature-flag": "0.1.20",
Expand Down
41 changes: 41 additions & 0 deletions providers/configcat/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# ConfigCat OpenFeature provider for Go

OpenFeature Go provider implementation for [ConfigCat](https://configcat.com) that uses the official [ConfigCat Go SDK](https://github.com/configcat/go-sdk).

## Installation

```shell
# ConfigCat SDK
go get github.com/configcat/go-sdk/v7

# OpenFeature SDK
go get github.com/open-feature/go-sdk/pkg/openfeature
go get github.com/open-feature/go-sdk-contrib/providers/configcat
```

## Usage

Here's a basic example:

```go
import (
"context"
"fmt"

sdk "github.com/configcat/go-sdk/v7"
configcat "github.com/open-feature/go-sdk-contrib/providers/configcat/pkg"
"github.com/open-feature/go-sdk/pkg/openfeature"
)

func main() {
provider := configcat.NewProvider(sdk.NewClient("..."))
openfeature.SetProvider(provider)

client := openfeature.NewClient("app")

val, err := client.BooleanValue(context.Background(), "flag_name", false, openfeature.NewEvaluationContext("123", map[string]any{
configcat.EmailKey: "test@example.com",
}))
fmt.Printf("val: %+v - error: %v\n", val, err)
}
```
14 changes: 14 additions & 0 deletions providers/configcat/go.mod
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
module github.com/open-feature/go-sdk-contrib/providers/configcat

go 1.13
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Minor - I hope this is to be compatible with configcat go-sdk v7 and to support old platforms?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's correct I noticed a comment from you that seemed to suggest it was best to align with the underlying SDK.

Should this instead align with go1.18 from the go-sdk?


require (
github.com/configcat/go-sdk/v7 v7.10.1
github.com/open-feature/go-sdk v1.4.0
github.com/stretchr/testify v1.8.4
)

require (
github.com/sirupsen/logrus v1.9.3 // indirect
golang.org/x/sys v0.9.0 // indirect
)
1,984 changes: 1,984 additions & 0 deletions providers/configcat/go.sum

Large diffs are not rendered by default.

171 changes: 171 additions & 0 deletions providers/configcat/internal/clienttest/client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,171 @@
package clienttest

import (
"sync"

sdk "github.com/configcat/go-sdk/v7"
)

// NewClient creates enough of the ConfigCat client to record flag interactions.
func NewClient() *Client {
return &Client{}
}

type Client struct {
mu sync.Mutex
requests []Request

boolEvaluation func(req Request) sdk.BoolEvaluationDetails
stringEvaluation func(req Request) sdk.StringEvaluationDetails
floatEvaluation func(req Request) sdk.FloatEvaluationDetails
intEvaluation func(req Request) sdk.IntEvaluationDetails
}

type Request struct {
Key string
DefaultValue interface{}
User sdk.User
}

func (r *Request) UserData() sdk.UserData {
userData, ok := r.User.(*sdk.UserData)
if !ok {
panic("user is not of type sdk.UserData")
}
return *userData
}

func (c *Client) Reset() {
c.mu.Lock()
defer c.mu.Unlock()
c.requests = nil
c.boolEvaluation = nil
c.stringEvaluation = nil
c.floatEvaluation = nil
c.intEvaluation = nil
}

func (c *Client) GetRequests() []Request {
c.mu.Lock()
defer c.mu.Unlock()
requests := make([]Request, len(c.requests))
copy(requests, c.requests)
return requests
}

func (c *Client) WithBoolEvaluation(eval func(req Request) sdk.BoolEvaluationDetails) {
c.mu.Lock()
defer c.mu.Unlock()
c.boolEvaluation = eval
}

func (c *Client) WithStringEvaluation(eval func(req Request) sdk.StringEvaluationDetails) {
c.mu.Lock()
defer c.mu.Unlock()
c.stringEvaluation = eval
}

func (c *Client) WithFloatEvaluation(eval func(req Request) sdk.FloatEvaluationDetails) {
c.mu.Lock()
defer c.mu.Unlock()
c.floatEvaluation = eval
}

func (c *Client) WithIntEvaluation(eval func(req Request) sdk.IntEvaluationDetails) {
c.mu.Lock()
defer c.mu.Unlock()
c.intEvaluation = eval
}

func (c *Client) GetBoolValueDetails(key string, defaultValue bool, user sdk.User) sdk.BoolEvaluationDetails {
c.mu.Lock()
defer c.mu.Unlock()

req := Request{
Key: key,
DefaultValue: defaultValue,
User: user,
}
c.requests = append(c.requests, req)

if c.boolEvaluation == nil {
return sdk.BoolEvaluationDetails{
Data: evalNotFound(key, user),
Value: defaultValue,
}
}

return c.boolEvaluation(req)
}

func (c *Client) GetStringValueDetails(key string, defaultValue string, user sdk.User) sdk.StringEvaluationDetails {
c.mu.Lock()
defer c.mu.Unlock()

req := Request{
Key: key,
DefaultValue: defaultValue,
User: user,
}
c.requests = append(c.requests, req)

if c.stringEvaluation == nil {
return sdk.StringEvaluationDetails{
Data: evalNotFound(key, user),
Value: defaultValue,
}
}

return c.stringEvaluation(req)
}

func (c *Client) GetFloatValueDetails(key string, defaultValue float64, user sdk.User) sdk.FloatEvaluationDetails {
c.mu.Lock()
defer c.mu.Unlock()

req := Request{
Key: key,
DefaultValue: defaultValue,
User: user,
}
c.requests = append(c.requests, req)

if c.floatEvaluation == nil {
return sdk.FloatEvaluationDetails{
Data: evalNotFound(key, user),
Value: defaultValue,
}
}

return c.floatEvaluation(req)
}

func (c *Client) GetIntValueDetails(key string, defaultValue int, user sdk.User) sdk.IntEvaluationDetails {
c.mu.Lock()
defer c.mu.Unlock()

req := Request{
Key: key,
DefaultValue: defaultValue,
User: user,
}
c.requests = append(c.requests, req)

if c.intEvaluation == nil {
return sdk.IntEvaluationDetails{
Data: evalNotFound(key, user),
Value: defaultValue,
}
}

return c.intEvaluation(req)
}

func evalNotFound(key string, user sdk.User) sdk.EvaluationDetailsData {
return sdk.EvaluationDetailsData{
Key: key,
User: user,
IsDefaultValue: true,
Error: sdk.ErrKeyNotFound{Key: key},
}
}
Loading