2
2
import re
3
3
import typing
4
4
from dataclasses import dataclass
5
+ from pathlib import Path
6
+
7
+ from jsonschema import Draft7Validator , ValidationError
8
+ from referencing import Registry , Resource
5
9
6
10
from openfeature .event import ProviderEventDetails
7
11
from openfeature .exception import ParseError
8
12
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 )
9
21
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
+ )
23
29
24
30
25
31
class FlagStore :
@@ -39,6 +45,11 @@ def get_flag(self, key: str) -> typing.Optional["Flag"]:
39
45
return self .flags .get (key )
40
46
41
47
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
+
42
53
flags = flags_data .get ("flags" , {})
43
54
metadata = flags_data .get ("metadata" , {})
44
55
evaluators : typing .Optional [dict ] = flags_data .get ("$evaluators" )
@@ -54,8 +65,6 @@ def update(self, flags_data: dict) -> None:
54
65
raise ParseError ("`flags` key of configuration must be a dictionary" )
55
66
if not isinstance (metadata , dict ):
56
67
raise ParseError ("`metadata` key of configuration must be a dictionary" )
57
- for key , value in metadata .items ():
58
- _validate_metadata (key , value )
59
68
60
69
self .flags = {key : Flag .from_dict (key , data ) for key , data in flags .items ()}
61
70
self .flag_set_metadata = metadata
@@ -79,29 +88,9 @@ class Flag:
79
88
] = None
80
89
81
90
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 :
97
92
raise ParseError ("Default variant does not match set of variants" )
98
93
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
-
105
94
@classmethod
106
95
def from_dict (cls , key : str , data : dict ) -> "Flag" :
107
96
if "defaultVariant" in data :
0 commit comments