Skip to content
Draft
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
30 changes: 27 additions & 3 deletions providers/ofrep/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,15 @@ You can configure the provider using following configuration options,
|----------------------|-------------------------------------------------------------------------------------------------------------------------|
| WithApiKeyAuth | Set the token to be used with "X-API-Key" header |
| WithBearerToken | Set the token to be used with "Bearer" HTTP Authorization schema |
| WithClient | Provider a custom, pre-configured http.Client for OFREP service communication |
| WithClient | Provide a custom, pre-configured http.Client for OFREP service communication |
| WithHeader | Set a custom header to be used for authorization |
| WithHeaderProvider | Register a custom header provider for OFREP calls. You may utilize this for custom authentication/authorization headers |
| WithBaseURI | Set the base URI of the OFREP service |
| WithTimeout | Set the timeout for the http client used for communication with the OFREP service (ignored if custom client is used) |
| WithFromEnv | Configure the provider using environment variables (experimental) |


For example, consider below example which set bearer token and provider a customized http client,
For example, consider below example which sets bearer token and provides a customized http client,

```go
provider := ofrep.NewProvider(
Expand All @@ -47,4 +51,24 @@ provider := ofrep.NewProvider(
ofrep.WithClient(&http.Client{
Timeout: 1 * time.Second,
}))
```
```

### Environment Variable Configuration (Experimental)

You can use the `WithFromEnv()` option to configure the provider using environment variables:

```go
provider := ofrep.NewProvider(
"http://localhost:8016",
ofrep.WithFromEnv())
```

Supported environment variables:

| Environment Variable | Description | Example |
|---------------------|------------------------------------------------------------------|----------------------------------|
| OFREP_ENDPOINT | Base URI for the OFREP service (overrides the baseUri parameter) | `http://localhost:8016` |
| OFREP_TIMEOUT | Timeout duration for HTTP requests (ignored if custom client is used) | `30s`, `500ms` |
| OFREP_API_KEY | API key for X-API-Key authentication | `your-api-key` |
| OFREP_BEARER_TOKEN | Token for Bearer authentication | `your-bearer-token` |
| OFREP_HEADERS | Comma-separated custom headers | `Key1=Value1,Key2=Value2` |
2 changes: 1 addition & 1 deletion providers/ofrep/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ module github.com/open-feature/go-sdk-contrib/providers/ofrep

go 1.24.0

require github.com/open-feature/go-sdk v1.16.0
require github.com/open-feature/go-sdk v1.16.1-0.20251030122235-1a0d39ea7e4f

