use std::sync::{Arc, LazyLock};
use rmcp::model::JsonObject;
pub static TOOL_INPUT_BASE_SCHEMA: LazyLock<Arc<JsonObject>> =
LazyLock::new(|| Arc::new(build_schema::<crate::tool::ToolInput>()));
pub static TOOL_OUTPUT_BASE_SCHEMA: LazyLock<Arc<JsonObject>> =
LazyLock::new(|| Arc::new(build_schema::<crate::tool::ToolOutput>()));
fn build_schema<T: schemars::JsonSchema>() -> JsonObject {
let schema = schemars::schema_for!(T);
match schema.to_value() {
serde_json::Value::Object(map) => map,
other => unreachable!(
"schemars produced a non-Object root for a JsonSchema-derived struct: {other:?}"
),
}
}
pub fn fresh_tool_input_schema() -> JsonObject {
(**TOOL_INPUT_BASE_SCHEMA).clone()
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn tool_input_schema_is_object_with_properties() {
let schema = fresh_tool_input_schema();
let props = schema
.get("properties")
.and_then(|v| v.as_object())
.expect("ToolInput schema must have a 'properties' object");
assert!(props.contains_key("flags"), "must have 'flags' property");
assert!(props.contains_key("args"), "must have 'args' property");
}
#[test]
fn tool_output_schema_is_object_with_properties() {
let schema = (**TOOL_OUTPUT_BASE_SCHEMA).clone();
let props = schema
.get("properties")
.and_then(|v| v.as_object())
.expect("ToolOutput schema must have a 'properties' object");
assert!(props.contains_key("stdout"), "must have 'stdout' property");
assert!(props.contains_key("stderr"), "must have 'stderr' property");
assert!(
props.contains_key("exit_code"),
"must have 'exit_code' property"
);
}
#[test]
fn fresh_tool_input_schema_is_independent() {
let mut fresh = fresh_tool_input_schema();
fresh.insert("test_key".into(), serde_json::Value::Bool(true));
let second = fresh_tool_input_schema();
assert!(
!second.contains_key("test_key"),
"mutating a fresh clone must not affect the cache"
);
}
#[test]
fn cache_returns_same_arc_for_repeated_reads() {
let first = Arc::clone(&TOOL_INPUT_BASE_SCHEMA);
let second = Arc::clone(&TOOL_INPUT_BASE_SCHEMA);
assert!(
Arc::ptr_eq(&first, &second),
"repeated reads must return the same Arc allocation"
);
}
}