1- use std:: { rc:: Rc , sync:: Arc } ;
1+ use std:: {
2+ rc:: Rc ,
3+ sync:: { Arc , Mutex } ,
4+ } ;
25
36use crate :: {
47 compiler,
@@ -10,8 +13,9 @@ use crate::{
1013 validator:: { PartialApplication , Validate } ,
1114 ValidationError , ValidationOptions ,
1215} ;
16+ use ahash:: AHashSet ;
1317use once_cell:: sync:: OnceCell ;
14- use referencing:: { Draft , List , Registry , Resource , Uri , VocabularySet } ;
18+ use referencing:: { Draft , List , Registry , Uri , VocabularySet } ;
1519use serde_json:: { Map , Value } ;
1620
1721pub ( crate ) enum RefValidator {
@@ -24,31 +28,34 @@ impl RefValidator {
2428 pub ( crate ) fn compile < ' a > (
2529 ctx : & compiler:: Context ,
2630 reference : & str ,
27- is_recursive : bool ,
31+ maybe_recursive : bool ,
2832 keyword : & str ,
2933 ) -> Option < CompilationResult < ' a > > {
3034 let location = ctx. location ( ) . join ( keyword) ;
3135 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 ) {
3438 Ok ( resolved) => resolved,
3539 Err ( error) => return Some ( Err ( error) ) ,
3640 }
3741 } {
3842 // 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 ( ) {
4044 if let Some ( Some ( resolved) ) = contents. get ( keyword) . map ( Value :: as_str) {
4145 if resolved == reference {
4246 return None ;
4347 }
4448 }
4549 }
4650 Ok ( Box :: new ( RefValidator :: Lazy ( LazyRefValidator {
47- resource,
51+ reference : Reference :: Default {
52+ reference : reference. to_string ( ) ,
53+ } ,
4854 config : Arc :: clone ( ctx. config ( ) ) ,
4955 registry : Arc :: clone ( & ctx. registry ) ,
5056 base_uri,
5157 scopes,
58+ seen : ctx. seen ( ) ,
5259 location,
5360 vocabularies : ctx. vocabularies ( ) . clone ( ) ,
5461 draft : ctx. draft ( ) ,
@@ -79,6 +86,11 @@ impl RefValidator {
7986 }
8087}
8188
89+ enum Reference {
90+ Default { reference : String } ,
91+ Recursive ,
92+ }
93+
8294/// Lazily evaluated validator used for recursive references.
8395///
8496/// The validator tree nodes can't be arbitrary looked up in the current
@@ -87,11 +99,12 @@ impl RefValidator {
8799/// representation for the validation tree may allow building cycles easier and
88100/// lazy evaluation won't be needed.
89101pub ( crate ) struct LazyRefValidator {
90- resource : Resource ,
102+ reference : Reference ,
91103 config : Arc < ValidationOptions > ,
92104 registry : Arc < Registry > ,
93105 scopes : List < Uri < String > > ,
94106 base_uri : Arc < Uri < String > > ,
107+ seen : Arc < Mutex < AHashSet < Arc < Uri < String > > > > > ,
95108 vocabularies : VocabularySet ,
96109 location : Location ,
97110 draft : Draft ,
@@ -101,20 +114,15 @@ pub(crate) struct LazyRefValidator {
101114impl LazyRefValidator {
102115 #[ inline]
103116 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 ( ) ?;
112119 Ok ( Box :: new ( LazyRefValidator {
113- resource ,
120+ reference : Reference :: Recursive ,
114121 config : Arc :: clone ( ctx. config ( ) ) ,
115122 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 ( ) ,
118126 vocabularies : ctx. vocabularies ( ) . clone ( ) ,
119127 location : ctx. location ( ) . join ( "$recursiveRef" ) ,
120128 draft : ctx. draft ( ) ,
@@ -123,21 +131,58 @@ impl LazyRefValidator {
123131 }
124132 fn lazy_compile ( & self ) -> & SchemaNode {
125133 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- ) ;
138134 // INVARIANT: This schema was already used during compilation before detecting a
139135 // 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+ }
141186 } )
142187 }
143188}
0 commit comments