require (
github.com/go-logr/logr v1.4.3 // indirect
Expand Down
12 changes: 4 additions & 8 deletions providers/ofrep/go.sum
Original file line number Diff line number Diff line change
@@ -1,12 +1,8 @@
github.com/go-logr/logr v1.4.3 h1:CjnDlHq8ikf6E492q6eKboGOC0T8CDaOvkHCIg8idEI=
github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY=
github.com/open-feature/go-sdk v1.15.1 h1:TC3FtHtOKlGlIbSf3SEpxXVhgTd/bCbuc39XHIyltkw=
github.com/open-feature/go-sdk v1.15.1/go.mod h1:2WAFYzt8rLYavcubpCoiym3iSCXiHdPB6DxtMkv2wyo=
github.com/open-feature/go-sdk v1.16.0 h1:5NCHYv5slvNBIZhYXAzAufo0OI59OACZ5tczVqSE+Tg=
github.com/open-feature/go-sdk v1.16.0/go.mod h1:EIF40QcoYT1VbQkMPy2ZJH4kvZeY+qGUXAorzSWgKSo=
go.uber.org/mock v0.5.2 h1:LbtPTcP8A5k9WPXj54PPPbjcI4Y6lhyOZXn+VS7wNko=
go.uber.org/mock v0.5.2/go.mod h1:wLlUxC2vVTPTaE3UD51E0BGOAElKrILxhVSDYQLld5o=
github.com/open-feature/go-sdk v1.16.1-0.20251030122235-1a0d39ea7e4f h1:LECR8thRHyrC16SqGXa96+aEFQchcZheYkfrXyewQL4=
github.com/open-feature/go-sdk v1.16.1-0.20251030122235-1a0d39ea7e4f/go.mod h1:lPxPSu1UnZ4E3dCxZi5gV3et2ACi8O8P+zsTGVsDZUw=
go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y=
go.uber.org/mock v0.6.0/go.mod h1:KiVJ4BqZJaMj4svdfmHM0AUx4NJYO8ZNpPnZn1Z+BBU=
golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M=
golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA=
golang.org/x/text v0.30.0 h1:yznKA/E9zq54KzlzBEAWn1NXSQ8DIp/NYMy88xJjl4k=
golang.org/x/text v0.30.0/go.mod h1:yDdHFIX9t+tORqspjENWgzaCVXgk0yYnYuSZ8UzzBVM=
2 changes: 1 addition & 1 deletion providers/ofrep/internal/evaluate/flags_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -359,7 +359,7 @@ func genericValidator[T knownTypes](test testDefinition[T], resolvedValue T, rea
}

if !reflect.DeepEqual(test.defaultValue, resolvedValue) {
t.Errorf("expected deafault value %v, but got %v", test.defaultValue, resolvedValue)
t.Errorf("expected default value %v, but got %v", test.defaultValue, resolvedValue)
}

if reason != of.ErrorReason {
Expand Down
10 changes: 5 additions & 5 deletions providers/ofrep/internal/evaluate/resolver.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,13 +31,13 @@ func NewOutboundResolver(cfg outbound.Configuration) *OutboundResolver {
func (g *OutboundResolver) resolveSingle(ctx context.Context, key string, evalCtx map[string]any) (*successDto, *of.ResolutionError) {
b, err := json.Marshal(requestFrom(evalCtx))
if err != nil {
resErr := of.NewGeneralResolutionError(fmt.Sprintf("context marshelling error: %v", err))
resErr := of.NewGeneralResolutionError(fmt.Sprintf("context marshelling error: %v", err), err)
return nil, &resErr
}

rsp, err := g.client.Single(ctx, key, b)
if err != nil {
resErr := of.NewGeneralResolutionError(fmt.Sprintf("ofrep request error: %v", err))
resErr := of.NewGeneralResolutionError(fmt.Sprintf("ofrep request error: %v", err), err)
return nil, &resErr
}

Expand All @@ -47,7 +47,7 @@ func (g *OutboundResolver) resolveSingle(ctx context.Context, key string, evalCt
var success evaluationSuccess
err := json.Unmarshal(rsp.Data, &success)
if err != nil {
resErr := of.NewParseErrorResolutionError(fmt.Sprintf("error parsing the response: %v", err))
resErr := of.NewParseErrorResolutionError(fmt.Sprintf("error parsing the response: %v", err), err)
return nil, &resErr
}
return toSuccessDto(success)
Expand Down Expand Up @@ -82,7 +82,7 @@ func parseError400(data []byte) *of.ResolutionError {
var evalError evaluationError
err := json.Unmarshal(data, &evalError)
if err != nil {
resErr := of.NewGeneralResolutionError(fmt.Sprintf("error parsing error payload: %v", err))
resErr := of.NewGeneralResolutionError(fmt.Sprintf("error parsing error payload: %v", err), err)
return &resErr
}

Expand Down Expand Up @@ -127,7 +127,7 @@ func parseError500(data []byte) *of.ResolutionError {

err := json.Unmarshal(data, &evalError)
if err != nil {
resErr = of.NewGeneralResolutionError(fmt.Sprintf("error parsing error payload: %v", err))
resErr = of.NewGeneralResolutionError(fmt.Sprintf("error parsing error payload: %v", err), err)
} else {
resErr = of.NewGeneralResolutionError(evalError.ErrorDetails)
}
Expand Down
5 changes: 4 additions & 1 deletion providers/ofrep/internal/outbound/http.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ type HeaderCallback func() (name string, value string)

type Configuration struct {
BaseURI string
Timeout time.Duration
Callbacks []HeaderCallback
Client *http.Client
}
Expand All @@ -39,7 +40,7 @@ type Outbound struct {
func NewHttp(cfg Configuration) *Outbound {
if cfg.Client == nil {
cfg.Client = &http.Client{
Timeout: 10 * time.Second,
Timeout: cfg.Timeout,
}
}

Expand All @@ -62,6 +63,8 @@ func (h *Outbound) Single(ctx context.Context, key string, payload []byte) (*Res
return nil, &resErr
}

req.Header.Set("Content-Type", "application/json")

for _, callback := range h.headerProvider {
req.Header.Set(callback())
}
Expand Down
79 changes: 75 additions & 4 deletions providers/ofrep/provider.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,17 @@ import (
"context"
"fmt"
"net/http"
"os"
"strings"
"time"

"github.com/open-feature/go-sdk-contrib/providers/ofrep/internal/evaluate"
"github.com/open-feature/go-sdk-contrib/providers/ofrep/internal/outbound"
"github.com/open-feature/go-sdk/openfeature"
)

var _ openfeature.FeatureProvider = (*Provider)(nil)

// Provider implementation for OFREP
type Provider struct {
evaluator Evaluator
Expand All @@ -22,6 +27,7 @@ type Option func(*outbound.Configuration)
func NewProvider(baseUri string, options ...Option) *Provider {
cfg := outbound.Configuration{
BaseURI: baseUri,
Timeout: 10 * time.Second,
}

for _, option := range options {
Expand Down Expand Up @@ -67,14 +73,21 @@ func (p Provider) Hooks() []openfeature.Hook {

// options of the OFREP provider

// WithHeaderProvider allows to configure a custom header callback to set a custom authorization header
// WithHeader allows to set a custom header to be used for authorization.
func WithHeader(name, value string) func(*outbound.Configuration) {
return WithHeaderProvider(func() (string, string) {
return name, value
})
}

// WithHeaderProvider allows to configure a custom header callback to set a custom authorization header.
func WithHeaderProvider(callback outbound.HeaderCallback) func(*outbound.Configuration) {
return func(c *outbound.Configuration) {
c.Callbacks = append(c.Callbacks, callback)
}
}

// WithBearerToken allows to set token to be used for bearer token authorization
// WithBearerToken allows to set token to be used for bearer token authorization.
func WithBearerToken(token string) func(*outbound.Configuration) {
return func(c *outbound.Configuration) {
c.Callbacks = append(c.Callbacks, func() (string, string) {
Expand All @@ -83,7 +96,7 @@ func WithBearerToken(token string) func(*outbound.Configuration) {
}
}

// WithApiKeyAuth allows to set token to be used for api key authorization
// WithApiKeyAuth allows to set token to be used for api key authorization.
func WithApiKeyAuth(token string) func(*outbound.Configuration) {
return func(c *outbound.Configuration) {
c.Callbacks = append(c.Callbacks, func() (string, string) {
Expand All @@ -92,9 +105,67 @@ func WithApiKeyAuth(token string) func(*outbound.Configuration) {
}
}

// WithClient allows to provide a pre-configured http.Client for the communication with the OFREP service
// WithClient allows to provide a pre-configured http.Client for the communication with the OFREP service.
func WithClient(client *http.Client) func(configuration *outbound.Configuration) {
return func(configuration *outbound.Configuration) {
configuration.Client = client
}
}

// WithBaseURI allows to set the base URI of the OFREP service.
func WithBaseURI(baseURI string) func(configuration *outbound.Configuration) {
return func(configuration *outbound.Configuration) {
configuration.BaseURI = baseURI
}
}

// WithTimeout allows to set the timeout for the http client used for communication with the OFREP service.
func WithTimeout(timeout time.Duration) func(configuration *outbound.Configuration) {
return func(configuration *outbound.Configuration) {
configuration.Timeout = timeout
}
}

// WithFromEnv uses environment variables to configure the provider.
//
// Experimental: This feature is experimental and may change in future versions.
//
// Supported environment variables:
// - OFREP_ENDPOINT: base URI for the OFREP service
// - OFREP_TIMEOUT: timeout duration (e.g., "30s", "500ms")
// - OFREP_API_KEY: API key for X-API-Key authentication
// - OFREP_BEARER_TOKEN: token for Bearer authentication
// - OFREP_HEADERS: comma-separated custom headers (e.g., "Key1=Value1,Key2=Value2")
func WithFromEnv() func(*outbound.Configuration) {
envHandlers := map[string]func(*outbound.Configuration, string){
"OFREP_ENDPOINT": func(c *outbound.Configuration, v string) {
WithBaseURI(v)(c)
},
"OFREP_TIMEOUT": func(c *outbound.Configuration, v string) {
if t, err := time.ParseDuration(v); err == nil {
WithTimeout(t)(c)
}
},
"OFREP_API_KEY": func(c *outbound.Configuration, v string) {
WithApiKeyAuth(v)(c)
},
"OFREP_BEARER_TOKEN": func(c *outbound.Configuration, v string) {
WithBearerToken(v)(c)
},
"OFREP_HEADERS": func(c *outbound.Configuration, v string) {
for pair := range strings.SplitSeq(v, ",") {
kv := strings.SplitN(strings.TrimSpace(pair), "=", 2)
if len(kv) == 2 {
WithHeader(kv[0], kv[1])(c)
}
}
},
}
return func(c *outbound.Configuration) {
for key, handler := range envHandlers {
if v := os.Getenv(key); v != "" {
handler(c, v)
}
}
}
}
Loading
Loading