3
3
/// Unlike json schema we let you put definitions inline, not just in a single '#/$defs/' block or similar.
4
4
/// We use DefinitionsBuilder to collect the references / definitions into a single vector
5
5
/// and then get a definition from a reference using an integer id (just for performance of not using a HashMap)
6
- use std:: collections:: hash_map:: Entry ;
6
+ use std:: {
7
+ collections:: hash_map:: Entry ,
8
+ fmt:: Debug ,
9
+ sync:: { Arc , OnceLock } ,
10
+ } ;
7
11
8
- use pyo3:: prelude:: * ;
12
+ use pyo3:: { prelude:: * , PyTraverseError , PyVisit } ;
9
13
10
14
use ahash:: AHashMap ;
11
15
12
- use crate :: build_tools:: py_schema_err;
13
-
14
- // An integer id for the reference
15
- pub type ReferenceId = usize ;
16
+ use crate :: { build_tools:: py_schema_err, py_gc:: PyGcTraverse } ;
16
17
17
18
/// Definitions are validators and serializers that are
18
19
/// shared by reference.
@@ -24,91 +25,154 @@ pub type ReferenceId = usize;
24
25
/// They get indexed by a ReferenceId, which are integer identifiers
25
26
/// that are handed out and managed by DefinitionsBuilder when the Schema{Validator,Serializer}
26
27
/// gets build.
27
- pub type Definitions < T > = [ T ] ;
28
+ #[ derive( Clone ) ]
29
+ pub struct Definitions < T > ( AHashMap < Arc < String > , Definition < T > > ) ;
28
30
29
- #[ derive( Clone , Debug ) ]
30
- struct Definition < T > {
31
- pub id : ReferenceId ,
32
- pub value : Option < T > ,
31
+ impl < T > Definitions < T > {
32
+ pub fn values ( & self ) -> impl Iterator < Item = & Definition < T > > {
33
+ self . 0 . values ( )
34
+ }
35
+ }
36
+
37
+ /// Internal type which contains a definition to be filled
38
+ pub struct Definition < T > ( Arc < OnceLock < T > > ) ;
39
+
40
+ impl < T > Definition < T > {
41
+ pub fn get ( & self ) -> Option < & T > {
42
+ self . 0 . get ( )
43
+ }
44
+ }
45
+
46
+ /// Reference to a definition.
47
+ pub struct DefinitionRef < T > {
48
+ name : Arc < String > ,
49
+ value : Definition < T > ,
50
+ }
51
+
52
+ // DefinitionRef can always be cloned (#[derive(Clone)] would require T: Clone)
53
+ impl < T > Clone for DefinitionRef < T > {
54
+ fn clone ( & self ) -> Self {
55
+ Self {
56
+ name : self . name . clone ( ) ,
57
+ value : self . value . clone ( ) ,
58
+ }
59
+ }
60
+ }
61
+
62
+ impl < T > DefinitionRef < T > {
63
+ pub fn id ( & self ) -> usize {
64
+ Arc :: as_ptr ( & self . value . 0 ) as usize
65
+ }
66
+
67
+ pub fn name ( & self ) -> & str {
68
+ & self . name
69
+ }
70
+
71
+ pub fn get ( & self ) -> Option < & T > {
72
+ self . value . 0 . get ( )
73
+ }
74
+ }
75
+
76
+ impl < T : Debug > Debug for DefinitionRef < T > {
77
+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
78
+ // To avoid possible infinite recursion from recursive definitions,
79
+ // a DefinitionRef just displays debug as its name
80
+ self . name . fmt ( f)
81
+ }
82
+ }
83
+
84
+ impl < T : Debug > Debug for Definitions < T > {
85
+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
86
+ self . 0 . fmt ( f)
87
+ }
88
+ }
89
+
90
+ impl < T > Clone for Definition < T > {
91
+ fn clone ( & self ) -> Self {
92
+ Self ( self . 0 . clone ( ) )
93
+ }
94
+ }
95
+
96
+ impl < T : Debug > Debug for Definition < T > {
97
+ fn fmt ( & self , f : & mut std:: fmt:: Formatter < ' _ > ) -> std:: fmt:: Result {
98
+ match self . 0 . get ( ) {
99
+ Some ( value) => value. fmt ( f) ,
100
+ None => "..." . fmt ( f) ,
101
+ }
102
+ }
103
+ }
104
+
105
+ impl < T : PyGcTraverse > PyGcTraverse for DefinitionRef < T > {
106
+ fn py_gc_traverse ( & self , visit : & PyVisit < ' _ > ) -> Result < ( ) , PyTraverseError > {
107
+ if let Some ( value) = self . value . 0 . get ( ) {
108
+ value. py_gc_traverse ( visit) ?;
109
+ }
110
+ Ok ( ( ) )
111
+ }
112
+ }
113
+
114
+ impl < T : PyGcTraverse > PyGcTraverse for Definitions < T > {
115
+ fn py_gc_traverse ( & self , visit : & PyVisit < ' _ > ) -> Result < ( ) , PyTraverseError > {
116
+ for value in self . 0 . values ( ) {
117
+ if let Some ( value) = value. 0 . get ( ) {
118
+ value. py_gc_traverse ( visit) ?;
119
+ }
120
+ }
121
+ Ok ( ( ) )
122
+ }
33
123
}
34
124
35
125
#[ derive( Clone , Debug ) ]
36
126
pub struct DefinitionsBuilder < T > {
37
- definitions : AHashMap < String , Definition < T > > ,
127
+ definitions : Definitions < T > ,
38
128
}
39
129
40
- impl < T : Clone + std:: fmt:: Debug > DefinitionsBuilder < T > {
130
+ impl < T : std:: fmt:: Debug > DefinitionsBuilder < T > {
41
131
pub fn new ( ) -> Self {
42
132
Self {
43
- definitions : AHashMap :: new ( ) ,
133
+ definitions : Definitions ( AHashMap :: new ( ) ) ,
44
134
}
45
135
}
46
136
47
137
/// Get a ReferenceId for the given reference string.
48
- // This ReferenceId can later be used to retrieve a definition
49
- pub fn get_reference_id ( & mut self , reference : & str ) -> ReferenceId {
50
- let next_id = self . definitions . len ( ) ;
138
+ pub fn get_definition ( & mut self , reference : & str ) -> DefinitionRef < T > {
51
139
// We either need a String copy or two hashmap lookups
52
140
// Neither is better than the other
53
141
// We opted for the easier outward facing API
54
- match self . definitions . entry ( reference. to_string ( ) ) {
55
- Entry :: Occupied ( entry) => entry. get ( ) . id ,
56
- Entry :: Vacant ( entry) => {
57
- entry. insert ( Definition {
58
- id : next_id,
59
- value : None ,
60
- } ) ;
61
- next_id
62
- }
142
+ let name = Arc :: new ( reference. to_string ( ) ) ;
143
+ let value = match self . definitions . 0 . entry ( name. clone ( ) ) {
144
+ Entry :: Occupied ( entry) => entry. into_mut ( ) ,
145
+ Entry :: Vacant ( entry) => entry. insert ( Definition ( Arc :: new ( OnceLock :: new ( ) ) ) ) ,
146
+ } ;
147
+ DefinitionRef {
148
+ name,
149
+ value : value. clone ( ) ,
63
150
}
64
151
}
65
152
66
153
/// Add a definition, returning the ReferenceId that maps to it
67
- pub fn add_definition ( & mut self , reference : String , value : T ) -> PyResult < ReferenceId > {
68
- let next_id = self . definitions . len ( ) ;
69
- match self . definitions . entry ( reference. clone ( ) ) {
70
- Entry :: Occupied ( mut entry) => match entry. get_mut ( ) . value . replace ( value) {
71
- Some ( _) => py_schema_err ! ( "Duplicate ref: `{}`" , reference) ,
72
- None => Ok ( entry. get ( ) . id ) ,
73
- } ,
74
- Entry :: Vacant ( entry) => {
75
- entry. insert ( Definition {
76
- id : next_id,
77
- value : Some ( value) ,
78
- } ) ;
79
- Ok ( next_id)
154
+ pub fn add_definition ( & mut self , reference : String , value : T ) -> PyResult < DefinitionRef < T > > {
155
+ let name = Arc :: new ( reference) ;
156
+ let value = match self . definitions . 0 . entry ( name. clone ( ) ) {
157
+ Entry :: Occupied ( entry) => {
158
+ let definition = entry. into_mut ( ) ;
159
+ match definition. 0 . set ( value) {
160
+ Ok ( ( ) ) => definition. clone ( ) ,
161
+ Err ( _) => return py_schema_err ! ( "Duplicate ref: `{}`" , name) ,
162
+ }
80
163
}
81
- }
82
- }
83
-
84
- /// Retrieve an item definition using a ReferenceId
85
- /// If the definition doesn't yet exist (as happens in recursive types) then we create it
86
- /// At the end (in finish()) we check that there are no undefined definitions
87
- pub fn get_definition ( & self , reference_id : ReferenceId ) -> PyResult < & T > {
88
- let ( reference, def) = match self . definitions . iter ( ) . find ( |( _, def) | def. id == reference_id) {
89
- Some ( v) => v,
90
- None => return py_schema_err ! ( "Definitions error: no definition for ReferenceId `{}`" , reference_id) ,
164
+ Entry :: Vacant ( entry) => entry. insert ( Definition ( Arc :: new ( OnceLock :: from ( value) ) ) ) . clone ( ) ,
91
165
} ;
92
- match def. value . as_ref ( ) {
93
- Some ( v) => Ok ( v) ,
94
- None => py_schema_err ! (
95
- "Definitions error: attempted to use `{}` before it was filled" ,
96
- reference
97
- ) ,
98
- }
166
+ Ok ( DefinitionRef { name, value } )
99
167
}
100
168
101
169
/// Consume this Definitions into a vector of items, indexed by each items ReferenceId
102
- pub fn finish ( self ) -> PyResult < Vec < T > > {
103
- // We need to create a vec of defs according to the order in their ids
104
- let mut defs: Vec < ( usize , T ) > = Vec :: new ( ) ;
105
- for ( reference, def) in self . definitions {
106
- match def. value {
107
- None => return py_schema_err ! ( "Definitions error: definition {} was never filled" , reference) ,
108
- Some ( v) => defs. push ( ( def. id , v) ) ,
170
+ pub fn finish ( self ) -> PyResult < Definitions < T > > {
171
+ for ( reference, def) in & self . definitions . 0 {
172
+ if def. 0 . get ( ) . is_none ( ) {
173
+ return py_schema_err ! ( "Definitions error: definition `{}` was never filled" , reference) ;
109
174
}
110
175
}
111
- defs. sort_by_key ( |( id, _) | * id) ;
112
- Ok ( defs. into_iter ( ) . map ( |( _, v) | v) . collect ( ) )
176
+ Ok ( self . definitions )
113
177
}
114
178
}
0 commit comments