22import re
33import typing
44from dataclasses import dataclass
5+ from pathlib import Path
6+
7+ from jsonschema import Draft7Validator , ValidationError
8+ from referencing import Registry , Resource
59
610from openfeature .event import ProviderEventDetails
711from openfeature .exception import ParseError
812
13+ project_root = Path (__file__ ).resolve ().parents [7 ]
14+ SCHEMAS = project_root / "openfeature/schemas/json"
15+
16+
17+ def retrieve_from_filesystem (uri : str ) -> Resource :
18+ path = SCHEMAS / Path (uri .removeprefix ("https://flagd.dev/schema/v0/" ))
19+ contents = json .loads (path .read_text ())
20+ return Resource .from_contents (contents )
921
10- def _validate_metadata (key : str , value : typing .Union [float , int , str , bool ]) -> None :
11- if key is None :
12- raise ParseError ("Metadata key must be set" )
13- elif not isinstance (key , str ):
14- raise ParseError (f"Metadata key { key } must be of type str, but is { type (key )} " )
15- elif not key :
16- raise ParseError ("key must not be empty" )
17- if value is None :
18- raise ParseError (f"Metadata value for key { key } must be set" )
19- elif not isinstance (value , (float , int , str , bool )):
20- raise ParseError (
21- f"Metadata value { value } for key { key } must be of type float, int, str or bool, but is { type (value )} "
22- )
22+
23+ registry = Registry (retrieve = retrieve_from_filesystem ) # type: ignore[call-arg]
24+
25+ validator = Draft7Validator (
26+ registry = registry ,
27+ schema = {"$ref" : "https://flagd.dev/schema/v0/flags.json" },
28+ )
2329
2430
2531class FlagStore :
@@ -39,6 +45,11 @@ def get_flag(self, key: str) -> typing.Optional["Flag"]:
3945 return self .flags .get (key )
4046
4147 def update (self , flags_data : dict ) -> None :
48+ try :
49+ validator .validate (flags_data )
50+ except ValidationError as e :
51+ raise ParseError (e .message ) from e
52+
4253 flags = flags_data .get ("flags" , {})
4354 metadata = flags_data .get ("metadata" , {})
4455 evaluators : typing .Optional [dict ] = flags_data .get ("$evaluators" )
@@ -54,8 +65,6 @@ def update(self, flags_data: dict) -> None:
5465 raise ParseError ("`flags` key of configuration must be a dictionary" )
5566 if not isinstance (metadata , dict ):
5667 raise ParseError ("`metadata` key of configuration must be a dictionary" )
57- for key , value in metadata .items ():
58- _validate_metadata (key , value )
5968
6069 self .flags = {key : Flag .from_dict (key , data ) for key , data in flags .items ()}
6170 self .flag_set_metadata = metadata
@@ -79,29 +88,9 @@ class Flag:
7988 ] = None
8089
8190 def __post_init__ (self ) -> None :
82- if not self .state or not isinstance (self .state , str ):
83- raise ParseError ("Incorrect 'state' value provided in flag config" )
84-
85- if not self .variants or not isinstance (self .variants , dict ):
86- raise ParseError ("Incorrect 'variants' value provided in flag config" )
87-
88- if not self .default_variant or not isinstance (
89- self .default_variant , (str , bool )
90- ):
91- raise ParseError ("Incorrect 'defaultVariant' value provided in flag config" )
92-
93- if self .targeting and not isinstance (self .targeting , dict ):
94- raise ParseError ("Incorrect 'targeting' value provided in flag config" )
95-
96- if self .default_variant not in self .variants :
91+ if self .default_variant and self .default_variant not in self .variants :
9792 raise ParseError ("Default variant does not match set of variants" )
9893
99- if self .metadata :
100- if not isinstance (self .metadata , dict ):
101- raise ParseError ("Flag metadata is not a valid json object" )
102- for key , value in self .metadata .items ():
103- _validate_metadata (key , value )
104-
10594 @classmethod
10695 def from_dict (cls , key : str , data : dict ) -> "Flag" :
10796 if "defaultVariant" in data :
0 commit comments