Skip to content

Conversation

aepfli
Copy link
Member

@aepfli aepfli commented Sep 1, 2025

this is my naive attempt of adding flagmap configuration support. i am most likely missing a few crucial parts here, but i think this is overall doing what it should. Before i continue i want to gather some feedback and insights

Copy link

netlify bot commented Sep 1, 2025

Deploy Preview for polite-licorice-3db33c canceled.

Name Link
🔨 Latest commit 19697cb
🔍 Latest deploy log https://app.netlify.com/projects/polite-licorice-3db33c/deploys/68b9b291e5cbad0008a27d9c

gemini-code-assist[bot]

This comment was marked as outdated.

gemini-code-assist[bot]

This comment was marked as outdated.

gemini-code-assist[bot]

This comment was marked as outdated.

gemini-code-assist[bot]

This comment was marked as outdated.

gemini-code-assist[bot]

This comment was marked as outdated.

Comment on lines -288 to -305
raw, err := txn.First(flagsTable, keySourceCompoundIndex, key, source)
if err != nil {
s.logger.Error(fmt.Sprintf("unable to get flag %s from source %s: %v", key, source, err))
continue
}
oldFlag, ok := raw.(model.Flag)
// If we already have a flag with the same key and source, we need to check if it has the same flagSetId
if ok {
if oldFlag.FlagSetId != newFlag.FlagSetId {
// If the flagSetId is different, we need to delete the entry, since flagSetId+key represents the primary index, and it's now been changed.
// This is important especially for clients listening to flagSetId changes, as they expect the flag to be removed from the set in this case.
_, err = txn.DeleteAll(flagsTable, idIndex, oldFlag.FlagSetId, key)
if err != nil {
s.logger.Error(fmt.Sprintf("unable to delete flags with key %s and flagSetId %s: %v", key, oldFlag.FlagSetId, err))
continue
}
}
}
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

i am not sure, that we actually need this - we can have multiple sources with the same key, but also different flagsetids - so if a flag was deleted in one source, does not mean we need to delete it all - or am i missing here something

gemini-code-assist[bot]

This comment was marked as outdated.

gemini-code-assist[bot]

This comment was marked as outdated.

gemini-code-assist[bot]

This comment was marked as outdated.

gemini-code-assist[bot]

This comment was marked as outdated.

gemini-code-assist[bot]

This comment was marked as outdated.

gemini-code-assist[bot]

This comment was marked as outdated.

Copy link

Summary of Changes

This pull request significantly refactors the flag configuration handling by introducing support for an array-based flag definition format. This change required comprehensive updates to the JSON schema validation process, a redesign of the internal flag storage to correctly manage flags with potentially non-unique keys across different flag sets, and a cleaner interface for flag evaluation. The aim is to provide a more flexible and robust system for defining and managing feature flags.

Highlights

  • Flagmap Configuration Support: The pull request introduces support for a new flag configuration format, allowing flags to be defined as an array of objects instead of a map. This enhances flexibility in how flag configurations are structured.
  • Updated JSON Schema Validation: A new, more powerful JSON schema compiler (santhosh-tekuri/jsonschema/v6) has been integrated to provide more robust validation of incoming flag configurations.
  • Refactored Flag Storage and Retrieval: The internal flag storage and retrieval mechanisms have been updated to handle flags as slices instead of maps. This includes changes to how flags are indexed and retrieved, specifically addressing uniqueness using a combination of flagSetId and flagKey.
  • Simplified IEvaluator Interface: The SetState method in the IEvaluator interface has been simplified to return only an error, streamlining its contract.
