Skip to content

Commit

Permalink
feat: dynamicRef and dynamicAnchor
Browse files Browse the repository at this point in the history
Signed-off-by: Dmitry Dygalo <dmitry@dygalo.dev>
  • Loading branch information
Stranger6667 committed Sep 29, 2024
1 parent efe45b9 commit dfa74db
Show file tree
Hide file tree
Showing 11 changed files with 53 additions and 31 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions crates/jsonschema-py/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

- `$anchor` support.
- `$recursiveRef` & `$recursiveAnchor` support in Draft 2019-09.
- `$dynamicRef` & `$dynamicAnchor` support in Draft 2020-12.

### Changed

Expand Down
9 changes: 7 additions & 2 deletions crates/jsonschema-referencing/src/anchors/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,11 @@ impl Anchor {
/// Get the resource for this anchor.
pub(crate) fn resolve<'r>(&'r self, resolver: Resolver<'r>) -> Result<Resolved<'r>, 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() {
Expand All @@ -50,6 +54,7 @@ impl Anchor {
Ok(Resolved::new(
last.contents(),
resolver.in_subresource((**last).as_ref())?,
last.draft(),
))
}
}
Expand Down Expand Up @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion crates/jsonschema-referencing/src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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: \"[]\" }"
);
}

Expand Down
31 changes: 22 additions & 9 deletions crates/jsonschema-referencing/src/resolver.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
///
Expand All @@ -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() {
Expand Down Expand Up @@ -56,12 +56,12 @@ impl<'r> Resolver<'r> {
pub(crate) fn from_parts(
registry: &'r Registry,
base_uri: UriRef<String>,
parent: VecDeque<UriRef<String>>,
scopes: VecDeque<UriRef<String>>,
) -> Self {
Self {
registry,
base_uri,
scopes: parent,
scopes,
}
}
#[must_use]
Expand Down Expand Up @@ -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.
///
Expand Down Expand Up @@ -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]
Expand All @@ -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)
}
}
2 changes: 1 addition & 1 deletion crates/jsonschema-referencing/src/resource.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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]
Expand Down
19 changes: 10 additions & 9 deletions crates/jsonschema/src/compiler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,7 @@ impl<'a> Context<'a> {
&self,
reference: &str,
is_recursive: bool,
) -> Result<Option<(Uri, Resource)>, ValidationError<'static>> {
) -> Result<Option<(Uri, VecDeque<Uri>, Resource)>, ValidationError<'static>> {
let resolved = if reference == "#" {
// Known & simple recursive reference
// It may also use some additional logic from the `$recursiveAnchor` keyword
Expand All @@ -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::<VecDeque<_>>();
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)))
}
}

Expand Down
5 changes: 4 additions & 1 deletion crates/jsonschema/src/keywords/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,10 @@ pub(crate) fn get_for_draft(draft: Draft, keyword: &str) -> Option<CompileFunc>
Some(unevaluated_properties::compile)
}

// Default case
// Draft 2020-12 specific
(Draft::Draft202012, "$dynamicRef") => Some(ref_::compile),

// Unknown or not-yet-implemented keyword
_ => None,
}
}
Expand Down
9 changes: 5 additions & 4 deletions crates/jsonschema/src/keywords/ref_.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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()),
Expand All @@ -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())?;
Expand Down
2 changes: 1 addition & 1 deletion crates/jsonschema/src/keywords/unevaluated_properties.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)?;
Expand Down
3 changes: 0 additions & 3 deletions crates/jsonschema/tests/suite.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down

0 comments on commit dfa74db

Please sign in to comment.