11import json
22import re
33import typing
4+
45from dataclasses import dataclass
6+ from pathlib import Path
7+
8+ from jsonschema import Draft7Validator , ValidationError
9+ from referencing import Registry , Resource
510
611from openfeature .event import ProviderEventDetails
712from openfeature .exception import ParseError
813
14+ project_root = Path (__file__ ).resolve ().parents [7 ]
15+ SCHEMAS = project_root / "openfeature/schemas/json"
916
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- )
17+
18+ def retrieve_from_filesystem ( uri : str ) -> Resource :
19+ path = SCHEMAS / Path ( uri . removeprefix ( "https://flagd.dev/schema/v0/" ) )
20+ contents = json . loads ( path . read_text ())
21+ return Resource . from_contents ( contents )
22+
23+
24+ registry = Registry ( retrieve = retrieve_from_filesystem )
25+
26+ validator = Draft7Validator (
27+ registry = registry ,
28+ schema = { "$ref" : "https://flagd.dev/schema/v0/flags.json" },
29+ )
2330
2431
2532class FlagStore :
@@ -39,6 +46,11 @@ def get_flag(self, key: str) -> typing.Optional["Flag"]:
3946 return self .flags .get (key )
4047
4148 def update (self , flags_data : dict ) -> None :
49+ try :
50+ validator .validate (flags_data )
51+ except ValidationError as e :
52+ raise ParseError (e .message ) from e
53+
4254 flags = flags_data .get ("flags" , {})
4355 metadata = flags_data .get ("metadata" , {})
4456 evaluators : typing .Optional [dict ] = flags_data .get ("$evaluators" )
@@ -54,8 +66,6 @@ def update(self, flags_data: dict) -> None:
5466 raise ParseError ("`flags` key of configuration must be a dictionary" )
5567 if not isinstance (metadata , dict ):
5668 raise ParseError ("`metadata` key of configuration must be a dictionary" )
57- for key , value in metadata .items ():
58- _validate_metadata (key , value )
5969
6070 self .flags = {key : Flag .from_dict (key , data ) for key , data in flags .items ()}
6171 self .flag_set_metadata = metadata
@@ -72,36 +82,16 @@ class Flag:
7282 key : str
7383 state : str
7484 variants : typing .Mapping [str , typing .Any ]
75- default_variant : typing .Union [bool , str ]
85+ default_variant : typing .Optional [ typing . Union [bool , str ]] = None
7686 targeting : typing .Optional [dict ] = None
7787 metadata : typing .Optional [
7888 typing .Mapping [str , typing .Union [float , int , str , bool ]]
7989 ] = None
8090
8191 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 :
92+ if self .default_variant and self .default_variant not in self .variants :
9793 raise ParseError ("Default variant does not match set of variants" )
9894
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-
10595 @classmethod
10696 def from_dict (cls , key : str , data : dict ) -> "Flag" :
10797 if "defaultVariant" in data :
0 commit comments