diff --git a/internal/flagset/flagset.go b/internal/flagset/flagset.go new file mode 100644 index 00000000000..195ba0fedbc --- /dev/null +++ b/internal/flagset/flagset.go @@ -0,0 +1,23 @@ +package flagset + +import ( + "github.com/thomaspoignant/go-feature-flag/exporter" + "github.com/thomaspoignant/go-feature-flag/internal/cache" + "github.com/thomaspoignant/go-feature-flag/retriever" +) + +type Type string + +const ( + FlagSetTypeStatic Type = "static" + FlagSetTypeDynamic Type = "dynamic" +) + +type FlagSet struct { + Name string + Type Type + + Cache cache.Manager + DataExporter *exporter.Scheduler + RetrieverManager *retriever.Manager +} diff --git a/internal/flagset/flagsetBuilder.go b/internal/flagset/flagsetBuilder.go new file mode 100644 index 00000000000..66680577da7 --- /dev/null +++ b/internal/flagset/flagsetBuilder.go @@ -0,0 +1,66 @@ +package flagset + +import ( + "errors" + + "github.com/thomaspoignant/go-feature-flag/exporter" + "github.com/thomaspoignant/go-feature-flag/notifier" + "github.com/thomaspoignant/go-feature-flag/retriever" +) + +var ( + ErrInvalidFlagSetType = errors.New("invalid flagset type") + ErrNameRequired = errors.New("flagset name is required") +) + +type Builder interface { + Notifiers([]notifier.Notifier) Builder + Retrievers([]retriever.Retriever) Builder + Exporter(exporter.CommonExporter) Builder + Build() (*FlagSet, error) +} + +type builderImpl struct { + name string + flagSetType Type + notifiers []notifier.Notifier + retrievers []retriever.Retriever + exporter exporter.CommonExporter +} + +func NewBuilder(name string, flagSetType Type) Builder { + return &builderImpl{ + name: name, + flagSetType: flagSetType, + } +} + +func (f *builderImpl) Notifiers(notifiers []notifier.Notifier) Builder { + f.notifiers = notifiers + return f +} + +func (f *builderImpl) Retrievers(retrievers []retriever.Retriever) Builder { + f.retrievers = retrievers + return f +} + +func (f *builderImpl) Exporter(exporter exporter.CommonExporter) Builder { + f.exporter = exporter + return f +} + +func (f *builderImpl) Build() (*FlagSet, error) { + if f.name == "" { + return nil, ErrNameRequired + } + if f.flagSetType != FlagSetTypeDynamic && f.flagSetType != FlagSetTypeStatic { + return nil, ErrInvalidFlagSetType + } + + return &FlagSet{ + Name: f.name, + Type: f.flagSetType, + }, nil + +} diff --git a/internal/flagset/flagsetBuilder_test.go b/internal/flagset/flagsetBuilder_test.go new file mode 100644 index 00000000000..c8951f256bc --- /dev/null +++ b/internal/flagset/flagsetBuilder_test.go @@ -0,0 +1,85 @@ +package flagset_test + +import ( + "log/slog" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/thomaspoignant/go-feature-flag/exporter" + "github.com/thomaspoignant/go-feature-flag/exporter/logsexporter" + "github.com/thomaspoignant/go-feature-flag/internal/flagset" + "github.com/thomaspoignant/go-feature-flag/notifier" + "github.com/thomaspoignant/go-feature-flag/notifier/logsnotifier" + "github.com/thomaspoignant/go-feature-flag/retriever" + "github.com/thomaspoignant/go-feature-flag/retriever/fileretriever" + "github.com/thomaspoignant/go-feature-flag/utils/fflog" +) + +func TestFlagSetBuilder(t *testing.T) { + tests := []struct { + name string + flagsetName string + flagsetType flagset.Type + wantErr assert.ErrorAssertionFunc + notifiers []notifier.Notifier + retrievers []retriever.Retriever + exporter exporter.CommonExporter + expected *flagset.FlagSet + expectedError string + }{ + { + name: "Should return an error if no name for flag set", + flagsetName: "", + flagsetType: flagset.FlagSetTypeDynamic, + wantErr: assert.Error, + expectedError: "flagset name is required", + }, + { + name: "Should return an error if no flag set type provided", + flagsetName: "test", + wantErr: assert.Error, + expectedError: "invalid flagset type", + }, + { + name: "Should return an error if wrong flag set type provided", + flagsetName: "test", + flagsetType: "foobar", + wantErr: assert.Error, + expectedError: "invalid flagset type", + }, + { + name: "Should return a flag set will everything set", + flagsetName: "test", + wantErr: assert.NoError, + notifiers: []notifier.Notifier{ + &logsnotifier.Notifier{ + Logger: &fflog.FFLogger{LeveledLogger: slog.Default()}, + }, + }, + retrievers: []retriever.Retriever{ + &fileretriever.Retriever{Path: "../../testdata/flag1.json"}, + }, + exporter: &logsexporter.Exporter{}, + expected: &flagset.FlagSet{ + Name: "test", + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + fsBuilder := flagset.NewBuilder(tt.flagsetName, tt.flagsetType) + fsBuilder.Notifiers(tt.notifiers) + fsBuilder.Retrievers(tt.retrievers) + fsBuilder.Exporter(tt.exporter) + got, err := fsBuilder.Build() + tt.wantErr(t, err) + + if tt.expectedError != "" { + assert.Equal(t, tt.expectedError, err.Error()) + return + } + assert.Equal(t, tt.expected, got) + }) + } +} diff --git a/internal/flagset/manager.go b/internal/flagset/manager.go new file mode 100644 index 00000000000..2d5374ed9d4 --- /dev/null +++ b/internal/flagset/manager.go @@ -0,0 +1,21 @@ +package flagset + +import "fmt" + +type Manager struct { + FlagSets map[string]*FlagSet +} + +func NewManager() *Manager { + return &Manager{ + FlagSets: make(map[string]*FlagSet), + } +} + +func (f *Manager) AddFlagSet(name string, flagSet *FlagSet) error { + if _, ok := f.FlagSets[name]; ok { + return fmt.Errorf("flagset %s already exists", name) + } + f.FlagSets[name] = flagSet + return nil +}