Skip to content

Commit

Permalink
feat!: flagSetMetadata in OFREP/ResolveAll, core refactors
Browse files Browse the repository at this point in the history
Signed-off-by: Todd Baert <todd.baert@dynatrace.com>
  • Loading branch information
toddbaert committed Jan 29, 2025
1 parent 4281c6e commit 738c41d
Show file tree
Hide file tree
Showing 30 changed files with 441 additions and 986 deletions.
9 changes: 8 additions & 1 deletion config/samples/example_flags.flagd.json
Original file line number Diff line number Diff line change
@@ -1,13 +1,20 @@
{
"$schema": "https://flagd.dev/schema/v0/flags.json",
"metadata": {
"flagSetId": "example",
"version": "v1"
},
"flags": {
"myBoolFlag": {
"state": "ENABLED",
"variants": {
"on": true,
"off": false
},
"defaultVariant": "on"
"defaultVariant": "on",
"metadata": {
"version": "v2"
}
},
"myStringFlag": {
"state": "ENABLED",
Expand Down
3 changes: 3 additions & 0 deletions config/samples/example_flags_secondary.flagd.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
{
"$schema": "https://flagd.dev/schema/v0/flags.json",
"metadata": {
"version": "v2"
},
"flags": {
"myBoolFlag": {
"state": "ENABLED",
Expand Down
4 changes: 0 additions & 4 deletions core/go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -317,10 +317,6 @@ golang.org/x/crypto v0.18.0/go.mod h1:R0j02AL6hcrfOiy9T4ZYp/rcWeMxM3L6QYxlOuEG1m
golang.org/x/crypto v0.32.0 h1:euUpcYgM8WcP71gNpTqQCn6rC2t6ULUPiOzfWaXVVfc=
golang.org/x/crypto v0.32.0/go.mod h1:ZnnJkOaASj8g0AjIduWNlq2NRxL0PlBrbKVyZ6V/Ugc=
golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA=
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/exp v0.0.0-20250128144449-3edf0e91c1ae h1:COZdc9Ut6wLq7MO9GIYxfZl4n4ScmgqQLoHocKXrxco=
golang.org/x/exp v0.0.0-20250128144449-3edf0e91c1ae/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c h1:KL/ZBHXgKGVmuZBZ01Lt57yE5ws8ZPSkkihmEyq7FXc=
golang.org/x/exp v0.0.0-20250128182459-e0ece0dbea4c/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE=
Expand Down
2 changes: 1 addition & 1 deletion core/pkg/evaluator/fractional_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -587,7 +587,7 @@ func BenchmarkFractionalEvaluation(b *testing.B) {
for name, tt := range tests {
b.Run(name, func(b *testing.B) {
log := logger.NewLogger(nil, false)
je := NewJSON(log, &store.Flags{Flags: tt.flags.Flags})
je := NewJSON(log, &store.State{Flags: tt.flags.Flags})
for i := 0; i < b.N; i++ {
value, variant, reason, _, err := resolve[string](
ctx, reqID, tt.flagKey, tt.context, je.evaluateVariant)
Expand Down
19 changes: 10 additions & 9 deletions core/pkg/evaluator/ievaluator.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package evaluator
import (
"context"

"github.com/open-feature/flagd/core/pkg/model"
"github.com/open-feature/flagd/core/pkg/sync"
)

Expand All @@ -11,12 +12,12 @@ type AnyValue struct {
Variant string
Reason string
FlagKey string
Metadata map[string]interface{}
Metadata model.Metadata
Error error
}

func NewAnyValue(
value interface{}, variant string, reason string, flagKey string, metadata map[string]interface{},
value interface{}, variant string, reason string, flagKey string, metadata model.Metadata,
err error,
) AnyValue {
return AnyValue{
Expand All @@ -34,7 +35,7 @@ IEvaluator is an extension of IResolver, allowing storage updates and retrievals
*/
type IEvaluator interface {
GetState() (string, error)
SetState(payload sync.DataSync) (map[string]interface{}, bool, error)
SetState(payload sync.DataSync) (model.Metadata, bool, error)
IResolver
}

Expand All @@ -44,31 +45,31 @@ type IResolver interface {
ctx context.Context,
reqID string,
flagKey string,
context map[string]any) (value bool, variant string, reason string, metadata map[string]interface{}, err error)
context map[string]any) (value bool, variant string, reason string, metadata model.Metadata, err error)
ResolveStringValue(
ctx context.Context,
reqID string,
flagKey string,
context map[string]any) (
value string, variant string, reason string, metadata map[string]interface{}, err error)
value string, variant string, reason string, metadata model.Metadata, err error)
ResolveIntValue(
ctx context.Context,
reqID string,
flagKey string,
context map[string]any) (
value int64, variant string, reason string, metadata map[string]interface{}, err error)
value int64, variant string, reason string, metadata model.Metadata, err error)
ResolveFloatValue(
ctx context.Context,
reqID string,
flagKey string,
context map[string]any) (
value float64, variant string, reason string, metadata map[string]interface{}, err error)
value float64, variant string, reason string, metadata model.Metadata, err error)
ResolveObjectValue(
ctx context.Context,
reqID string,
flagKey string,
context map[string]any) (
value map[string]any, variant string, reason string, metadata map[string]interface{}, err error)
value map[string]any, variant string, reason string, metadata model.Metadata, err error)
ResolveAsAnyValue(
ctx context.Context,
reqID string,
Expand All @@ -77,5 +78,5 @@ type IResolver interface {
ResolveAllValues(
ctx context.Context,
reqID string,
context map[string]any) (values []AnyValue, err error)
context map[string]any) (resolutions []AnyValue, metadata model.Metadata, err error)
}
56 changes: 19 additions & 37 deletions core/pkg/evaluator/json.go
Original file line number Diff line number Diff line change
Expand Up @@ -64,13 +64,13 @@ func WithEvaluator(name string, evalFunc func(interface{}, interface{}) interfac

// JSON evaluator
type JSON struct {
store *store.Flags
store *store.State
Logger *logger.Logger
jsonEvalTracer trace.Tracer
Resolver
}

func NewJSON(logger *logger.Logger, s *store.Flags, opts ...JSONEvaluatorOption) *JSON {
func NewJSON(logger *logger.Logger, s *store.State, opts ...JSONEvaluatorOption) *JSON {
logger = logger.WithFields(
zap.String("component", "evaluator"),
zap.String("evaluator", "json"),
Expand Down Expand Up @@ -107,9 +107,9 @@ func (je *JSON) SetState(payload sync.DataSync) (map[string]interface{}, bool, e
trace.WithAttributes(attribute.String("feature_flag.sync_type", payload.Type.String())))
defer span.End()

var newFlags Flags
var definition Definition

err := configToFlags(je.Logger, payload.FlagData, &newFlags)
err := configToFlagDefinition(je.Logger, payload.FlagData, &definition)
if err != nil {
span.SetStatus(codes.Error, "flagSync error")
span.RecordError(err)
Expand All @@ -119,15 +119,16 @@ func (je *JSON) SetState(payload sync.DataSync) (map[string]interface{}, bool, e
var events map[string]interface{}
var reSync bool

// TODO: handle other types
switch payload.Type {
case sync.ALL:
events, reSync = je.store.Merge(je.Logger, payload.Source, payload.Selector, newFlags.Flags)
events, reSync = je.store.Merge(je.Logger, payload.Source, payload.Selector, definition.Flags, definition.Metadata)
case sync.ADD:
events = je.store.Add(je.Logger, payload.Source, payload.Selector, newFlags.Flags)
events = je.store.Add(je.Logger, payload.Source, payload.Selector, definition.Flags)
case sync.UPDATE:
events = je.store.Update(je.Logger, payload.Source, payload.Selector, newFlags.Flags)
events = je.store.Update(je.Logger, payload.Source, payload.Selector, definition.Flags)
case sync.DELETE:
events = je.store.DeleteFlags(je.Logger, payload.Source, newFlags.Flags)
events = je.store.DeleteFlags(je.Logger, payload.Source, definition.Flags)
default:
return nil, false, fmt.Errorf("unsupported sync type: %d", payload.Type)
}
Expand Down Expand Up @@ -156,14 +157,14 @@ func NewResolver(store store.IStore, logger *logger.Logger, jsonEvalTracer trace
return Resolver{store: store, Logger: logger, tracer: jsonEvalTracer}
}

func (je *Resolver) ResolveAllValues(ctx context.Context, reqID string, context map[string]any) ([]AnyValue, error) {
func (je *Resolver) ResolveAllValues(ctx context.Context, reqID string, context map[string]any) ([]AnyValue, model.Metadata, error) {
_, span := je.tracer.Start(ctx, "resolveAll")
defer span.End()

var err error
allFlags, err := je.store.GetAll(ctx)
allFlags, flagSetMetadata, err := je.store.GetAll(ctx)
if err != nil {
return nil, fmt.Errorf("error retreiving flags from the store: %w", err)
return nil, flagSetMetadata, fmt.Errorf("error retreiving flags from the store: %w", err)
}

values := []AnyValue{}
Expand Down Expand Up @@ -195,7 +196,7 @@ func (je *Resolver) ResolveAllValues(ctx context.Context, reqID string, context
values = append(values, NewAnyValue(value, variant, reason, flagKey, metadata, err))
}

return values, nil
return values, flagSetMetadata, nil
}

func (je *Resolver) ResolveBooleanValue(
Expand Down Expand Up @@ -312,9 +313,7 @@ func resolve[T constraints](ctx context.Context, reqID string, key string, conte
func (je *Resolver) evaluateVariant(ctx context.Context, reqID string, flagKey string, evalCtx map[string]any) (
variant string, variants map[string]interface{}, reason string, metadata map[string]interface{}, err error,
) {
metadata = map[string]interface{}{}

flag, ok := je.store.Get(ctx, flagKey)
flag, metadata, ok := je.store.Get(ctx, flagKey)
if !ok {
// flag not found
je.Logger.DebugWithID(reqID, fmt.Sprintf("requested flag could not be found: %s", flagKey))
Expand Down Expand Up @@ -447,8 +446,8 @@ func loadAndCompileSchema(log *logger.Logger) *gojsonschema.Schema {
return compiledSchema
}

// configToFlags convert string configurations to flags and store them to pointer newFlags
func configToFlags(log *logger.Logger, config string, newFlags *Flags) error {
// configToFlagDefinition convert string configurations to flags and store them to pointer newFlags
func configToFlagDefinition(log *logger.Logger, config string, definition *Definition) error {
compiledSchema := loadAndCompileSchema(log)

flagStringLoader := gojsonschema.NewStringLoader(config)
Expand All @@ -467,33 +466,16 @@ func configToFlags(log *logger.Logger, config string, newFlags *Flags) error {
return fmt.Errorf("transposing evaluators: %w", err)
}

var configData ConfigWithMetadata
err = json.Unmarshal([]byte(transposedConfig), &configData)
err = json.Unmarshal([]byte(transposedConfig), &definition)
if err != nil {
return fmt.Errorf("unmarshalling provided configurations: %w", err)
}

// Assign the flags from the unmarshalled config to the newFlags struct
newFlags.Flags = configData.Flags

// Assign metadata as a map to each flag's metadata
for key, flag := range newFlags.Flags {
if flag.Metadata == nil {
flag.Metadata = make(map[string]interface{})
}
for metaKey, metaValue := range configData.Metadata {
if _, exists := flag.Metadata[metaKey]; !exists {
flag.Metadata[metaKey] = metaValue
}
}
newFlags.Flags[key] = flag
}

return validateDefaultVariants(newFlags)
return validateDefaultVariants(definition)
}

// validateDefaultVariants returns an error if any of the default variants aren't valid
func validateDefaultVariants(flags *Flags) error {
func validateDefaultVariants(flags *Definition) error {
for name, flag := range flags.Flags {
if _, ok := flag.Variants[flag.DefaultVariant]; !ok {
return fmt.Errorf(
Expand Down
2 changes: 1 addition & 1 deletion core/pkg/evaluator/json_model.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type Evaluators struct {
Evaluators map[string]json.RawMessage `json:"$evaluators"`
}

type ConfigWithMetadata struct {
type Definition struct {
Flags map[string]model.Flag `json:"flags"`
Metadata map[string]interface{} `json:"metadata"`
}
Expand Down
Loading

0 comments on commit 738c41d

Please sign in to comment.