1
- use std:: { rc:: Rc , sync:: Arc } ;
1
+ use std:: {
2
+ rc:: Rc ,
3
+ sync:: { Arc , Mutex } ,
4
+ } ;
2
5
3
6
use crate :: {
4
7
compiler,
@@ -10,8 +13,9 @@ use crate::{
10
13
validator:: { PartialApplication , Validate } ,
11
14
ValidationError , ValidationOptions ,
12
15
} ;
16
+ use ahash:: AHashSet ;
13
17
use once_cell:: sync:: OnceCell ;
14
- use referencing:: { Draft , List , Registry , Resource , Uri , VocabularySet } ;
18
+ use referencing:: { Draft , List , Registry , Uri , VocabularySet } ;
15
19
use serde_json:: { Map , Value } ;
16
20
17
21
pub ( crate ) enum RefValidator {
@@ -24,31 +28,34 @@ impl RefValidator {
24
28
pub ( crate ) fn compile < ' a > (
25
29
ctx : & compiler:: Context ,
26
30
reference : & str ,
27
- is_recursive : bool ,
31
+ maybe_recursive : bool ,
28
32
keyword : & str ,
29
33
) -> Option < CompilationResult < ' a > > {
30
34
let location = ctx. location ( ) . join ( keyword) ;
31
35
Some (
32
- if let Some ( ( base_uri, scopes, resource ) ) = {
33
- match ctx. lookup_maybe_recursive ( reference, is_recursive ) {
36
+ if let Some ( ( base_uri, scopes, resolved ) ) = {
37
+ match ctx. lookup_maybe_recursive ( reference, maybe_recursive ) {
34
38
Ok ( resolved) => resolved,
35
39
Err ( error) => return Some ( Err ( error) ) ,
36
40
}
37
41
} {
38
42
// NOTE: A better approach would be to compare the absolute locations
39
- if let Value :: Object ( contents) = resource . contents ( ) {
43
+ if let Value :: Object ( contents) = resolved . contents ( ) {
40
44
if let Some ( Some ( resolved) ) = contents. get ( keyword) . map ( Value :: as_str) {
41
45
if resolved == reference {
42
46
return None ;
43
47
}
44
48
}
45
49
}
46
50
Ok ( Box :: new ( RefValidator :: Lazy ( LazyRefValidator {
47
- resource,
51
+ reference : Reference :: Default {
52
+ reference : reference. to_string ( ) ,
53
+ } ,
48
54
config : Arc :: clone ( ctx. config ( ) ) ,
49
55
registry : Arc :: clone ( & ctx. registry ) ,
50
56
base_uri,
51
57
scopes,
58
+ seen : ctx. seen ( ) ,
52
59
location,
53
60
vocabularies : ctx. vocabularies ( ) . clone ( ) ,
54
61
draft : ctx. draft ( ) ,
@@ -79,6 +86,11 @@ impl RefValidator {
79
86
}
80
87
}
81
88
89
+ enum Reference {
90
+ Default { reference : String } ,
91
+ Recursive ,
92
+ }
93
+
82
94
/// Lazily evaluated validator used for recursive references.
83
95
///
84
96
/// The validator tree nodes can't be arbitrary looked up in the current
@@ -87,11 +99,12 @@ impl RefValidator {
87
99
/// representation for the validation tree may allow building cycles easier and
88
100
/// lazy evaluation won't be needed.
89
101
pub ( crate ) struct LazyRefValidator {
90
- resource : Resource ,
102
+ reference : Reference ,
91
103
config : Arc < ValidationOptions > ,
92
104
registry : Arc < Registry > ,
93
105
scopes : List < Uri < String > > ,
94
106
base_uri : Arc < Uri < String > > ,
107
+ seen : Arc < Mutex < AHashSet < Arc < Uri < String > > > > > ,
95
108
vocabularies : VocabularySet ,
96
109
location : Location ,
97
110
draft : Draft ,
@@ -101,20 +114,15 @@ pub(crate) struct LazyRefValidator {
101
114
impl LazyRefValidator {
102
115
#[ inline]
103
116
pub ( crate ) fn compile < ' a > ( ctx : & compiler:: Context ) -> CompilationResult < ' a > {
104
- let scopes = ctx. scopes ( ) ;
105
- let resolved = ctx. lookup_recursive_reference ( ) ?;
106
- let resource = ctx. draft ( ) . create_resource ( resolved. contents ( ) . clone ( ) ) ;
107
- let resolver = resolved. resolver ( ) ;
108
- let mut base_uri = resolver. base_uri ( ) ;
109
- if let Some ( id) = resource. id ( ) {
110
- base_uri = resolver. resolve_against ( & base_uri. borrow ( ) , id) ?;
111
- } ;
117
+ // Verify that the reference is resolvable
118
+ ctx. lookup_recursive_reference ( ) ?;
112
119
Ok ( Box :: new ( LazyRefValidator {
113
- resource ,
120
+ reference : Reference :: Recursive ,
114
121
config : Arc :: clone ( ctx. config ( ) ) ,
115
122
registry : Arc :: clone ( & ctx. registry ) ,
116
- base_uri,
117
- scopes,
123
+ base_uri : ctx. full_base_uri ( ) ,
124
+ scopes : ctx. scopes ( ) ,
125
+ seen : ctx. seen ( ) ,
118
126
vocabularies : ctx. vocabularies ( ) . clone ( ) ,
119
127
location : ctx. location ( ) . join ( "$recursiveRef" ) ,
120
128
draft : ctx. draft ( ) ,
@@ -123,21 +131,58 @@ impl LazyRefValidator {
123
131
}
124
132
fn lazy_compile ( & self ) -> & SchemaNode {
125
133
self . inner . get_or_init ( || {
126
- let resolver = self
127
- . registry
128
- . resolver_from_raw_parts ( self . base_uri . clone ( ) , self . scopes . clone ( ) ) ;
129
-
130
- let ctx = compiler:: Context :: new (
131
- Arc :: clone ( & self . config ) ,
132
- Arc :: clone ( & self . registry ) ,
133
- Rc :: new ( resolver) ,
134
- self . vocabularies . clone ( ) ,
135
- self . draft ,
136
- self . location . clone ( ) ,
137
- ) ;
138
134
// INVARIANT: This schema was already used during compilation before detecting a
139
135
// reference cycle that lead to building this validator.
140
- compiler:: compile ( & ctx, self . resource . as_ref ( ) ) . expect ( "Invalid schema" )
136
+ match & self . reference {
137
+ Reference :: Default { reference } => {
138
+ let resolver = self
139
+ . registry
140
+ . resolver_from_raw_parts ( self . base_uri . clone ( ) , self . scopes . clone ( ) ) ;
141
+ let resolved = resolver. lookup ( reference) . unwrap ( ) ;
142
+ let resource = self . draft . create_resource_ref ( resolved. contents ( ) ) ;
143
+ let mut base_uri = resolved. resolver ( ) . base_uri ( ) ;
144
+ let scopes = resolved. resolver ( ) . dynamic_scope ( ) ;
145
+ if let Some ( id) = resource. id ( ) {
146
+ base_uri = self
147
+ . registry
148
+ . resolve_against ( & base_uri. borrow ( ) , id)
149
+ . unwrap ( ) ;
150
+ } ;
151
+
152
+ let resolver = self . registry . resolver_from_raw_parts ( base_uri, scopes) ;
153
+
154
+ let ctx = compiler:: Context :: new (
155
+ Arc :: clone ( & self . config ) ,
156
+ Arc :: clone ( & self . registry ) ,
157
+ Rc :: new ( resolver) ,
158
+ self . vocabularies . clone ( ) ,
159
+ self . draft ,
160
+ self . location . clone ( ) ,
161
+ self . seen . clone ( ) ,
162
+ ) ;
163
+
164
+ compiler:: compile ( & ctx, resource) . expect ( "Invalid schema" )
165
+ }
166
+ Reference :: Recursive => {
167
+ let resolver = self
168
+ . registry
169
+ . resolver_from_raw_parts ( self . base_uri . clone ( ) , self . scopes . clone ( ) ) ;
170
+ let resolved = resolver
171
+ . lookup_recursive_ref ( )
172
+ . expect ( "Failed to resolve a recursive reference" ) ;
173
+ let ctx = compiler:: Context :: new (
174
+ Arc :: clone ( & self . config ) ,
175
+ Arc :: clone ( & self . registry ) ,
176
+ Rc :: new ( resolver) ,
177
+ self . vocabularies . clone ( ) ,
178
+ self . draft ,
179
+ self . location . clone ( ) ,
180
+ self . seen . clone ( ) ,
181
+ ) ;
182
+ let resource = ctx. draft ( ) . create_resource_ref ( resolved. contents ( ) ) ;
183
+ compiler:: compile ( & ctx, resource) . expect ( "Invalid schema" )
184
+ }
185
+ }
141
186
} )
142
187
}
143
188
}
0 commit comments