diff --git a/CHANGELOG.md b/CHANGELOG.md index 57c53713..6a4eb769 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Changing base URI when `$ref` is present in drafts 7 and earlier. + ## [0.18.2] - 2024-09-11 ### Fixed diff --git a/bindings/python/CHANGELOG.md b/bindings/python/CHANGELOG.md index db71c2bd..e6a5b87e 100644 --- a/bindings/python/CHANGELOG.md +++ b/bindings/python/CHANGELOG.md @@ -2,6 +2,10 @@ ## [Unreleased] +### Fixed + +- Changing base URI when `$ref` is present in drafts 7 and earlier. + ## [0.18.2] - 2024-09-11 ### Fixed diff --git a/jsonschema/src/compilation/context.rs b/jsonschema/src/compilation/context.rs index 55729fa1..988d1b31 100644 --- a/jsonschema/src/compilation/context.rs +++ b/jsonschema/src/compilation/context.rs @@ -3,7 +3,7 @@ use crate::{ compilation::DEFAULT_SCOPE, paths::{JSONPointer, JsonPointerNode, PathChunkRef}, resolver::Resolver, - schemas, + schemas, Draft, }; use serde_json::Value; use std::{borrow::Cow, sync::Arc}; @@ -101,12 +101,26 @@ impl<'a> CompilationContext<'a> { #[inline] pub(crate) fn push(&'a self, schema: &Value) -> Result { if let Some(id) = schemas::id_of(self.config.draft(), schema) { - Ok(CompilationContext { - base_uri: self.base_uri.with_new_scope(id)?, - config: Arc::clone(&self.config), - resolver: Arc::clone(&self.resolver), - schema_path: self.schema_path.clone(), - }) + if matches!( + self.config.draft(), + Draft::Draft4 | Draft::Draft6 | Draft::Draft7 + ) && schema.get("$ref").is_some() + { + // If `$ref` is present, then other keywords are ignored in Drafts 7 and earlier + Ok(CompilationContext { + base_uri: self.base_uri.clone(), + config: Arc::clone(&self.config), + resolver: Arc::clone(&self.resolver), + schema_path: self.schema_path.clone(), + }) + } else { + Ok(CompilationContext { + base_uri: self.base_uri.with_new_scope(id)?, + config: Arc::clone(&self.config), + resolver: Arc::clone(&self.resolver), + schema_path: self.schema_path.clone(), + }) + } } else { Ok(CompilationContext { base_uri: self.base_uri.clone(), diff --git a/jsonschema/src/keywords/ref_.rs b/jsonschema/src/keywords/ref_.rs index bfb59b9e..e2e1afcf 100644 --- a/jsonschema/src/keywords/ref_.rs +++ b/jsonschema/src/keywords/ref_.rs @@ -56,6 +56,7 @@ impl Validate for RefValidator { if let Some(sub_nodes) = self.sub_nodes.read().as_ref() { return sub_nodes.is_valid(instance); } + dbg!(777); if let Ok((scope, resolved)) = self.resolver.resolve_fragment( self.config.draft(), &self.reference, @@ -157,7 +158,7 @@ pub(crate) const fn supports_adjacent_validation(draft: Draft) -> bool { #[cfg(test)] mod tests { - use crate::{tests_util, JSONSchema}; + use crate::{tests_util, Draft, JSONSchema}; use serde_json::{json, Value}; use test_case::test_case; @@ -227,6 +228,37 @@ mod tests { tests_util::is_valid(schema, instance); } + #[test] + fn test_ref_prevents_sibling_id_from_changing_the_base_uri() { + let schema = json!({ + "id": "http://localhost:1234/sibling_id/base/", + "definitions": { + "foo": { + "id": "http://localhost:1234/sibling_id/foo.json", + "type": "string" + }, + "base_foo": { + "$comment": "this canonical uri is http://localhost:1234/sibling_id/base/foo.json", + "id": "foo.json", + "type": "number" + } + }, + "allOf": [ + { + "$comment": "$ref resolves to http://localhost:1234/sibling_id/base/foo.json, not http://localhost:1234/sibling_id/foo.json", + "id": "http://localhost:1234/sibling_id/", + "$ref": "foo.json" + } + ] + }); + let instance = json!("a"); + let compiled = JSONSchema::options() + .with_draft(Draft::Draft4) + .compile(&schema) + .expect("Invalid schema"); + tests_util::is_not_valid_with(&compiled, &instance); + } + #[test_case( &json!({ "properties": { diff --git a/jsonschema/src/lib.rs b/jsonschema/src/lib.rs index 4b4045b2..284e210c 100644 --- a/jsonschema/src/lib.rs +++ b/jsonschema/src/lib.rs @@ -133,7 +133,7 @@ pub(crate) mod tests_util { use crate::ValidationError; use serde_json::Value; - fn is_not_valid_inner(compiled: &JSONSchema, instance: &Value) { + pub(crate) fn is_not_valid_with(compiled: &JSONSchema, instance: &Value) { assert!( !compiled.is_valid(instance), "{} should not be valid (via is_valid)", @@ -153,7 +153,7 @@ pub(crate) mod tests_util { pub(crate) fn is_not_valid(schema: &Value, instance: &Value) { let compiled = JSONSchema::compile(schema).unwrap(); - is_not_valid_inner(&compiled, instance) + is_not_valid_with(&compiled, instance) } #[cfg(any(feature = "draft201909", feature = "draft202012"))] @@ -162,7 +162,7 @@ pub(crate) mod tests_util { .with_draft(draft) .compile(schema) .unwrap(); - is_not_valid_inner(&compiled, instance) + is_not_valid_with(&compiled, instance) } pub(crate) fn expect_errors(schema: &Value, instance: &Value, errors: &[&str]) { @@ -177,7 +177,7 @@ pub(crate) mod tests_util { ) } - fn is_valid_inner(compiled: &JSONSchema, instance: &Value) { + pub(crate) fn is_valid_with(compiled: &JSONSchema, instance: &Value) { if let Err(mut errors) = compiled.validate(instance) { let first = errors.next().expect("Errors iterator is empty"); panic!( @@ -199,7 +199,7 @@ pub(crate) mod tests_util { pub(crate) fn is_valid(schema: &Value, instance: &Value) { let compiled = JSONSchema::compile(schema).unwrap(); - is_valid_inner(&compiled, instance); + is_valid_with(&compiled, instance); } #[cfg(any(feature = "draft201909", feature = "draft202012"))] @@ -208,7 +208,7 @@ pub(crate) mod tests_util { .with_draft(draft) .compile(schema) .unwrap(); - is_valid_inner(&compiled, instance) + is_valid_with(&compiled, instance) } pub(crate) fn validate(schema: &Value, instance: &Value) -> ValidationError<'static> {