From 1d3712317e7061ee29765556fec31db6f6e9155f Mon Sep 17 00:00:00 2001 From: Rain Date: Sun, 11 Aug 2024 13:59:47 -0700 Subject: [PATCH] [taplo-common] fix infinite recursion with composed allOfs --- crates/taplo-common/Cargo.toml | 6 ++++ crates/taplo-common/src/schema/mod.rs | 46 ++++++++++++++++++++++++-- test-data/schemas/all-of-composed.json | 23 +++++++++++++ 3 files changed, 72 insertions(+), 3 deletions(-) create mode 100644 test-data/schemas/all-of-composed.json diff --git a/crates/taplo-common/Cargo.toml b/crates/taplo-common/Cargo.toml index c586c2080..1f7413ff5 100644 --- a/crates/taplo-common/Cargo.toml +++ b/crates/taplo-common/Cargo.toml @@ -9,6 +9,12 @@ homepage = { workspace = true } license = { workspace = true } repository = { workspace = true } +[dev-dependencies] +tokio = { version = "1.24.2", features = [ + "macros", + "test-util", +] } + [features] # default-tls enables native-tls but without enabling native-tls specific features. native-tls = ["reqwest/default-tls"] diff --git a/crates/taplo-common/src/schema/mod.rs b/crates/taplo-common/src/schema/mod.rs index a4c0282e8..bf934f023 100644 --- a/crates/taplo-common/src/schema/mod.rs +++ b/crates/taplo-common/src/schema/mod.rs @@ -542,9 +542,12 @@ impl Schemas { if let Some(all_ofs) = schema["allOf"].as_array() { if !all_ofs.is_empty() && composed { let mut schema = schema.clone(); - if let Some(obj) = schema["allOf"].as_object_mut() { - obj.remove("allOf"); - } + // Remove the allOf because we're resolving in this spot -- not doing so will cause + // infinite recursion. + schema + .as_object_mut() + .expect("schema is an object") + .remove("allOf"); let mut merged_all_of = Value::Object(serde_json::Map::default()); @@ -747,3 +750,40 @@ mod formats { semver::VersionReq::parse(value).is_ok() } } + +#[cfg(test)] +mod tests { + use std::path::Path; + + use crate::environment::native::NativeEnvironment; + + use super::*; + + #[tokio::test] + async fn test_all_of_composed() { + let schemas = new_schemas(); + let schema_url = url_for_test_schema("all-of-composed.json"); + + // Ensure this doesn't panic: this ensures that allOfs don't cause infinite recursion. + schemas + .possible_schemas_from(&schema_url, &serde_json::Value::Null, &Keys::empty(), 8) + .await + .unwrap(); + } + + fn new_schemas() -> Schemas { + Schemas::new(NativeEnvironment::new(), reqwest::Client::new()) + } + + fn url_for_test_schema(name: &str) -> Url { + let dir = Path::new(env!("CARGO_MANIFEST_DIR")); + let path = dir + .parent() + .unwrap() + .parent() + .unwrap() + .join("test-data/schemas") + .join(name); + Url::parse(&format!("file://{}", path.display())).unwrap() + } +} diff --git a/test-data/schemas/all-of-composed.json b/test-data/schemas/all-of-composed.json new file mode 100644 index 000000000..62f22539a --- /dev/null +++ b/test-data/schemas/all-of-composed.json @@ -0,0 +1,23 @@ +{ + "$schema": "http://json-schema.org/draft-07/schema#", + "properties": { + "duration": { + "allOf": [ + { + "$ref": "#/definitions/Duration" + } + ] + } + }, + "definitions": { + "Duration": { + "title": "Duration", + "description": "A duration, specified in a human-readable format.", + "examples": [ + "60s", + "1w 3d" + ], + "type": "string" + } + } +}