feat: support flag metadata (#1476)

- Adds functionality to return flag metadata as part of the response.

Signed-off-by: Aasif Khan <aasifk106@gmail.com>
This commit is contained in:
aasifkhan7 2025-01-16 01:19:03 +05:30 committed by GitHub
parent 26b0b1af8b
commit 13fbbad4d8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 255 additions and 8 deletions

View File

@ -327,6 +327,13 @@ func (je *Resolver) evaluateVariant(ctx context.Context, reqID string, flagKey s
metadata[SelectorMetadataKey] = selector
}
for key, value := range flag.Metadata {
// If value is not nil or empty, copy to metadata
if value != nil {
metadata[key] = value
}
}
if flag.State == Disabled {
je.Logger.DebugWithID(reqID, fmt.Sprintf("requested flag is disabled: %s", flagKey))
return "", flag.Variants, model.ErrorReason, metadata, errors.New(model.FlagDisabledErrorCode)
@ -460,11 +467,28 @@ func configToFlags(log *logger.Logger, config string, newFlags *Flags) error {
return fmt.Errorf("transposing evaluators: %w", err)
}
err = json.Unmarshal([]byte(transposedConfig), &newFlags)
var configData ConfigWithMetadata
err = json.Unmarshal([]byte(transposedConfig), &configData)
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)
}

View File

@ -10,6 +10,11 @@ type Evaluators struct {
Evaluators map[string]json.RawMessage `json:"$evaluators"`
}
type ConfigWithMetadata struct {
Flags map[string]model.Flag `json:"flags"`
Metadata map[string]interface{} `json:"metadata"`
}
type Flags struct {
Flags map[string]model.Flag `json:"flags"`
}

View File

@ -1184,6 +1184,222 @@ func TestState_Evaluator(t *testing.T) {
expectedError: true,
expectedResync: false,
},
"flag metadata": {
inputState: `
{
"flags": {
"fibAlgo": {
"variants": {
"recursive": "recursive",
"memo": "memo",
"loop": "loop",
"binet": "binet"
},
"metadata": {
"id": 1
},
"defaultVariant": "recursive",
"state": "ENABLED",
"targeting": {
"if": [
{
"$ref": "emailWithFaas"
}, "binet", null
]
}
}
},
"$evaluators": {
"emailWithFaas": {
"in": ["@faas.com", {
"var": ["email"]
}]
}
}
}
`,
inputSyncType: sync.ALL,
expectedOutputState: `
{
"flags": {
"fibAlgo": {
"variants": {
"recursive": "recursive",
"memo": "memo",
"loop": "loop",
"binet": "binet"
},
"metadata": {
"id": 1
},
"defaultVariant": "recursive",
"state": "ENABLED",
"source":"",
"selector":"",
"targeting": {
"if": [
{
"in": ["@faas.com", {
"var": ["email"]
}]
}, "binet", null
]
}
}
},
"flagSources":null
}
`,
},
"flagSet and flag metadata": {
inputState: `
{
"flags": {
"fibAlgo": {
"variants": {
"recursive": "recursive",
"memo": "memo",
"loop": "loop",
"binet": "binet"
},
"metadata": {
"id": "sso/dev",
"version": "1.0.0"
},
"defaultVariant": "recursive",
"state": "ENABLED",
"targeting": {
"if": [
{
"$ref": "emailWithFaas"
}, "binet", null
]
}
}
},
"metadata": {
"flagSetId": "test",
"flagSetVersion": "1"
},
"$evaluators": {
"emailWithFaas": {
"in": ["@faas.com", {
"var": ["email"]
}]
}
}
}
`,
inputSyncType: sync.ALL,
expectedOutputState: `
{
"flags": {
"fibAlgo": {
"variants": {
"recursive": "recursive",
"memo": "memo",
"loop": "loop",
"binet": "binet"
},
"metadata": {
"id": "sso/dev",
"version": "1.0.0",
"flagSetId": "test",
"flagSetVersion": "1"
},
"defaultVariant": "recursive",
"state": "ENABLED",
"source":"",
"selector":"",
"targeting": {
"if": [
{
"in": ["@faas.com", {
"var": ["email"]
}]
}, "binet", null
]
}
}
},
"flagSources":null
}
`,
},
"flag metadata priority": {
inputState: `
{
"flags": {
"fibAlgo": {
"variants": {
"recursive": "recursive",
"memo": "memo",
"loop": "loop",
"binet": "binet"
},
"metadata": {
"id": "sso/dev",
"version": "1.0.0"
},
"defaultVariant": "recursive",
"state": "ENABLED",
"targeting": {
"if": [
{
"$ref": "emailWithFaas"
}, "binet", null
]
}
}
},
"metadata": {
"id": "test",
"flagSetVersion": "1"
},
"$evaluators": {
"emailWithFaas": {
"in": ["@faas.com", {
"var": ["email"]
}]
}
}
}
`,
inputSyncType: sync.ALL,
expectedOutputState: `
{
"flags": {
"fibAlgo": {
"variants": {
"recursive": "recursive",
"memo": "memo",
"loop": "loop",
"binet": "binet"
},
"metadata": {
"id": "sso/dev",
"version": "1.0.0",
"flagSetVersion": "1"
},
"defaultVariant": "recursive",
"state": "ENABLED",
"source":"",
"selector":"",
"targeting": {
"if": [
{
"in": ["@faas.com", {
"var": ["email"]
}]
}, "binet", null
]
}
}
},
"flagSources":null
}
`,
},
}
for name, tt := range tests {

View File

@ -9,6 +9,7 @@ type Flag struct {
Targeting json.RawMessage `json:"targeting,omitempty"`
Source string `json:"source"`
Selector string `json:"selector"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
type Evaluators struct {

View File

@ -21,7 +21,8 @@ type Flags struct {
mx sync.RWMutex
Flags map[string]model.Flag `json:"flags"`
FlagSources []string
SourceMetadata map[string]SourceDetails
SourceMetadata map[string]SourceDetails `json:"sourceMetadata,omitempty"`
Metadata map[string]interface{} `json:"metadata,omitempty"`
}
type SourceDetails struct {