Skip to content

Commit

Permalink
Change schema for % rollout flag: validate and populate missing fields
Browse files Browse the repository at this point in the history
  • Loading branch information
drichelson committed Jun 4, 2024
1 parent 3ff633d commit 8a13780
Show file tree
Hide file tree
Showing 6 changed files with 159 additions and 7 deletions.
37 changes: 37 additions & 0 deletions internal/dorkly/flag.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package dorkly

import (
"encoding/base64"
"errors"
"github.com/launchdarkly/go-sdk-common/v3/ldvalue"
"github.com/launchdarkly/go-server-sdk-evaluation/v3/ldmodel"
)
Expand Down Expand Up @@ -30,22 +31,58 @@ const (
// when combined with a FlagBase, it can be converted to a LaunchDarkly FeatureFlag
type FlagConfigForEnv interface {
ToLdFlag(flagBase FlagBase) ldmodel.FeatureFlag
Validate(flagBase FlagBase) error
}

var _ FlagConfigForEnv = &FlagBoolean{}

// FlagBoolean is a boolean flag that is either on (true) or off (false)
type FlagBoolean struct {
Variation bool `yaml:"variation"`
}

func (f *FlagBoolean) Validate(flagBase FlagBase) error {
return nil
}

func (f *FlagBoolean) ToLdFlag(flagBase FlagBase) ldmodel.FeatureFlag {
return flagBase.ldFeatureFlagBoolean(f.Variation)
}

var _ FlagConfigForEnv = &FlagBooleanRollout{}

// FlagBooleanRollout is a boolean flag that is on (true) for a percentage of users based on the id field
type FlagBooleanRollout struct {
PercentRollout BooleanRolloutVariation `yaml:"percentRollout"`
}

func (f *FlagBooleanRollout) Validate(flagBase FlagBase) error {
if f.PercentRollout.True < 0.0 {
return errors.New("percentRollout.true must be >= 0")
}
if f.PercentRollout.False < 0.0 {
return errors.New("percentRollout.false must be >= 0")
}

if f.PercentRollout.True+f.PercentRollout.False > 100 {
return errors.New("sum of percentRollout values must be <= 100")
}

if f.PercentRollout.True == 0.0 && f.PercentRollout.False == 0 {
return errors.New("at least one of percentRollout.true or percentRollout.false must be > 0")
}

if f.PercentRollout.True == 0.0 {
f.PercentRollout.True = 100.0 - f.PercentRollout.False
}

if f.PercentRollout.False == 0.0 {
f.PercentRollout.False = 100.0 - f.PercentRollout.True
}

return nil
}

type BooleanRolloutVariation struct {
True float64 `yaml:"true"`
False float64 `yaml:"false"`
Expand Down
111 changes: 111 additions & 0 deletions internal/dorkly/flag_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,117 @@ import (
"testing"
)

func TestFlagBooleanRollout_Validate(t *testing.T) {
tests := []struct {
name string
input FlagBooleanRollout
expected *FlagBooleanRollout
wantErr bool
}{
{
name: "ValidPercentages",
input: FlagBooleanRollout{
PercentRollout: BooleanRolloutVariation{
True: 30.0,
False: 70.0,
},
},
expected: &FlagBooleanRollout{
PercentRollout: BooleanRolloutVariation{
True: 30.0,
False: 70.0,
},
},
wantErr: false,
},
{
name: "ValidTruePercentage",
input: FlagBooleanRollout{
PercentRollout: BooleanRolloutVariation{
True: 30.0,
},
},
expected: &FlagBooleanRollout{
PercentRollout: BooleanRolloutVariation{
True: 30.0,
False: 70.0,
},
},
wantErr: false,
},
{
name: "ValidFalsePercentage",
input: FlagBooleanRollout{
PercentRollout: BooleanRolloutVariation{
False: 70.0,
},
},
expected: &FlagBooleanRollout{
PercentRollout: BooleanRolloutVariation{
True: 30.0,
False: 70.0,
},
},
wantErr: false,
},
{
name: "NegativeTruePercentage",
input: FlagBooleanRollout{
PercentRollout: BooleanRolloutVariation{
True: -10.0,
False: 70.0,
},
},
expected: nil,
wantErr: true,
},
{
name: "NegativeFalsePercentage",
input: FlagBooleanRollout{
PercentRollout: BooleanRolloutVariation{
True: 30.0,
False: -10.0,
},
},
expected: nil,
wantErr: true,
},
{
name: "SumGreaterThan100",
input: FlagBooleanRollout{
PercentRollout: BooleanRolloutVariation{
True: 60.0,
False: 50.0,
},
},
wantErr: true,
},
{
name: "BothPercentagesZero",
input: FlagBooleanRollout{
PercentRollout: BooleanRolloutVariation{
True: 0.0,
False: 0.0,
},
},
expected: nil,
wantErr: true,
},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := tt.input.Validate(FlagBase{})
if tt.wantErr {
assert.Error(t, err)
} else {
assert.NoError(t, err)
assert.Equal(t, tt.expected, &tt.input)
}
})
}
}

func Test_FlagBoolean_ToLdFlag(t *testing.T) {
cases := []struct {
name string
Expand Down
8 changes: 7 additions & 1 deletion internal/dorkly/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -110,10 +110,16 @@ func (p *Project) loadFlagYamlFile(filePath string) (*Flag, error) {
flag.key = getFileNameNoSuffix(f.Name())
flag.envConfigs = make(map[string]FlagConfigForEnv)
for _, env := range p.environments {
flag.envConfigs[env], err = p.loadFlagConfigForEnvYamlFile(flag, filepath.Join(p.path, "environments", env, flag.key+".yml"))
flagEnvConfig, err := p.loadFlagConfigForEnvYamlFile(flag, filepath.Join(p.path, "environments", env, flag.key+".yml"))
if err != nil {
return nil, err
}
err = flagEnvConfig.Validate(flag.FlagBase)
if err != nil {
return nil, err
}
flag.envConfigs[env] = flagEnvConfig

}

return &flag, nil
Expand Down
4 changes: 2 additions & 2 deletions internal/dorkly/project_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ var (
EnableMobileKey: true,
},
envConfigs: map[string]FlagConfigForEnv{
"production": &FlagBooleanRollout{PercentRollout: 31.0},
"staging": &FlagBooleanRollout{PercentRollout: 100.0},
"production": &FlagBooleanRollout{PercentRollout: BooleanRolloutVariation{True: 31.0, False: 69.0}},
"staging": &FlagBooleanRollout{PercentRollout: BooleanRolloutVariation{True: 100.0, False: 0.0}},
},
},
},
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
percentRollout:
true: 31.0
false: 69.0
true: 31.0
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
percentRollout:
true: 100.0
false: 0.0
true: 100.0

0 comments on commit 8a13780

Please sign in to comment.