11import json
22import re
33import typing
4+
5+ from jsonschema import ValidationError
6+ from pathlib import Path
47from dataclasses import dataclass
58
69from openfeature .event import ProviderEventDetails
710from openfeature .exception import ParseError
11+ from referencing import Registry , Resource
12+ from jsonschema import Draft7Validator
813
14+ project_root = Path (__file__ ).resolve ().parents [7 ]
15+ SCHEMAS = project_root / "openfeature/schemas/json"
16+
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 )
922
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- )
23+
24+ registry = Registry (retriever = 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