Skip to content

Commit

Permalink
fix: Resolving external references nested inside local references
Browse files Browse the repository at this point in the history
Signed-off-by: Dmitry Dygalo <[email protected]>
  • Loading branch information
Stranger6667 committed Jan 22, 2025
1 parent 29b37f0 commit 7a2ac3e
Show file tree
Hide file tree
Showing 4 changed files with 68 additions and 4 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## [Unreleased]

### Fixed

- Resolving external references that nested inside local references. [#671](https://github.com/Stranger6667/jsonschema/issues/671)

## [0.28.1] - 2024-12-31

### Fixed
Expand Down
4 changes: 4 additions & 0 deletions crates/jsonschema-py/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,10 @@

## [Unreleased]

### Fixed

- Resolving external references that nested inside local references. [#671](https://github.com/Stranger6667/jsonschema/issues/671)

## [0.28.1] - 2024-12-31

### Fixed
Expand Down
20 changes: 16 additions & 4 deletions crates/jsonschema-referencing/src/registry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -444,28 +444,40 @@ fn collect_external_resources(
collected: &mut AHashSet<Uri<String>>,
seen: &mut AHashSet<u64>,
) -> Result<(), Error> {
// URN schemes are not supported for external resolution
if base.scheme().as_str() == "urn" {
return Ok(());
}
for key in ["$ref", "$schema"] {
if let Some(reference) = contents.get(key).and_then(Value::as_str) {
if reference.starts_with('#')
|| reference.starts_with("https://json-schema.org/draft/2020-12/")
// Skip well-known schema references
if reference.starts_with("https://json-schema.org/draft/2020-12/")
|| reference.starts_with("https://json-schema.org/draft/2019-09/")
|| reference.starts_with("http://json-schema.org/draft-07/")
|| reference.starts_with("http://json-schema.org/draft-06/")
|| reference.starts_with("http://json-schema.org/draft-04/")
{
// Not an external resource
return Ok(());
continue;
}
// Handle local references separately as they may have nested references to external resources
if reference.starts_with('#') {
if reference == "#" {
continue;
}
if let Some(referenced) = contents.pointer(reference.trim_start_matches('#')) {
collect_external_resources(base, referenced, collected, seen)?;
}
continue;
}

let mut hasher = AHasher::default();
(base.as_str(), reference).hash(&mut hasher);
let hash = hasher.finish();
if !seen.insert(hash) {
// Reference has already been seen
return Ok(());
}

let resolved = if reference.contains('#') && base.has_fragment() {
uri::resolve_against(&uri::DEFAULT_ROOT_URI.borrow(), reference)?
} else {
Expand Down
44 changes: 44 additions & 0 deletions crates/jsonschema/src/keywords/ref_.rs
Original file line number Diff line number Diff line change
Expand Up @@ -607,4 +607,48 @@ mod tests {
let validator = crate::validator_for(&json!({"$ref": "#"})).expect("Invalid schema");
assert!(validator.is_valid(&json!(42)));
}

#[test]
fn test_nested_external_reference() {
let schema = json!({
"$id": "foo://schema_1.json",
"$ref": "#/$defs/a/b",
"$defs": {
"a": {
"b": {
"description": "nested schema with external ref",
"$ref": "foo://schema_2.json"
}
}
}
});

struct NestedRetrieve;

impl Retrieve for NestedRetrieve {
fn retrieve(
&self,
uri: &Uri<&str>,
) -> Result<Value, Box<dyn std::error::Error + Send + Sync>> {
match uri.as_str() {
"foo://schema_2.json" => Ok(json!({
"$id": "foo://schema_2.json",
"type": "string"
})),
_ => panic!("Unexpected URI: {}", uri.path()),
}
}
}

let validator = match crate::options()
.with_retriever(NestedRetrieve)
.build(&schema)
{
Ok(validator) => validator,
Err(error) => panic!("Failed to build validator: {}", error),
};

assert!(validator.is_valid(&json!("test")));
assert!(!validator.is_valid(&json!(42)));
}
}

0 comments on commit 7a2ac3e

Please sign in to comment.