Changelog
  • core/go.mod
    • Added new dependencies: github.com/google/uuid, github.com/hashicorp/go-memdb, github.com/mitchellh/mapstructure, github.com/santhosh-tekuri/jsonschema/v6.
    • Updated github.com/open-feature/flagd-schemas version.
    • Removed github.com/zeebo/xxh3.
  • core/go.sum
    • Updated checksums for new and updated dependencies, and removed entries for removed dependencies.
  • core/pkg/evaluator/fractional_test.go
    • Updated test cases to use slices for flag definitions instead of maps.
    • Updated NewJSON calls to handle error returns.
  • core/pkg/evaluator/ievaluator.go
    • Removed GetState() method from IEvaluator interface.
    • Changed SetState method signature in IEvaluator interface to return only an error.
  • core/pkg/evaluator/json.go
    • Imported flagd_definitions and jsonschema/v6.
    • Added addSchemaResource helper function for schema management.
    • NewJSON now returns (*JSON, error) and initializes jsonSchema using the new compiler.
    • SetState method no longer returns map[string]interface{}, bool and now calls configToFlagDefinition.
    • ResolveAllValues now iterates over slices of model.Flag.
    • configToFlagDefinition now uses the new jsonschema/v6 for validation and handles both map-based and array-based flag definitions, ensuring flag Key is set for array-based flags.
    • validateDefaultVariants now uses flag.Key for error messages.
  • core/pkg/evaluator/json_model.go
    • Changed Flags and Definition structs to use []model.Flag instead of map[string]model.Flag.
  • core/pkg/evaluator/json_test.go
    • Changed package from evaluator_test to evaluator.
    • Removed strings import.
    • Added ValidMapFlags constant for array-based flag definitions.
    • Renamed Flags to FlagsConfig.
    • Updated NewJSON calls to handle error returns.
    • Updated SetState calls to reflect the new signature.
    • Renamed and refactored tests related to GetState and SetState to reflect new behavior.
    • Updated TestState_Evaluator to handle []model.Flag and perform more robust comparisons.
    • Updated TestFlagStateSafeForConcurrentReadWrites to use the new NewJSON signature.
  • core/pkg/evaluator/semver_test.go
    • Updated test cases to use slices for flag definitions instead of maps.
    • Updated NewJSON calls to handle error returns.
  • core/pkg/evaluator/string_comparison_test.go
    • Updated test cases to use slices for flag definitions instead of maps.
    • Updated NewJSON calls to handle error returns.
  • core/pkg/model/flag.go
    • Changed Key field in Flag struct to json:"key" (removing json:"-" and updating comment).
  • core/pkg/notifications/notifications.go
    • Added a comment clarifying the purpose of notifications within flagd.
  • core/pkg/store/store.go
    • Removed encoding/json and notifications imports.
    • FlagQueryResult now uses []model.Flag.
    • IStore interface updated: GetAll returns []model.Flag, Update no longer returns map[string]interface{}, bool, and String() method removed.
    • GetAll now returns []model.Flag.
    • Update method now processes []model.Flag directly, handles flag deletion based on compound key, and no longer returns notifications.
    • collect method now returns []model.Flag and handles duplicate flags based on FlagSetId and Key.
  • core/pkg/store/store_test.go
    • Added sort import.
    • Updated TestUpdateFlags to use []model.Flag for newFlags and wantFlags, removed wantNotifs and wantResync, and added sorting for comparison.
    • Updated TestGet and TestGetAllNoWatcher to use []model.Flag for flag definitions and wantFlags, and added sorting for comparison.
    • Updated TestWatch to use []model.Flag for flag definitions.
  • flagd/pkg/runtime/from_config.go
    • Updated evaluator.NewJSON call to handle error return.
  • schemas
    • Updated subproject commit to 3a4396c00de561b012ee0334138de46a48a24c8a.
  • spec
    • Updated subproject commit to 969e11c4d5df4ab16b400965ef1b3e313dcb923e.
