diff --git a/CHANGELOG.md b/CHANGELOG.md index e9653b3d..6af2d369 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ While backward compatibility is maintained for now, users are encouraged to upda - `$anchor` support. - `$recursiveRef` & `$recursiveAnchor` support in Draft 2019-09. +- `$dynamicRef` & `$dynamicAnchor` support in Draft 2020-12. ### Changed diff --git a/crates/jsonschema-py/CHANGELOG.md b/crates/jsonschema-py/CHANGELOG.md index 789000b9..051059c4 100644 --- a/crates/jsonschema-py/CHANGELOG.md +++ b/crates/jsonschema-py/CHANGELOG.md @@ -6,6 +6,7 @@ - `$anchor` support. - `$recursiveRef` & `$recursiveAnchor` support in Draft 2019-09. +- `$dynamicRef` & `$dynamicAnchor` support in Draft 2020-12. ### Changed diff --git a/crates/jsonschema-referencing/src/anchors/mod.rs b/crates/jsonschema-referencing/src/anchors/mod.rs index fee645ee..37958d3c 100644 --- a/crates/jsonschema-referencing/src/anchors/mod.rs +++ b/crates/jsonschema-referencing/src/anchors/mod.rs @@ -33,7 +33,11 @@ impl Anchor { /// Get the resource for this anchor. pub(crate) fn resolve<'r>(&'r self, resolver: Resolver<'r>) -> Result, Error> { match self { - Anchor::Default { resource, .. } => Ok(Resolved::new(resource.contents(), resolver)), + Anchor::Default { resource, .. } => Ok(Resolved::new( + resource.contents(), + resolver, + resource.draft(), + )), Anchor::Dynamic { name, resource, .. } => { let mut last = resource; for uri in resolver.dynamic_scope() { @@ -50,6 +54,7 @@ impl Anchor { Ok(Resolved::new( last.contents(), resolver.in_subresource((**last).as_ref())?, + last.draft(), )) } } @@ -189,7 +194,7 @@ mod tests { .lookup("#fooAnchor") .expect("Lookup failed"); assert_eq!(fourth.contents(), root.contents()); - assert_eq!(format!("{:?}", fourth.resolver()), "Resolver { base_uri: \"http://example.com\", parent: \"[http://example.com/foo/, http://example.com, http://example.com]\" }"); + assert_eq!(format!("{:?}", fourth.resolver()), "Resolver { base_uri: \"http://example.com\", scopes: \"[http://example.com/foo/, http://example.com, http://example.com]\" }"); } #[test] diff --git a/crates/jsonschema-referencing/src/registry.rs b/crates/jsonschema-referencing/src/registry.rs index ffb1bc69..86796c82 100644 --- a/crates/jsonschema-referencing/src/registry.rs +++ b/crates/jsonschema-referencing/src/registry.rs @@ -786,7 +786,7 @@ mod tests { .expect("Invalid base URI"); assert_eq!( format!("{resolver:?}"), - "Resolver { base_uri: \"http://127.0.0.1/schema\", parent: \"[]\" }" + "Resolver { base_uri: \"http://127.0.0.1/schema\", scopes: \"[]\" }" ); } diff --git a/crates/jsonschema-referencing/src/resolver.rs b/crates/jsonschema-referencing/src/resolver.rs index 4a05c4b0..f6cc3e0f 100644 --- a/crates/jsonschema-referencing/src/resolver.rs +++ b/crates/jsonschema-referencing/src/resolver.rs @@ -4,7 +4,7 @@ use std::collections::VecDeque; use fluent_uri::UriRef; use serde_json::Value; -use crate::{uri, Error, Registry, ResourceRef}; +use crate::{uri, Draft, Error, Registry, ResourceRef}; /// A reference resolver. /// @@ -27,7 +27,7 @@ impl<'r> fmt::Debug for Resolver<'r> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Resolver") .field("base_uri", &self.base_uri.as_str()) - .field("parent", &{ + .field("scopes", &{ let mut buf = String::from("["); let mut values = self.scopes.iter(); if let Some(value) = values.next() { @@ -56,12 +56,12 @@ impl<'r> Resolver<'r> { pub(crate) fn from_parts( registry: &'r Registry, base_uri: UriRef, - parent: VecDeque>, + scopes: VecDeque>, ) -> Self { Self { registry, base_uri, - scopes: parent, + scopes, } } #[must_use] @@ -104,7 +104,11 @@ impl<'r> Resolver<'r> { } let resolver = self.evolve(uri); - Ok(Resolved::new(retrieved.contents(), resolver)) + Ok(Resolved::new( + retrieved.contents(), + resolver, + retrieved.draft(), + )) } /// Resolve a recursive reference. /// @@ -198,11 +202,16 @@ pub struct Resolved<'r> { contents: &'r Value, /// The resolver that resolved this reference, which can be used for further resolutions. resolver: Resolver<'r>, + draft: Draft, } impl<'r> Resolved<'r> { - pub(crate) fn new(contents: &'r Value, resolver: Resolver<'r>) -> Self { - Self { contents, resolver } + pub(crate) fn new(contents: &'r Value, resolver: Resolver<'r>, draft: Draft) -> Self { + Self { + contents, + resolver, + draft, + } } /// Resolved contents. #[must_use] @@ -216,7 +225,11 @@ impl<'r> Resolved<'r> { } #[must_use] - pub fn into_inner(self) -> (&'r Value, Resolver<'r>) { - (self.contents, self.resolver) + pub fn draft(&self) -> Draft { + self.draft + } + #[must_use] + pub fn into_inner(self) -> (&'r Value, Resolver<'r>, Draft) { + (self.contents, self.resolver, self.draft) } } diff --git a/crates/jsonschema-referencing/src/resource.rs b/crates/jsonschema-referencing/src/resource.rs index 29066c23..93d664b1 100644 --- a/crates/jsonschema-referencing/src/resource.rs +++ b/crates/jsonschema-referencing/src/resource.rs @@ -99,7 +99,7 @@ impl Resource { } resolver = new_resolver; } - Ok(Resolved::new(contents, resolver)) + Ok(Resolved::new(contents, resolver, self.draft())) } /// Give a reference to the underlying contents together with draft. #[must_use] diff --git a/crates/jsonschema/src/compiler.rs b/crates/jsonschema/src/compiler.rs index b73e1790..09faeef6 100644 --- a/crates/jsonschema/src/compiler.rs +++ b/crates/jsonschema/src/compiler.rs @@ -202,7 +202,7 @@ impl<'a> Context<'a> { &self, reference: &str, is_recursive: bool, - ) -> Result, ValidationError<'static>> { + ) -> Result, Resource)>, ValidationError<'static>> { let resolved = if reference == "#" { // Known & simple recursive reference // It may also use some additional logic from the `$recursiveAnchor` keyword @@ -218,15 +218,16 @@ impl<'a> Context<'a> { return Ok(None); }; let resource = self.draft().create_resource(resolved.contents().clone()); - let base_uri = if let Some(id) = resource.id() { - uri::from_str(id)? - } else { - resolved.resolver().base_uri().to_owned() + let mut base_uri = resolved.resolver().base_uri().to_owned(); + let scopes = resolved + .resolver() + .dynamic_scope() + .cloned() + .collect::>(); + if let Some(id) = resource.id() { + base_uri = uri::resolve_against(&base_uri.borrow(), id)?; }; - if !is_recursive { - self.mark_seen(reference)?; - } - Ok(Some((base_uri, resource))) + Ok(Some((base_uri, scopes, resource))) } } diff --git a/crates/jsonschema/src/keywords/mod.rs b/crates/jsonschema/src/keywords/mod.rs index 9f4b7f64..9b078741 100644 --- a/crates/jsonschema/src/keywords/mod.rs +++ b/crates/jsonschema/src/keywords/mod.rs @@ -133,7 +133,10 @@ pub(crate) fn get_for_draft(draft: Draft, keyword: &str) -> Option Some(unevaluated_properties::compile) } - // Default case + // Draft 2020-12 specific + (Draft::Draft202012, "$dynamicRef") => Some(ref_::compile), + + // Unknown or not-yet-implemented keyword _ => None, } } diff --git a/crates/jsonschema/src/keywords/ref_.rs b/crates/jsonschema/src/keywords/ref_.rs index ed26a7f6..4f02b7f4 100644 --- a/crates/jsonschema/src/keywords/ref_.rs +++ b/crates/jsonschema/src/keywords/ref_.rs @@ -26,8 +26,9 @@ impl RefValidator { reference: &str, is_recursive: bool, ) -> CompilationResult<'a> { - let scopes = ctx.scopes(); - if let Some((base_uri, resource)) = ctx.lookup_maybe_recursive(reference, is_recursive)? { + if let Some((base_uri, scopes, resource)) = + ctx.lookup_maybe_recursive(reference, is_recursive)? + { Ok(Box::new(RefValidator::Lazy(LazyRefValidator { resource, config: Arc::clone(ctx.config()), @@ -38,8 +39,8 @@ impl RefValidator { inner: OnceCell::default(), }))) } else { - let (contents, resolver) = ctx.lookup(reference)?.into_inner(); - let resource_ref = ctx.as_resource_ref(contents); + let (contents, resolver, draft) = ctx.lookup(reference)?.into_inner(); + let resource_ref = draft.create_resource_ref(contents); let ctx = ctx.with_resolver_and_draft(resolver, resource_ref.draft()); let inner = compiler::compile_with(&ctx, resource_ref).map_err(|err| err.into_owned())?; diff --git a/crates/jsonschema/src/keywords/unevaluated_properties.rs b/crates/jsonschema/src/keywords/unevaluated_properties.rs index d17dacf4..a294409a 100644 --- a/crates/jsonschema/src/keywords/unevaluated_properties.rs +++ b/crates/jsonschema/src/keywords/unevaluated_properties.rs @@ -1235,7 +1235,7 @@ impl ReferenceSubvalidator { .get("$recursiveAnchor") .and_then(Value::as_bool) .unwrap_or_default(); - if let Some((_, resource)) = ctx.lookup_maybe_recursive(reference, is_recursive)? { + if let Some((_, _, resource)) = ctx.lookup_maybe_recursive(reference, is_recursive)? { Self::from_value_impl(ctx, parent, resource.contents()) } else { let resolved = ctx.lookup(reference)?; diff --git a/crates/jsonschema/tests/suite.rs b/crates/jsonschema/tests/suite.rs index 252b7779..209882d1 100644 --- a/crates/jsonschema/tests/suite.rs +++ b/crates/jsonschema/tests/suite.rs @@ -29,10 +29,7 @@ use testsuite::{suite, Test}; "draft2019-09::unevaluated_items", "draft2019-09::unevaluated_properties::unevaluated_properties_with_recursive_ref", "draft2019-09::vocabulary::schema_that_uses_custom_metaschema_with_with_no_validation_vocabulary", - "draft2020-12::defs::validate_definition_against_metaschema::invalid_definition_schema", - "draft2020-12::dynamic_ref", "draft2020-12::optional::cross_draft::refs_to_historic_drafts_are_processed_as_historic_drafts", - "draft2020-12::optional::dynamic_ref::dynamic_ref_skips_over_intermediate_resources_pointer_reference_across_resource_boundary", "draft2020-12::optional::ecmascript_regex::d_in_pattern_properties_matches_0_9_not_unicode_digits", "draft2020-12::optional::ecmascript_regex::w_in_pattern_properties_matches_a_za_z0_9_not_unicode_letters", "draft2020-12::optional::format::duration::validation_of_duration_strings",