Skip to content

Commit

Permalink
Add reason and errorCode from open-feature (#269)
Browse files Browse the repository at this point in the history
  • Loading branch information
thomaspoignant authored Jul 18, 2022
1 parent 96db56d commit 6e9af48
Show file tree
Hide file tree
Showing 14 changed files with 441 additions and 169 deletions.
12 changes: 12 additions & 0 deletions internal/flag/error_code.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package flag

// ErrorCode is an enum following the open-feature specs about error code.
type ErrorCode = string

const (
ErrorCodeProviderNotReady ErrorCode = "PROVIDER_NOT_READY"
ErrorCodeFlagNotFound ErrorCode = "FLAG_NOT_FOUND"
ErrorCodeParseError ErrorCode = "PARSE_ERROR"
ErrorCodeTypeMismatch ErrorCode = "TYPE_MISMATCH"
ErrorCodeGeneral ErrorCode = "GENERAL"
)
11 changes: 11 additions & 0 deletions internal/flag/evaluation_context.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package flag

type EvaluationContext struct {
// Environment is the name of your current env
// this value will be added to the custom information of your user and,
// it will allow to create rules based on this environment,
Environment string

// DefaultSdkValue is the default value of the SDK when calling the variation.
DefaultSdkValue interface{}
}
5 changes: 2 additions & 3 deletions internal/flag/flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ import (
)

type Flag interface {
// Value is returning the Value associate to the flag (True / False / Default ) based
// if the flag apply to the user and environment or not.
Value(flagName string, user ffuser.User, environment string) (interface{}, string)
// Value is returning the Value associate to the flag (True / False / Default )
Value(flagName string, user ffuser.User, evaluationCtx EvaluationContext) (interface{}, ResolutionDetails)

// String display correctly a flag with the right formatting
String() string
Expand Down
12 changes: 12 additions & 0 deletions internal/flag/resolution_detail.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package flag

type ResolutionDetails struct {
// Variant indicates the name of the variant used when evaluating the flag
Variant string

// Reason indicates the reason of the decision
Reason ResolutionReason

// ErrorCode indicates the error code for this evaluation
ErrorCode string
}
36 changes: 36 additions & 0 deletions internal/flag/resolution_reason.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package flag

// ResolutionReason is an enum following the open-feature specs about resolution reasons.
type ResolutionReason = string

const (
// ReasonTargetingMatch Indicates that the feature flag is targeting
// 100% of the targeting audience,
// e.g. 100% rollout percentage
ReasonTargetingMatch ResolutionReason = "TARGETING_MATCH"

// ReasonSplit Indicates that the feature flag is targeting
// a subset of the targeting audience,
// e.g. less than 100% rollout percentage
ReasonSplit ResolutionReason = "SPLIT"

// ReasonDisabled Indicates that the feature flag is disabled
ReasonDisabled ResolutionReason = "DISABLED"

// ReasonDefault Indicates that the feature flag evaluated to the default value
ReasonDefault ResolutionReason = "DEFAULT"

// ReasonStatic Indicates that the feature flag evaluated to a
// static value, for example, the default value for the flag
//
// Note: Typically means that no dynamic evaluation has been
// executed for the feature flag
ReasonStatic ResolutionReason = "STATIC"

// ReasonUnknown Indicates an unknown issue occurred during evaluation
ReasonUnknown ResolutionReason = "UNKNOWN"

// ReasonError Indicates that an error occurred during evaluation
// Note: The `errorCode`-field contains the details of this error
ReasonError ResolutionReason = "ERROR"
)
30 changes: 8 additions & 22 deletions internal/flagstate/flag_state.go
Original file line number Diff line number Diff line change
@@ -1,30 +1,16 @@
package flagstate

import (
"time"
"github.com/thomaspoignant/go-feature-flag/internal/flag"
)

// NewFlagState is creating a state for a flag.
func NewFlagState(
trackEvents bool,
value interface{},
variationType string,
failed bool,
) FlagState {
return FlagState{
Value: value,
Timestamp: time.Now().Unix(),
VariationType: variationType,
Failed: failed,
TrackEvents: trackEvents,
}
}

// FlagState represents the state of an individual feature flag, with regard to a specific user, when it was called.
type FlagState struct {
Value interface{} `json:"value"`
Timestamp int64 `json:"timestamp"`
VariationType string `json:"variationType"`
TrackEvents bool `json:"trackEvents"`
Failed bool `json:"-"`
Value interface{} `json:"value"`
Timestamp int64 `json:"timestamp"`
VariationType string `json:"variationType"`
TrackEvents bool `json:"trackEvents"`
Failed bool `json:"-"`
ErrorCode flag.ErrorCode `json:"errorCode"`
Reason flag.ResolutionReason `json:"reason"`
}
53 changes: 41 additions & 12 deletions internal/flagv1/flag_data.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@ import (
"strings"
"time"

"github.com/thomaspoignant/go-feature-flag/internal/flag"

"github.com/nikunjy/rules/parser"
"github.com/thomaspoignant/go-feature-flag/ffuser"
"github.com/thomaspoignant/go-feature-flag/internal/utils"
Expand Down Expand Up @@ -56,24 +58,56 @@ type FlagData struct {

// Value is returning the Value associate to the flag (True / False / Default ) based
// if the toggle apply to the user or not.
func (f *FlagData) Value(flagName string, user ffuser.User, environment string) (interface{}, string) {
func (f *FlagData) Value(
flagName string,
user ffuser.User,
evaluationCtx flag.EvaluationContext,
) (interface{}, flag.ResolutionDetails) {
f.updateFlagStage()
if f.isExperimentationOver() {
// if we have an experimentation that has not started or that is finished we use the default value.
return f.getDefault(), VariationDefault
return f.getDefault(), flag.ResolutionDetails{
Variant: VariationDefault,
Reason: flag.ReasonDefault,
}
}

if f.evaluateRule(user, environment) {
// Flag disable we cannot apply it.
if f.GetDisable() {
return evaluationCtx.DefaultSdkValue, flag.ResolutionDetails{
Variant: flag.VariationSDKDefault,
Reason: flag.ReasonDisabled,
}
}

// we are targeting all users
if f.getRule() == "" && f.getPercentage() == 100 {
return f.getTrue(), flag.ResolutionDetails{
Variant: VariationTrue,
Reason: flag.ReasonTargetingMatch,
}
}

if f.evaluateRule(user, evaluationCtx.Environment) {
if f.isInPercentage(flagName, user) {
// Rule applied and user in the cohort.
return f.getTrue(), VariationTrue
return f.getTrue(), flag.ResolutionDetails{
Variant: VariationTrue,
Reason: flag.ReasonSplit,
}
}
// Rule applied and user not in the cohort.
return f.getFalse(), VariationFalse
return f.getFalse(), flag.ResolutionDetails{
Variant: VariationFalse,
Reason: flag.ReasonSplit,
}
}

// Default value is used if the rule does not applied to the user.
return f.getDefault(), VariationDefault
// Default value is used if the rule does not apply to the user.
return f.getDefault(), flag.ResolutionDetails{
Variant: VariationDefault,
Reason: flag.ReasonDefault,
}
}

func (f *FlagData) isExperimentationOver() bool {
Expand Down Expand Up @@ -103,11 +137,6 @@ func (f *FlagData) isInPercentage(flagName string, user ffuser.User) bool {

// evaluateRule is checking if the rule can apply to a specific user.
func (f *FlagData) evaluateRule(user ffuser.User, environment string) bool {
// Flag disable we cannot apply it.
if f.GetDisable() {
return false
}

// No rule means that all user can be impacted.
if f.getRule() == "" {
return true
Expand Down
10 changes: 0 additions & 10 deletions internal/flagv1/flag_priv_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -28,16 +28,6 @@ func TestFlag_evaluateRule(t *testing.T) {
args args
want bool
}{
{
name: "Disabled toggle",
fields: fields{
Disable: true,
},
args: args{
user: ffuser.NewAnonymousUser("random-key"),
},
want: false,
},
{
name: "Toggle enabled and no rule",
fields: fields{
Expand Down
Loading

0 comments on commit 6e9af48

Please sign in to comment.