Activity
  • The author aepfli has repeatedly requested /gemini review.
  • A Netlify deploy preview was canceled.
  • gemini-code-assist[bot] encountered an error creating a review.
  • toddbaert provided feedback on the new jsonschema compiler.
  • gemini-code-assist[bot] provided critical feedback on missing schema validation.
  • gemini-code-assist[bot] identified incorrect error messages for unmarshalling schemas.
  • gemini-code-assist[bot] suggested improving log messages for deleted flags.
  • gemini-code-assist[bot] pointed out a syntax error due to incorrect indentation.
  • gemini-code-assist[bot] suggested extracting schema loading logic into a helper function.
  • gemini-code-assist[bot] noted a misleading log message for duplicate flags.
  • gemini-code-assist[bot] suggested using json.Marshal/Unmarshal for flag conversion instead of manual mapping.
  • gemini-code-assist[bot] identified dead code in the store's collect method.
  • gemini-code-assist[bot] found a bug in handling array-based flag format where model.Flag.Key was not populated.
  • gemini-code-assist[bot] suggested removing an unused Flag struct.
  • gemini-code-assist[bot] suggested a more precise comment for txn.Insert behavior.
  • toddbaert and aepfli discussed the necessity of a compound key for flags.
  • gemini-code-assist[bot] highlighted critical issues with schema compilation in init() and suggested moving it to NewJSON.
  • gemini-code-assist[bot] identified an issue with jsonschema.UnmarshalJSON and compiler.AddResource.
  • gemini-code-assist[bot] noted that a test now passes due to a bug in store.Update and should be reverted.
  • aepfli acknowledged being wrong about a previous point.
  • gemini-code-assist[bot] reiterated critical feedback on log.Fatalf and error handling in addSchemaResource and NewJSON.
  • gemini-code-assist[bot] identified a critical bug where flags with duplicate keys but different flagSetIds would be overwritten.
  • gemini-code-assist[bot] suggested using mapstructure for more efficient flag conversion.
  • gemini-code-assist[bot] found a bug in convertToModelFlag where Key was not correctly populated due to field shadowing.
  • gemini-code-assist[bot] found a copy-paste error in a log statement.
  • gemini-code-assist[bot] suggested explicitly failing benchmarks if NewJSON returns an error.
  • gemini-code-assist[bot] recommended investigating the root cause of test failures with compiler.AddResource to remove deprecated calls.
  • gemini-code-assist[bot] provided critical feedback on data loss due to map conversion in Update method.
  • gemini-code-assist[bot] clarified a misleading comment about jsonschema.UnmarshalJSON.
  • gemini-code-assist[bot] identified a critical mismatch between IEvaluator interface and its implementation.
  • gemini-code-assist[bot] highlighted a high-priority issue: no validation for empty flag keys in array format.
  • gemini-code-assist[bot] noted a high-priority issue with String() method leading to data loss and non-deterministic output.
  • aepfli questioned the need for the String() method and later removed it.
  • gemini-code-assist[bot] suggested improving the efficiency of convertToModelFlag.
  • gemini-code-assist[bot] recommended sorting both slices in tests to prevent flakiness.
  • gemini-code-assist[bot] noted the removal of feature_flag.change_count metric and suggested re-introducing it.
  • gemini-code-assist[bot] suggested improving the error message for missing keys.
  • gemini-code-assist[bot] suggested renaming misleading test names.
  • gemini-code-assist[bot] highlighted a high-priority breaking change in SetState signature and its impact on ISync implementations.
  • aepfli responded that no usages were found and tests passed.
  • gemini-code-assist[bot] suggested removing an unused helper function.
  • gemini-code-assist[bot] recommended defining expected flags as struct literals in tests for clarity.
  • gemini-code-assist[bot] identified an invalid struct tag for the Key field in model.Flag.

Copy link

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces support for a new array-based flag configuration format (flagmap), which is a significant and welcome feature. The implementation correctly handles both the old map-based format and the new array-based one.

My review focuses on a few key areas for improvement:

  • Efficiency: The flag definition parsing can be made more efficient by avoiding multiple JSON unmarshalling operations.
  • Error Handling: Schema validation for flag configurations should probably return an error instead of just a warning to prevent running with invalid configurations.
  • Regressions: The changes to the IEvaluator.SetState interface have removed the event notification mechanism, which affects telemetry for flag changes. This seems like a regression that should be addressed.
  • Code Quality: There are a few minor issues, like an invalid struct tag and a test that could be more robust.

