11import json
22import re
33import typing
4+ import warnings
5+
6+ import jsonschema
7+ from pathlib import Path
48from dataclasses import dataclass
59
610from openfeature .event import ProviderEventDetails
711from openfeature .exception import ParseError
12+ from referencing import Registry , Resource
13+ from jsonschema import Draft7Validator
814
15+ project_root = Path (__file__ ).resolve ().parents [7 ]
16+ SCHEMAS = project_root / "openfeature/schemas/json"
17+
18+
19+ def retrieve_from_filesystem (uri : str ):
20+ path = SCHEMAS / Path (uri .removeprefix ("https://flagd.dev/schema/v0/" ))
21+ contents = json .loads (path .read_text ())
22+ return Resource .from_contents (contents )
923
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- )
24+
25+ registry = Registry (retrieve = retrieve_from_filesystem )
26+
27+ validator = Draft7Validator (
28+ registry = registry ,
29+ schema = {"$ref" : "https://flagd.dev/schema/v0/flags.json" },
30+ )
2331
2432
2533class FlagStore :
@@ -39,6 +47,12 @@ def get_flag(self, key: str) -> typing.Optional["Flag"]:
3947 return self .flags .get (key )
4048
4149 def update (self , flags_data : dict ) -> None :
50+ try :
51+ validation = validator .validate (flags_data )
52+ except jsonschema .exceptions .ValidationError as e :
53+ warnings .warn (e .message )
54+ raise ParseError (e .message )
55+
4256 flags = flags_data .get ("flags" , {})
4357 metadata = flags_data .get ("metadata" , {})
4458 evaluators : typing .Optional [dict ] = flags_data .get ("$evaluators" )
@@ -54,8 +68,6 @@ def update(self, flags_data: dict) -> None:
5468 raise ParseError ("`flags` key of configuration must be a dictionary" )
5569 if not isinstance (metadata , dict ):
5670 raise ParseError ("`metadata` key of configuration must be a dictionary" )
57- for key , value in metadata .items ():
58- _validate_metadata (key , value )
5971
6072 self .flags = {key : Flag .from_dict (key , data ) for key , data in flags .items ()}
6173 self .flag_set_metadata = metadata
@@ -72,36 +84,16 @@ class Flag:
7284 key : str
7385 state : str
7486 variants : typing .Mapping [str , typing .Any ]
75- default_variant : typing .Union [bool , str ]
87+ default_variant : typing .Optional [ typing . Union [bool , str ]] = None
7688 targeting : typing .Optional [dict ] = None
7789 metadata : typing .Optional [
7890 typing .Mapping [str , typing .Union [float , int , str , bool ]]
7991 ] = None
8092
8193 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 :
94+ if self .default_variant and self .default_variant not in self .variants :
9795 raise ParseError ("Default variant does not match set of variants" )
9896
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-
10597 @classmethod
10698 def from_dict (cls , key : str , data : dict ) -> "Flag" :
10799 if "defaultVariant" in data :
0 commit comments