Repository: open-feature/go-sdk (go)

Issue:
[BUG] Awkward usage when trying to evaluate a boolean flag in a complex conditional
### Observed behavior

(Related to #259, but that one was closed, so filing this instead)

I just ran into a papercut when working on an experiment to move from an in-house feature management system to OpenFeature. The previous system allowed calling a simple `IsEnabled(flag string) bool` method, which I'd like to replace with a call to `client.BooleanValue`. Mostly this works fine, but I just ran into an awkward case for some code with a fairly convoluted conditional:

```go
if cond1 && (cond2 || (cond3 && features.IsEnabled(ctx, "myflag"))) {
...
```

In this case it's impossible to simply replace the `features.IsEnabled` call with `client.BooleanValue` due to the `err` return value. And I'd rather avoid evaluating the flag altogether if `cond1` is false.

This isn't insurmountable, but it does make it more awkward to use OpenFeature in this case, and will cause adoption friction.

### Expected Behavior

The package should be less awkward to integrate with an existing project.

Could an additional set of methods be considered like these?

```go
func (c *Client) MustBoolean(ctx context.Context, flag string, defaultValue bool, evalCtx EvaluationContext, options ...Option) bool

func (c *Client) MustFloat(ctx context.Context, flag string, defaultValue float64, evalCtx EvaluationContext, options ...Option) float64

func (c *Client) MustInt(ctx context.Context, flag string, defaultValue int64, evalCtx EvaluationContext, options ...Option) int64

func (c *Client) MustObject(ctx context.Context, flag string, defaultValue interface{}, evalCtx EvaluationContext, options ...Option) interface{}

func (c *Client) MustString(ctx context.Context, flag string, defaultValue string, evalCtx EvaluationContext, options ...Option) string
```

### Steps to reproduce

(See above)

Pull request description:
feat: Adding simplified evaluation methods
<!-- Please use this template for your pull request. -->
<!-- Please use the sections that you need and delete other sections -->

## This PR
<!-- add the description of the PR here -->

- adds new client methods `Boolean`, `String`, `Float`, `Int`, and `Object` that enable simpler usage (no error handling required)

### Related Issues
<!-- add here the GitHub issue that this PR resolves if applicable -->

Fixes #262

### Notes
<!-- any additional notes for this PR -->

There seems to be a soft consensus on #262, so this may need to change if the TC deems necessary, but I'm submitting this early for discussion's sake.

### Follow-up Tasks
<!-- anything that is related to this PR but not done here should be noted under this section -->
<!-- if there is a need for a new issue, please link it here -->

### How to test
<!-- if applicable, add testing instructions under this section -->

See additional examples provided.


Patch:
diff --git a/openfeature/client.go b/openfeature/client.go
index 3391531..4bb83f2 100644
--- a/openfeature/client.go
+++ b/openfeature/client.go
@@ -28,6 +28,12 @@ type IClient interface {
 	FloatValueDetails(ctx context.Context, flag string, defaultValue float64, evalCtx EvaluationContext, options ...Option) (FloatEvaluationDetails, error)
 	IntValueDetails(ctx context.Context, flag string, defaultValue int64, evalCtx EvaluationContext, options ...Option) (IntEvaluationDetails, error)
 	ObjectValueDetails(ctx context.Context, flag string, defaultValue interface{}, evalCtx EvaluationContext, options ...Option) (InterfaceEvaluationDetails, error)
+
+	Boolean(ctx context.Context, flag string, defaultValue bool, evalCtx EvaluationContext, options ...Option) bool
+	String(ctx context.Context, flag string, defaultValue string, evalCtx EvaluationContext, options ...Option) string
+	Float(ctx context.Context, flag string, defaultValue float64, evalCtx EvaluationContext, options ...Option) float64
+	Int(ctx context.Context, flag string, defaultValue int64, evalCtx EvaluationContext, options ...Option) int64
+	Object(ctx context.Context, flag string, defaultValue interface{}, evalCtx EvaluationContext, options ...Option) interface{}
 }
 
 // ClientMetadata provides a client's metadata
@@ -57,6 +63,9 @@ type Client struct {
 	logger            func() logr.Logger
 }
 
+// interface guard to ensure that Client implements IClient
+var _ IClient = (*Client)(nil)
+
 // NewClient returns a new Client. Name is a unique identifier for this client
 func NewClient(name string) *Client {
 	return &Client{
@@ -580,6 +589,86 @@ func (c *Client) ObjectValueDetails(ctx context.Context, flag string, defaultVal
 	return c.evaluate(ctx, flag, Object, defaultValue, evalCtx, *evalOptions)
 }
 
+// Boolean performs a flag evaluation that returns a boolean. Any error
+// encountered during the evaluation will result in the default value being
+// returned. To explicitly handle errors, use [BooleanValue] or [BooleanValueDetails]
+//
+// Parameters:
+// - ctx is the standard go context struct used to manage requests (e.g. timeouts)
+// - flag is the key that uniquely identifies a particular flag
+// - defaultValue is returned if an error occurs
+// - evalCtx is the evaluation context used in a flag evaluation (not to be confused with ctx)
+// - options are optional additional evaluation options e.g. WithHooks & WithHookHints
+func (c *Client) Boolean(ctx context.Context, flag string, defaultValue bool, evalCtx EvaluationContext, options ...Option) bool {
+	value, _ := c.BooleanValue(ctx, flag, defaultValue, evalCtx, options...)
+
+	return value
+}
+
+// String performs a flag evaluation that returns a string. Any error
+// encountered during the evaluation will result in the default value being
+// returned. To explicitly handle errors, use [StringValue] or [StringValueDetails]
+//
+// Parameters:
+// - ctx is the standard go context struct used to manage requests (e.g. timeouts)
+// - flag is the key that uniquely identifies a particular flag
+// - defaultValue is returned if an error occurs
+// - evalCtx is the evaluation context used in a flag evaluation (not to be confused with ctx)
+// - options are optional additional evaluation options e.g. WithHooks & WithHookHints
+func (c *Client) String(ctx context.Context, flag string, defaultValue string, evalCtx EvaluationContext, options ...Option) string {
+	value, _ := c.StringValue(ctx, flag, defaultValue, evalCtx, options...)
+
+	return value
+}
+
+// Float performs a flag evaluation that returns a float64. Any error
+// encountered during the evaluation will result in the default value being
+// returned. To explicitly handle errors, use [FloatValue] or [FloatValueDetails]
+//
+// Parameters:
+// - ctx is the standard go context struct used to manage requests (e.g. timeouts)
+// - flag is the key that uniquely identifies a particular flag
+// - defaultValue is returned if an error occurs
+// - evalCtx is the evaluation context used in a flag evaluation (not to be confused with ctx)
+// - options are optional additional evaluation options e.g. WithHooks & WithHookHints
+func (c *Client) Float(ctx context.Context, flag string, defaultValue float64, evalCtx EvaluationContext, options ...Option) float64 {
+	value, _ := c.FloatValue(ctx, flag, defaultValue, evalCtx, options...)
+
+	return value
+}
+
+// Int performs a flag evaluation that returns an int64. Any error
+// encountered during the evaluation will result in the default value being
+// returned. To explicitly handle errors, use [IntValue] or [IntValueDetails]
+//
+// Parameters:
+// - ctx is the standard go context struct used to manage requests (e.g. timeouts)
+// - flag is the key that uniquely identifies a particular flag
+// - defaultValue is returned if an error occurs
+// - evalCtx is the evaluation context used in a flag evaluation (not to be confused with ctx)
+// - options are optional additional evaluation options e.g. WithHooks & WithHookHints
+func (c *Client) Int(ctx context.Context, flag string, defaultValue int64, evalCtx EvaluationContext, options ...Option) int64 {
+	value, _ := c.IntValue(ctx, flag, defaultValue, evalCtx, options...)
+
+	return value
+}
+
+// Object performs a flag evaluation that returns an object. Any error
+// encountered during the evaluation will result in the default value being
+// returned. To explicitly handle errors, use [ObjectValue] or [ObjectValueDetails]
+//
+// Parameters:
+// - ctx is the standard go context struct used to manage requests (e.g. timeouts)
+// - flag is the key that uniquely identifies a particular flag
+// - defaultValue is returned if an error occurs
+// - evalCtx is the evaluation context used in a flag evaluation (not to be confused with ctx)
+// - options are optional additional evaluation options e.g. WithHooks & WithHookHints
+func (c *Client) Object(ctx context.Context, flag string, defaultValue interface{}, evalCtx EvaluationContext, options ...Option) interface{} {
+	value, _ := c.ObjectValue(ctx, flag, defaultValue, evalCtx, options...)
+
+	return value
+}
+
 func (c *Client) evaluate(
 	ctx context.Context, flag string, flagType Type, defaultValue interface{}, evalCtx EvaluationContext, options EvaluationOptions,
 ) (InterfaceEvaluationDetails, error) {


Test patch:
diff --git a/openfeature/client_example_test.go b/openfeature/client_example_test.go
index 4c257f9..27bc08c 100644
--- a/openfeature/client_example_test.go
+++ b/openfeature/client_example_test.go
@@ -80,3 +80,52 @@ func ExampleClient_ObjectValue() {
 	fmt.Printf("test-flag value: %v", string(str))
 	// Output: test-flag value: {"foo":"bar"}
 }
+
+func ExampleClient_Boolean() {
+	ctx := context.Background()
+	client := openfeature.NewClient("example-client")
+
+	if client.Boolean(ctx, "myflag", true, openfeature.EvaluationContext{}) {
+		fmt.Println("myflag is true")
+	} else {
+		fmt.Println("myflag is false")
+	}
+
+	// Output: myflag is true
+}
+
+func ExampleClient_String() {
+	ctx := context.Background()
+	client := openfeature.NewClient("example-client")
+
+	fmt.Println(client.String(ctx, "myflag", "default", openfeature.EvaluationContext{}))
+
+	// Output: default
+}
+
+func ExampleClient_Float() {
+	ctx := context.Background()
+	client := openfeature.NewClient("example-client")
+
+	fmt.Println(client.Float(ctx, "myflag", 0.5, openfeature.EvaluationContext{}))
+
+	// Output: 0.5
+}
+
+func ExampleClient_Int() {
+	ctx := context.Background()
+	client := openfeature.NewClient("example-client")
+
+	fmt.Println(client.Int(ctx, "myflag", 5, openfeature.EvaluationContext{}))
+
+	// Output: 5
+}
+
+func ExampleClient_Object() {
+	ctx := context.Background()
+	client := openfeature.NewClient("example-client")
+
+	fmt.Println(client.Object(ctx, "myflag", map[string]string{"foo": "bar"}, openfeature.EvaluationContext{}))
+
+	// Output: map[foo:bar]
+}