Overall, this is a good start, and with a few refinements, it will be a great addition.

Comment on lines 36 to 39
type IEvaluator interface {
GetState() (string, error)
SetState(payload sync.DataSync) (map[string]interface{}, bool, error)
SetState(payload sync.DataSync) error
IResolver
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The signature of SetState has been changed, and it no longer returns events about flag changes. This has removed the mechanism for notifying about flag creations, updates, and deletions.

This functionality was used for telemetry to track the number of flag changes. While the PR description mentions this is a naive attempt, this seems like a significant regression that should be addressed. The callers of SetState in the various sync providers will also need to be updated to reflect this new signature.

@open-feature open-feature deleted a comment from gemini-code-assist bot Sep 3, 2025
aepfli and others added 8 commits September 3, 2025 17:10
Signed-off-by: Simon Schrottner <[email protected]>

diff --git c/core/go.mod i/core/go.mod
index aff41b9..ebb2609 100644
--- c/core/go.mod
+++ i/core/go.mod
@@ -14,10 +14,11 @@ require (
 	github.com/google/go-cmp v0.7.0
 	github.com/google/uuid v1.6.0
 	github.com/hashicorp/go-memdb v1.3.5
-	github.com/open-feature/flagd-schemas v0.2.9-0.20250707123415-08b4c52d3b86
+	github.com/open-feature/flagd-schemas v0.2.9-0.20250829095801-c0108fa92a83
 	github.com/open-feature/open-feature-operator/apis v0.2.45
 	github.com/prometheus/client_golang v1.22.0
 	github.com/robfig/cron v1.2.0
+	github.com/santhosh-tekuri/jsonschema/v6 v6.0.2
 	github.com/stretchr/testify v1.10.0
 	github.com/twmb/murmur3 v1.1.8
 	github.com/xeipuuv/gojsonschema v1.2.0
@@ -113,6 +114,8 @@ require (
 	github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
 	github.com/hashicorp/go-uuid v1.0.2 // indirect
 	github.com/hashicorp/golang-lru v0.5.4 // indirect
+	github.com/hashicorp/go-immutable-radix v1.3.1 // indirect
+	github.com/hashicorp/golang-lru v0.5.4 // indirect
 	github.com/jmespath/go-jmespath v0.4.0 // indirect
 	github.com/josharian/intern v1.0.0 // indirect
 	github.com/json-iterator/go v1.1.12 // indirect
diff --git c/core/go.sum i/core/go.sum
index 6cb5c30..3fdfe73 100644
--- c/core/go.sum
+++ i/core/go.sum
@@ -119,6 +119,8 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r
 github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
 github.com/diegoholiveira/jsonlogic/v3 v3.8.4 h1:IVVU/VLz2hn10ImbmibjiUkdVsSFIB1vfDaOVsaipH4=
 github.com/diegoholiveira/jsonlogic/v3 v3.8.4/go.mod h1:OYRb6FSTVmMM+MNQ7ElmMsczyNSepw+OU4Z8emDSi4w=
+github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI=
+github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8=
 github.com/emicklei/go-restful/v3 v3.12.0 h1:y2DdzBAURM29NFF94q6RaY4vjIH1rtwDapwQtU84iWk=
 github.com/emicklei/go-restful/v3 v3.12.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc=
 github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4=
@@ -217,9 +219,14 @@ github.com/googleapis/gax-go/v2 v2.14.2/go.mod h1:ON64QhlJkhVtSqp4v1uaK92VyZ2gmv
 github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww=
 github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90=
 github.com/hashicorp/go-immutable-radix v1.3.1 h1:DKHmCUm2hRBK510BaiZlwvpD40f8bJFeZnpfm2KLowc=
+github.com/hashicorp/go-immutable-radix v1.3.1/go.mod h1:0y9vanUI8NX6FsYoO3zeMjhV/C5i9g4Q3DwcSNZ4P60=
 github.com/hashicorp/go-memdb v1.3.5 h1:b3taDMxCBCBVgyRrS1AZVHO14ubMYZB++QpNhBg+Nyo=
-github.com/hashicorp/go-uuid v1.0.2 h1:cfejS+Tpcp13yd5nYHWDI6qVCny6wyX2Mt5SGur2IGE=
+github.com/hashicorp/go-memdb v1.3.5/go.mod h1:8IVKKBkVe+fxFgdFOYxzQQNjz+sWCyHCdIC/+5+Vy1Y=
+github.com/hashicorp/go-uuid v1.0.0 h1:RS8zrF7PhGwyNPOtxSClXXj9HA8feRnJzgnI1RJCSnM=
+github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
+github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
 github.com/hashicorp/golang-lru v0.5.4 h1:YDjusn29QI/Das2iO9M0BHnIbxPeyuCHsjMW+lJfyTc=
+github.com/hashicorp/golang-lru v0.5.4/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4=
 github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg=
 github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo=
 github.com/jmespath/go-jmespath/internal/testify v1.5.1 h1:shLQSRRSCCPj3f2gpwzGwWFoC7ycTf1rcQZHOlsJ6N8=
@@ -253,8 +260,10 @@ github.com/onsi/ginkgo/v2 v2.21.0 h1:7rg/4f3rB88pb5obDgNZrNHrQ4e6WpjonchcpuBRnZM
 github.com/onsi/ginkgo/v2 v2.21.0/go.mod h1:7Du3c42kxCUegi0IImZ1wUQzMBVecgIHjR1C+NkhLQo=
 github.com/onsi/gomega v1.35.1 h1:Cwbd75ZBPxFSuZ6T+rN/WCb/gOc6YgFBXLlZLhC7Ds4=
 github.com/onsi/gomega v1.35.1/go.mod h1:PvZbdDc8J6XJEpDK4HCuRBm8a6Fzp9/DmhC9C7yFlog=
-github.com/open-feature/flagd-schemas v0.2.9-0.20250707123415-08b4c52d3b86 h1:r3e+qs3QUdf4+lUi2ZZnSHgYkjeLIb5yu5jo+ypA8iw=
-github.com/open-feature/flagd-schemas v0.2.9-0.20250707123415-08b4c52d3b86/go.mod h1:WKtwo1eW9/K6D+4HfgTXWBqCDzpvMhDa5eRxW7R5B2U=
+github.com/open-feature/flagd-schemas v0.2.9-0.20250827201840-3a4396c00de5 h1:TbyJKxuj174kiOMDqoR1sBcd+DeSnSrOmtT4PNvOGrA=
+github.com/open-feature/flagd-schemas v0.2.9-0.20250827201840-3a4396c00de5/go.mod h1:C0jnJ4C3j2LzGuqKgLDdTsdfKEWQp6sOHZyxu3QohFU=
+github.com/open-feature/flagd-schemas v0.2.9-0.20250829095801-c0108fa92a83 h1:CxYMSNaDtkudraJUuJHUnGGa+v4ZaaxxjJsmhMKM3QQ=
+github.com/open-feature/flagd-schemas v0.2.9-0.20250829095801-c0108fa92a83/go.mod h1:C0jnJ4C3j2LzGuqKgLDdTsdfKEWQp6sOHZyxu3QohFU=
 github.com/open-feature/open-feature-operator/apis v0.2.45 h1:URnUf22ZoAx7/W8ek8dXCBYgY8FmnFEuEOSDLROQafY=
 github.com/open-feature/open-feature-operator/apis v0.2.45/go.mod h1:PYh/Hfyna1lZYZUeu/8LM0qh0ZgpH7kKEXRLYaaRhGs=
 github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c h1:+mdjkGKdHQG3305AYmdv1U2eRNDiU2ErMBj1gwrq8eQ=
@@ -281,6 +290,8 @@ github.com/robfig/cron v1.2.0 h1:ZjScXvvxeQ63Dbyxy76Fj3AT3Ut0aKsyd2/tl3DTMuQ=
 github.com/robfig/cron v1.2.0/go.mod h1:JGuDeoQd7Z6yL4zQhZ3OPEVHB7fL6Ka6skscFHfmt2k=
 github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII=
 github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o=
+github.com/santhosh-tekuri/jsonschema/v6 v6.0.2 h1:KRzFb2m7YtdldCEkzs6KqmJw4nqEVZGK7IN2kJkjTuQ=
+github.com/santhosh-tekuri/jsonschema/v6 v6.0.2/go.mod h1:JXeL+ps8p7/KNMjDQk3TCwPpBy0wYklyWTfbkIzdIFU=
 github.com/spf13/pflag v1.0.6 h1:jFzHGLGAlb3ruxLB8MhbI6A8+AQX/2eW4qeyNZXNp2o=
 github.com/spiffe/go-spiffe/v2 v2.5.0 h1:N2I01KCUkv1FAjZXJMwh95KK1ZIQLYbPfhaxw8WS0hE=
 github.com/spiffe/go-spiffe/v2 v2.5.0/go.mod h1:P+NxobPc6wXhVtINNtFjNWGBTreew1GBUCwT2wPmb7g=
diff --git c/core/pkg/evaluator/json.go i/core/pkg/evaluator/json.go
index 8a0b4fb..2c9b34a 100644
--- c/core/pkg/evaluator/json.go
+++ i/core/pkg/evaluator/json.go
@@ -6,17 +6,19 @@ import (
 	"encoding/json"
 	"errors"
 	"fmt"
+	flagd_definitions "github.com/open-feature/flagd-schemas/json"
+	"log"
 	"regexp"
 	"strconv"
 	"strings"
 	"time"

 	"github.com/diegoholiveira/jsonlogic/v3"
-	schema "github.com/open-feature/flagd-schemas/json"
 	"github.com/open-feature/flagd/core/pkg/logger"
 	"github.com/open-feature/flagd/core/pkg/model"
 	"github.com/open-feature/flagd/core/pkg/store"
 	"github.com/open-feature/flagd/core/pkg/sync"
+	"github.com/santhosh-tekuri/jsonschema/v6"
 	"github.com/xeipuuv/gojsonschema"
 	"go.opentelemetry.io/otel"
 	"go.opentelemetry.io/otel/attribute"
@@ -35,10 +37,49 @@ const (
 	Disabled        = "DISABLED"
 )

+var compiledSchema *jsonschema.Schema
 var regBrace *regexp.Regexp

 func init() {
 	regBrace = regexp.MustCompile("^[^{]*{|}[^}]*$")
+
+	// Create a new JSON Schema compiler
+	compiler := jsonschema.NewCompiler()
+
+	// Add the Flagd Daemon schema
+	flagdFile := strings.NewReader(flagd_definitions.FlagdSchema)
+	schema, err := jsonschema.UnmarshalJSON(flagdFile)
+	if err != nil {
+		log.Fatalf("Failed to unmarshal targeting schema: %v", err)
+	}
+	if err := compiler.AddResource("https://flagd.dev/schema/v0/flagd.json", schema); err != nil {
+		log.Fatalf("Failed to add flagd schema: %v", err)
+	}
+
+	// Add the Flag schema
+	flagsFile := strings.NewReader(flagd_definitions.FlagSchema)
+	schema, err = jsonschema.UnmarshalJSON(flagsFile)
+	if err != nil {
+		log.Fatalf("Failed to unmarshal targeting schema: %v", err)
+	}
+	if err := compiler.AddResource("https://flagd.dev/schema/v0/flags.json", schema); err != nil {
+		log.Fatalf("Failed to add flags schema: %v", err)
+	}
+
+	// Add the Targeting schema
+	targetingFile := strings.NewReader(flagd_definitions.TargetingSchema)
+	schema, err = jsonschema.UnmarshalJSON(targetingFile)
+	if err != nil {
+		log.Fatalf("Failed to unmarshal targeting schema: %v", err)
+	}
+	if err := compiler.AddResource("https://flagd.dev/schema/v0/targeting.json", schema); err != nil {
+		log.Fatalf("Failed to add targeting schema: %v", err)
+	}
+	compiledSchema, err = compiler.Compile("https://flagd.dev/schema/v0/flagd.json")
+	if err != nil {
+		log.Fatalf("Failed to compile flagd schema: %v", err)
+	}
+
 }

 type constraints interface {
@@ -429,53 +470,114 @@ func getFlagdProperties(context map[string]any) (flagdProperties, bool) {
 	return p, true
 }

-func loadAndCompileSchema(log *logger.Logger) *gojsonschema.Schema {
-	schemaLoader := gojsonschema.NewSchemaLoader()
+type JsonDef struct {
+	Flags    interface{}            `json:"flags"`
+	Metadata map[string]interface{} `json:"metadata"`
+}

-	// compile dependency schema
-	targetingSchemaLoader := gojsonschema.NewStringLoader(schema.TargetingSchema)
-	if err := schemaLoader.AddSchemas(targetingSchemaLoader); err != nil {
-		log.Warn(fmt.Sprintf("error adding Targeting schema: %s", err))
-	}
-
-	// compile root schema
-	flagdDefinitionsLoader := gojsonschema.NewStringLoader(schema.FlagSchema)
-	compiledSchema, err := schemaLoader.Compile(flagdDefinitionsLoader)
-	if err != nil {
-		log.Warn(fmt.Sprintf("error compiling FlagdDefinitions schema: %s", err))
-	}
-
-	return compiledSchema
+type Flag struct {
+	model.Flag
+	Key       string `json:"key,omitempty"`
+	FlagSetId string `json:"flagSetId,omitempty"`
 }

 // 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)
-
-	result, err := compiledSchema.Validate(flagStringLoader)
-	if err != nil {
-		log.Logger.Warn(fmt.Sprintf("failed to execute JSON schema validation: %s", err))
-	} else if !result.Valid() {
-		log.Logger.Warn(fmt.Sprintf(
-			"flag definition does not conform to the schema; validation errors: %s", buildErrorString(result.Errors()),
-		))
-	}
-
+	// Transpose evaluators and unmarshal directly into JsonDef
 	transposedConfig, err := transposeEvaluators(config)
 	if err != nil {
 		return fmt.Errorf("transposing evaluators: %w", err)
 	}

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

+	definition.Metadata = intermediateConfig.Metadata
+	definition.Flags = map[string]model.Flag{}
+
+	// Process flags directly
+	switch v := intermediateConfig.Flags.(type) {
+	case map[string]interface{}: // Handle ValidFlags format
+		for k, e := range v {
+			flag, err := convertToModelFlag(e)
+			if err != nil {
+				return fmt.Errorf("failed to process flag for key %s: %w", k, err)
+			}
+			flag.Key = k // Populate the `Key` field explicitly
+			definition.Flags[k] = flag
+		}
+
+	case []interface{}: // Handle ValidMapFlags format
+		for _, value := range v {
+			flag, err := convertToModelFlag(value)
+			if err != nil {
+				return fmt.Errorf("failed to process flag: %w", err)
+			}
+			definition.Flags[flag.Key] = flag
+		}
+
+	default:
+		return fmt.Errorf("unexpected type for flags property: %T", v)
+	}
+
 	return validateDefaultVariants(definition)
 }

+// Helper function to convert a generic interface{} to model.Flag
+func convertToModelFlag(data interface{}) (model.Flag, error) {
+	flag := model.Flag{}
+
+	// Assert the data is a map[string]interface{}
+	flagData, ok := data.(map[string]interface{})
+	if !ok {
+		return flag, fmt.Errorf("unexpected type for flag data: %T", data)
+	}
+
+	// Populate fields of model.Flag
+	if key, ok := flagData["key"].(string); ok {
+		flag.Key = key
+	}
+
+	if state, ok := flagData["state"].(string); ok {
+		flag.State = state
+	}
+
+	if defaultVariant, ok := flagData["defaultVariant"].(string); ok {
+		flag.DefaultVariant = defaultVariant
+	}
+
+	if variants, ok := flagData["variants"].(map[string]interface{}); ok {
+		flag.Variants = variants
+	}
+
+	if targeting, ok := flagData["targeting"].(json.RawMessage); ok {
+		flag.Targeting = targeting
+	} else if targetingRaw, ok := flagData["targeting"].(string); ok {
+		flag.Targeting = json.RawMessage(targetingRaw)
+	} else if flagData["targeting"] != nil {
+		marshal, err := json.Marshal(flagData["targeting"])
+		if err != nil {
+			return flag, fmt.Errorf("marshalling targeting: %w", err)
+		}
+
+		flag.Targeting = json.RawMessage(marshal)
+	}
+
+	if source, ok := flagData["source"].(string); ok {
+		flag.Source = source
+	}
+
+	if metadata, ok := flagData["metadata"].(map[string]interface{}); ok {
+		// Assuming Metadata is a struct that can be populated directly
+		flag.Metadata = model.Metadata(metadata)
+	}
+
+	return flag, nil
+}
+
 // validateDefaultVariants returns an error if any of the default variants aren't valid
 func validateDefaultVariants(flags *Definition) error {
 	for name, flag := range flags.Flags {
diff --git c/core/pkg/evaluator/json_test.go i/core/pkg/evaluator/json_test.go
index c5c0eaf..76a5619 100644
--- c/core/pkg/evaluator/json_test.go
+++ i/core/pkg/evaluator/json_test.go
@@ -43,6 +43,23 @@ const ValidFlags = `{
     }
   }
 }`
+const ValidMapFlags = `{
+  "flags": [
+    {
+      "key": "validFlag",
+      "state": "ENABLED",
+      "variants": {
+        "on": true,
+        "off": false
+      },
+      "defaultVariant": "on",
+      "metadata": {
+        "flagSetId": "test",
+        "version": 3
+      }
+    }
+  ]
+}`

 const NullDefault = `{
   "flags": {
@@ -400,6 +417,25 @@ func TestGetState_Valid_ContainsFlag(t *testing.T) {
 		t.Fatalf("expected: %s to contain: %s", state, wants)
 	}
 }
+func TestGetState_ValidMap_ContainsFlag(t *testing.T) {
+	evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
+	_, _, err := evaluator.SetState(sync.DataSync{FlagData: ValidMapFlags, Source: "testSource"})
+	if err != nil {
+		t.Fatalf("Expected no error")
+	}
+
+	// get the state
+	state, err := evaluator.GetState()
+	if err != nil {
+		t.Fatalf("expected no error")
+	}
+
+	// validate it contains the flag
+	wants := "validFlag"
+	if !strings.Contains(state, wants) {
+		t.Fatalf("expected: %s to contain: %s", state, wants)
+	}
+}

 func TestSetState_Invalid_Error(t *testing.T) {
 	evaluator := evaluator.NewJSON(logger.NewLogger(nil, false), store.NewFlags())
diff --git c/schemas i/schemas
index 08b4c52..3a4396c 160000
--- c/schemas
+++ i/schemas
@@ -1 +1 @@
-Subproject commit 08b4c52d3b86d686f11e74322dbfee775db91656
+Subproject commit 3a4396c00de561b012ee0334138de46a48a24c8a
Signed-off-by: Simon Schrottner <[email protected]>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
…ich is causing more issues

Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
Signed-off-by: Simon Schrottner <[email protected]>
@aepfli
Copy link
Member Author

aepfli commented Sep 4, 2025

extracted build improvements (#1792 ) and interface cleanup (#1793 ) and json schema lib change (#1794) and test cleanup (#1795) for reduction of complexity in this pr

@aepfli
Copy link
Member Author

aepfli commented Sep 9, 2025

closed in favor of #1797

@aepfli aepfli closed this Sep 9, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants