Skip to content

Commit

Permalink
feat: OpenFeature provider (DVC-7165) (#228)
Browse files Browse the repository at this point in the history
* simple OpenFeature wrapper for the go SDK

* doing some proper data conversions and adding unit tests

* fix lint error

* More unit tests

* code tweak

* fixed logging reference

* removed deprecated references

* fix linter errors

* test all examples on PRs

* add note to README about openfeature

* convert openfeature_provider_test to use testify

* test for ignoring nested custom properties

* add tests for string, int, float, and object evaluation and fix some bugs

* add string, number, and json variables and variations to fixture_small_config.json

* add match and unsupported type tests for JSON variables

* return type mismatch error when invalid default provided

* add Client.OpenFeatureProvider

* update openfeature example

* handle and test for mismatched types and nils internally

* test for errors returned by Variable to increase coverage

* use local bucketing instead of cloud bucketing for openfeature example

* return DevCycleProvider Local|Cloud for the OF metadata

* prefer targetingKey over userId when building user from context

* allow nil to be set in custom data

* add tests for float values and truncating to int

* fix bad import

* update README

* fix error messages for nil and unexpected variable result type

* remove direct dependency on golang.org/x/exp/maps

* fix typo in README

* fix edge cases in createUserFromEvaluationContext

---------

Co-authored-by: chris-hoefgen <[email protected]>
  • Loading branch information
rob-odwyer and chris-hoefgen authored Nov 3, 2023
1 parent 2c52fe1 commit 274ca7c
Show file tree
Hide file tree
Showing 14 changed files with 1,407 additions and 40 deletions.
4 changes: 1 addition & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
name: Test

on:
push:
branches: [ main ]
pull_request:
branches: [ main ]

Expand All @@ -25,7 +23,7 @@ jobs:

- name: Test that CGO is not required for native bucketing
run: CGO_ENABLED=0 go test -run="^$" ./...

- name: Test that variable evaluation requires zero heap allocations
run: >
go test -run=^$ -bench "^Benchmark.*_VariableSerial$" -benchmem .
Expand Down
32 changes: 32 additions & 0 deletions .github/workflows/test_examples.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
name: Test Examples

on:
pull_request:
branches: [ main ]

jobs:
test_examples:
name: Test Examples
runs-on: ubuntu-latest
env:
DEVCYCLE_SERVER_SDK_KEY: ${{ secrets.DEVCYCLE_SERVER_SDK_KEY }}
DEVCYCLE_VARIABLE_KEY: test-boolean-variable
steps:
- uses: actions/checkout@v3

- name: Set up Go
uses: actions/setup-go@v3
with:
go-version: 1.19

- name: Run local bucketing example
run: |
go run ./example/local
- name: Run cloud bucketing example
run: |
go run ./example/cloud
- name: Run openfeature example
run: |
go run ./example/openfeature
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,17 @@ The following options are available when you are using the SDK in Local Bucketin
| MaxWasmWorkers | int | The number of WASM worker objects in the object pool to support high-concurrency. <br>*Not applicable for Native Bucketing Library.* | GOMAXPROCS |
| UseDebugWASM | bool | Configures the SDK to use a debug WASM binary to generate more detailed error reporting. Use caution when enabling this setting in production environments.<br>*Not applicable for Native Bucketing Library.* | false |

# OpenFeature Support

This SDK provides an implementation of the [OpenFeature](https://openfeature.dev/) Provider interface. Use the `OpenFeatureProvider()` method on the DevCycle SDK client to obtain a provider for OpenFeature.

```go
devcycleClient, err := devcycle.NewClient("DEVCYCLE_SERVER_SDK_KEY", &options)
err = openfeature.SetProvider(devcycleClient.OpenFeatureProvider())
```

- [The DevCycle Go OpenFeature Provider](https://docs.devcycle.com/sdk/server-side-sdks/go/go-openfeature)
- [The OpenFeature documentation](https://openfeature.dev/docs/reference/intro)

## Native Bucketing Library

Expand Down
10 changes: 6 additions & 4 deletions api/model_user_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@ package api

import (
"time"

"golang.org/x/exp/maps"
)

type User struct {
Expand Down Expand Up @@ -81,7 +79,11 @@ func (p *PopulatedUser) MergeClientCustomData(ccd map[string]interface{}) {

func (p *PopulatedUser) CombinedCustomData() map[string]interface{} {
ret := make(map[string]interface{}, len(p.CustomData)+len(p.PrivateCustomData))
maps.Copy(ret, p.CustomData)
maps.Copy(ret, p.PrivateCustomData)
for k, v := range p.CustomData {
ret[k] = v
}
for k, v := range p.PrivateCustomData {
ret[k] = v
}
return ret
}
6 changes: 4 additions & 2 deletions bench-dd/go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ require (
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/dgraph-io/ristretto v0.1.0 // indirect
github.com/dustin/go-humanize v1.0.0 // indirect
github.com/go-logr/logr v1.2.4 // indirect
github.com/go-playground/locales v0.14.1 // indirect
github.com/go-playground/universal-translator v0.18.1 // indirect
github.com/go-playground/validator/v10 v10.12.0 // indirect
Expand All @@ -34,6 +35,7 @@ require (
github.com/leodido/go-urn v1.2.2 // indirect
github.com/mailru/easyjson v0.7.7 // indirect
github.com/matryer/try v0.0.0-20161228173917-9ac251b645a2 // indirect
github.com/open-feature/go-sdk v1.8.0 // indirect
github.com/philhofer/fwd v1.1.1 // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/richardartoul/molecule v1.0.1-0.20221107223329-32cfee06a052 // indirect
Expand All @@ -44,9 +46,9 @@ require (
go4.org/intern v0.0.0-20211027215823-ae77deb06f29 // indirect
go4.org/unsafe/assume-no-moving-gc v0.0.0-20220617031537-928513b29760 // indirect
golang.org/x/crypto v0.7.0 // indirect
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 // indirect
golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb // indirect
golang.org/x/sys v0.7.0 // indirect
golang.org/x/text v0.9.0 // indirect
golang.org/x/text v0.13.0 // indirect
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
google.golang.org/genproto v0.0.0-20230410155749-daa745c078e1 // indirect
Expand Down
15 changes: 10 additions & 5 deletions bench-dd/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ github.com/fortytw2/leaktest v1.3.0/go.mod h1:jDsjWgpAGjm2CA7WthBh/CdZYEPF31XHqu
github.com/fsnotify/fsnotify v1.4.7/go.mod h1:jwhsz4b93w/PPRr/qN1Yymfu8t87LnFCMoQvtojpjFo=
github.com/fsnotify/fsnotify v1.4.9/go.mod h1:znqG4EE+3YCdAaPaxE2ZRY/06pZUdp0tY4IgpuI1SZQ=
github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU=
github.com/go-logr/logr v1.2.4 h1:g01GSCwiDw2xSZfjJ2/T9M+S6pFdcNtFYsp+Y43HYDQ=
github.com/go-logr/logr v1.2.4/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A=
github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s=
github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA=
github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY=
Expand All @@ -60,6 +62,7 @@ github.com/golang/freetype v0.0.0-20170609003504-e2365dfdc4a0/go.mod h1:E/TSTwGw
github.com/golang/glog v0.0.0-20160126235308-23def4e6c14b/go.mod h1:SBH7ygxi8pfUlaOkMMuAQtPIUF8ecWP5IEl/CR7VP2Q=
github.com/golang/glog v1.1.0 h1:/d3pCKDPWNnvIWe0vVUpNP32qc8U3PDVxySP/y360qE=
github.com/golang/glog v1.1.0/go.mod h1:pfYeQZ3JWZoXTV5sFc986z3HTpwQs9At6P4ImfuP3NQ=
github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc=
github.com/golang/protobuf v1.2.0/go.mod h1:6lQm79b+lXiMfvg/cZm0SGofjICqVBUtrP5yJMmIC1U=
github.com/golang/protobuf v1.4.0-rc.1/go.mod h1:ceaxUfeHdC40wWswd/P6IGgMaK3YpKi5j83Wpe3EHw8=
github.com/golang/protobuf v1.4.0-rc.1.0.20200221234624-67d41d38c208/go.mod h1:xKAWHe0F5eneWXFV3EuXVDTCmh+JuBKY0li0aMyXATA=
Expand Down Expand Up @@ -120,6 +123,8 @@ github.com/onsi/gomega v1.7.1/go.mod h1:XdKZgCCFLUoM/7CFJVPcG8C1xQ1AJ0vpAezJrB7J
github.com/onsi/gomega v1.10.1/go.mod h1:iN09h71vgCQne3DLsj+A5owkum+a2tYe+TOCB1ybHNo=
github.com/onsi/gomega v1.17.0/go.mod h1:HnhC7FXeEQY45zxNK3PPoIUhzk/80Xly9PcubAlGdZY=
github.com/onsi/gomega v1.18.1/go.mod h1:0q+aL8jAiMXy9hbwj2mr5GziHiwhAIQpFmmtT5hitRs=
github.com/open-feature/go-sdk v1.8.0 h1:jRkP7zeSGC3pSYn/s3EzJSpO9Q6CVP8BOnmvBZYQEa0=
github.com/open-feature/go-sdk v1.8.0/go.mod h1:hpKxVZIJ0b+GpnI8imSJf9nFTcmTb0wWJZTgAS/3giw=
github.com/opentracing/opentracing-go v1.2.0 h1:uEJPy/1a5RIPAJ0Ov+OIO8OxWu77jEv+1B0VhjKrZUs=
github.com/philhofer/fwd v1.1.1 h1:GdGcTjf5RNAxwS4QLsiMzJYj5KEvPJD3Abr261yRQXQ=
github.com/philhofer/fwd v1.1.1/go.mod h1:gk3iGcWd9+svBvR0sR+KPcfE+RNWozjowpeBVG3ZVNU=
Expand Down Expand Up @@ -147,8 +152,8 @@ github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/
github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7/go.mod h1:q4W45IWZaF22tdD+VEXcAWRA037jwmWEB5VWYORlTpc=
github.com/tinylib/msgp v1.1.6 h1:i+SbKraHhnrf9M5MYmvQhFnbLhAXSDWF8WWsuyRdocw=
github.com/tinylib/msgp v1.1.6/go.mod h1:75BAfg2hauQhs3qedfdDZmWAPcFMAvJE5b9rGOMufyw=
Expand All @@ -172,8 +177,8 @@ golang.org/x/exp v0.0.0-20180807140117-3d87b88a115f/go.mod h1:CJ0aWSM057203Lf6IL
golang.org/x/exp v0.0.0-20190125153040-c74c464bbbf2/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20191030013958-a1ab85dbe136/go.mod h1:JXzH8nQsPlswgeRAPE3MuO9GYsAcnJvJ4vnMwN/5qkY=
golang.org/x/exp v0.0.0-20230321023759-10a507213a29 h1:ooxPy7fPvB4kwsA2h+iBNHkAbp/4JxTSwCmvdjEYmug=
golang.org/x/exp v0.0.0-20230321023759-10a507213a29/go.mod h1:CxIveKay+FTh1D0yPZemJVgC/95VzuuOLq5Qi4xnoYc=
golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb h1:mIKbk8weKhSeLH2GmUTrvx8CjkyJmnU1wFmg59CUjFA=
golang.org/x/exp v0.0.0-20230811145659-89c5cff77bcb/go.mod h1:FXUEEKJgO7OQYeo8N01OfiKP8RXMtf6e8aTskBGqWdc=
golang.org/x/image v0.0.0-20180708004352-c73c2afc3b81/go.mod h1:ux5Hcp/YLpHSI86hEcLt0YII63i6oz57MZXIpbrjZUs=
golang.org/x/image v0.0.0-20190227222117-0694c2d4d067/go.mod h1:kZ7UVZpmo3dzQBMxlp+ypCbDeSB+sBbTgSJuh5dn5js=
golang.org/x/image v0.0.0-20190802002840-cff245a6509b/go.mod h1:FeLwcggjj3mMvU+oOTbSwawSJRM1uh48EjtB4UJZlP0=
Expand Down Expand Up @@ -222,8 +227,8 @@ golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk=
golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ=
golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ=
golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE=
golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=
golang.org/x/text v0.13.0 h1:ablQoSUd0tRdKxZewP80B+BaqeKJuVhuRxj/dkrun3k=
golang.org/x/text v0.13.0/go.mod h1:TvPlkZtksWOMsz7fbANvkp4WM8x/WCo/om8BMLbz+aE=
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11 h1:GZokNIeuVkl3aZHJchRrr13WCsols02MLUcz1U9is6M=
golang.org/x/time v0.0.0-20211116232009-f0f3c7e86c11/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ=
golang.org/x/tools v0.0.0-20180525024113-a5b4c53f6e8b/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ=
Expand Down
26 changes: 16 additions & 10 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -104,7 +104,7 @@ func NewClient(sdkKey string, options *Options) (*Client, error) {
if c.DevCycleOptions.Logger != nil {
util.SetLogger(c.DevCycleOptions.Logger)
}
if !c.DevCycleOptions.EnableCloudBucketing {
if c.IsLocalBucketing() {
if NATIVE_SDK {
util.Infof("Using Native Bucketing")
} else {
Expand Down Expand Up @@ -149,6 +149,10 @@ func NewClient(sdkKey string, options *Options) (*Client, error) {
return c, nil
}

func (c *Client) IsLocalBucketing() bool {
return !c.DevCycleOptions.EnableCloudBucketing
}

func (c *Client) handleInitialization() {
c.isInitialized = true
if c.DevCycleOptions.OnInitializedChannel != nil {
Expand Down Expand Up @@ -237,7 +241,7 @@ Get all features by key for user data
@return map[string]Feature
*/
func (c *Client) AllFeatures(user User) (map[string]Feature, error) {
if !c.DevCycleOptions.EnableCloudBucketing {
if c.IsLocalBucketing() {
if c.hasConfig() {
user, err := c.generateBucketedConfig(user)
if err != nil {
Expand Down Expand Up @@ -316,7 +320,7 @@ func (c *Client) Variable(userdata User, key string, defaultValue interface{}) (
}

convertedDefaultValue := convertDefaultValueType(defaultValue)
variableType, err := variableTypeFromValue(key, convertedDefaultValue, !c.DevCycleOptions.EnableCloudBucketing)
variableType, err := variableTypeFromValue(key, convertedDefaultValue, c.IsLocalBucketing())

if err != nil {
return Variable{}, err
Expand All @@ -334,7 +338,7 @@ func (c *Client) Variable(userdata User, key string, defaultValue interface{}) (
}
}()

if !c.DevCycleOptions.EnableCloudBucketing {
if c.IsLocalBucketing() {
if !c.hasConfig() {
util.Warnf("Variable called before client initialized, returning default value")

Expand Down Expand Up @@ -423,7 +427,7 @@ func (c *Client) AllVariables(user User) (map[string]ReadOnlyVariable, error) {
postBody interface{}
localVarReturnValue map[string]ReadOnlyVariable
)
if !c.DevCycleOptions.EnableCloudBucketing {
if c.IsLocalBucketing() {
if c.hasConfig() {
user, err := c.generateBucketedConfig(user)
if err != nil {
Expand Down Expand Up @@ -477,7 +481,7 @@ func (c *Client) Track(user User, event Event) (bool, error) {
return false, errors.New("event type is required")
}

if !c.DevCycleOptions.EnableCloudBucketing {
if c.IsLocalBucketing() {
if c.hasConfig() {
err := c.eventQueue.QueueEvent(user, event)
if err != nil {
Expand Down Expand Up @@ -527,7 +531,7 @@ func (c *Client) Track(user User, event Event) (bool, error) {
}

func (c *Client) FlushEvents() error {
if c.DevCycleOptions.EnableCloudBucketing || !c.isInitialized {
if !c.IsLocalBucketing() || !c.isInitialized {
return nil
}

Expand All @@ -543,7 +547,7 @@ func (c *Client) FlushEvents() error {
}

func (c *Client) SetClientCustomData(customData map[string]interface{}) error {
if !c.DevCycleOptions.EnableCloudBucketing {
if c.IsLocalBucketing() {
if c.isInitialized {
return c.localBucketing.SetClientCustomData(customData)
} else {
Expand All @@ -559,7 +563,7 @@ func (c *Client) SetClientCustomData(customData map[string]interface{}) error {
Close the client and flush any pending events. Stop any ongoing tickers
*/
func (c *Client) Close() (err error) {
if c.DevCycleOptions.EnableCloudBucketing {
if !c.IsLocalBucketing() {
return
}

Expand Down Expand Up @@ -707,6 +711,8 @@ func convertDefaultValueType(value interface{}) interface{} {
}
}

var ErrInvalidDefaultValue = errors.New("the default value for variable is not of type Boolean, Number, String, or JSON")

func variableTypeFromValue(key string, value interface{}, allowNil bool) (varType string, err error) {
switch value.(type) {
case float64:
Expand All @@ -723,7 +729,7 @@ func variableTypeFromValue(key string, value interface{}, allowNil bool) (varTyp
}
}

return "", fmt.Errorf("the default value for variable %s is not of type Boolean, Number, String, or JSON", key)
return "", fmt.Errorf("%w: %s", ErrInvalidDefaultValue, key)
}

// callAPI do the request.
Expand Down
10 changes: 3 additions & 7 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,21 +35,17 @@ func TestClient_AllFeatures_Local(t *testing.T) {
}

func TestClient_AllVariablesLocal(t *testing.T) {

httpmock.Activate()
defer httpmock.DeactivateAndReset()
httpConfigMock(200)
c, err := NewClient(test_environmentKey, &Options{})
fatalErr(t, err)
require.NoError(t, err)

variables, err := c.AllVariables(
User{UserId: "j_test", DeviceModel: "testing"})
fatalErr(t, err)
require.NoError(t, err)

fmt.Println(variables)
if len(variables) != 1 {
t.Error("Expected 1 variable, got", len(variables))
}
require.Len(t, variables, 5)
}

func TestClient_AllVariablesLocal_WithSpecialCharacters(t *testing.T) {
Expand Down
73 changes: 73 additions & 0 deletions example/openfeature/main.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package main

import (
"context"
"log"
"os"
"time"

devcycle "github.com/devcyclehq/go-server-sdk/v2"
"github.com/open-feature/go-sdk/pkg/openfeature"
)

func main() {
sdkKey := os.Getenv("DEVCYCLE_SERVER_SDK_KEY")
if sdkKey == "" {
log.Fatal("DEVCYCLE_SERVER_SDK_KEY env var not set: set it to your SDK key")
}

dvcOptions := devcycle.Options{
EnableEdgeDB: false,
EnableCloudBucketing: false,
EventFlushIntervalMS: time.Second * 10,
ConfigPollingIntervalMS: time.Second * 10,
RequestTimeout: time.Second * 10,
DisableAutomaticEventLogging: false,
DisableCustomEventLogging: false,
}
dvcClient, _ := devcycle.NewClient(sdkKey, &dvcOptions)

if err := openfeature.SetProvider(dvcClient.OpenFeatureProvider()); err != nil {
log.Fatalf("Failed to set DevCycle provider: %v", err)
}
client := openfeature.NewClient("devcycle")

evalCtx := openfeature.NewEvaluationContext("test-1234", map[string]interface{}{
"email": "[email protected]",
"name": "Test User",
"language": "en",
"country": "CA",
"appVersion": "1.0.0",
"appBuild": "1",
"customData": map[string]interface{}{"custom": "data"},
"privateCustomData": map[string]interface{}{"private": "data"},
"deviceModel": "Macbook",
})

// Retrieving an object variable with a default value
value, err := client.ObjectValue(context.Background(), "test-json-variable", map[string]interface{}{"value": "default"}, evalCtx)

if err != nil {
log.Fatal(err)
}
log.Printf("Variable results: %#v", value)

// Checking a boolean variable flag
booleanVariable := "test-boolean-variable"
if featureEnabled, err := client.BooleanValue(context.Background(), booleanVariable, false, evalCtx); err != nil {
log.Printf("Error retrieving feature flag: %v", err)
} else if featureEnabled {
log.Printf("%v = true, feature is enabled", booleanVariable)
} else {
log.Printf("%v = false, feature is disabled", booleanVariable)
}

// Retrieving a string variable along with the resolution details
details, err := client.StringValueDetails(context.Background(), "doesnt-exist", "default", evalCtx)

if err != nil {
log.Fatal(err)
}

log.Printf("Variable results for unknown variable: %#v", details)
}
Loading

0 comments on commit 274ca7c

Please sign in to comment.