use protocol_droid_schema::wire;
use serde_json::{Value, json};
fn initialize_params() -> Value {
json!({
"protocolVersion": "2025-06-18",
"capabilities": {
"roots": { "listChanged": true },
"sampling": {}
},
"clientInfo": { "name": "fixture", "version": "0.0.0" }
})
}
fn round_trips<T>(fixture: &Value)
where
T: for<'de> serde::Deserialize<'de> + serde::Serialize,
{
let Ok(decoded) = serde_json::from_value::<T>(fixture.clone()) else {
fail(&format!(
"fixture does not decode: {}\n{fixture:#}",
serde_json::from_value::<T>(fixture.clone())
.err()
.map_or_else(|| "unknown".to_owned(), |err| err.to_string())
));
};
let Ok(re_encoded) = serde_json::to_value(&decoded) else {
fail(&format!(
"decoded value does not encode: {err}",
err = serde_json::to_value(&decoded)
.err()
.map_or_else(|| "unknown".to_owned(), |err| err.to_string())
));
};
assert_eq!(re_encoded, *fixture, "round trip lost or changed a member");
}
fn fail(message: &str) -> ! {
eprintln!("test failed: {message}");
std::process::exit(1)
}
#[test]
fn initialize_params_round_trip() {
let params = initialize_params();
round_trips::<wire::v2025_06_18::InitializeRequest>(&json!({
"method": "initialize",
"params": params,
}));
}
#[test]
fn content_union_round_trips() {
let tool_result = json!({
"content": [
{ "type": "text", "text": "hello" },
{ "type": "image", "data": "AAAA", "mimeType": "image/png" },
{
"type": "resource",
"resource": { "uri": "file:///a.txt", "text": "inline" }
}
],
"isError": false
});
round_trips::<wire::v2025_06_18::CallToolResult>(&tool_result);
}
#[test]
fn unknown_members_survive_the_round_trip() {
let result = json!({
"content": [{ "type": "text", "text": "hi" }],
"isError": false,
"x-vendor-extension": { "nested": [1, 2, 3] }
});
round_trips::<wire::v2025_06_18::CallToolResult>(&result);
}
#[test]
fn empty_result_round_trips() {
let result = json!({ "content": [] });
round_trips::<wire::v2025_06_18::CallToolResult>(&result);
}
#[test]
fn maximal_content_round_trips() {
let content = json!({
"type": "text",
"text": "hello",
"annotations": {
"audience": ["user", "assistant"],
"priority": 0.5,
"lastModified": "2026-10-02T00:00:00Z"
}
});
round_trips::<wire::v2025_06_18::TextContent>(&content);
}
#[test]
fn jsonrpc_envelope_round_trips() {
round_trips::<wire::v2025_06_18::JSONRPCRequest>(&json!({
"jsonrpc": "2.0",
"id": 1,
"method": "initialize",
"params": initialize_params()
}));
round_trips::<wire::v2025_06_18::JSONRPCRequest>(&json!({
"jsonrpc": "2.0",
"id": "abc-123",
"method": "tools/list"
}));
}
#[test]
fn error_response_round_trips() {
round_trips::<wire::v2025_06_18::JSONRPCError>(&json!({
"jsonrpc": "2.0",
"id": 1,
"error": {
"code": -32601,
"message": "Method not found",
"data": { "attempted": "tools/frobnicate" }
}
}));
}
#[test]
fn every_revision_exposes_the_same_core_types() {
use protocol_droid_schema::wire;
macro_rules! assert_module {
($($ty:ty),+ $(,)?) => {{
$(let _: Option<$ty> = None;)+
}};
}
assert_module!(
wire::v2024_11_05::CallToolResult,
wire::v2024_11_05::Implementation,
wire::v2024_11_05::JSONRPCRequest,
wire::v2024_11_05::JSONRPCResponse,
wire::v2024_11_05::Tool,
wire::v2024_11_05::ListToolsResult,
wire::v2024_11_05::GetPromptResult,
wire::v2024_11_05::ReadResourceResult,
wire::v2024_11_05::ToolListChangedNotification,
wire::v2024_11_05::ResourceListChangedNotification,
wire::v2024_11_05::ListResourcesResult,
wire::v2024_11_05::ListRootsResult,
wire::v2024_11_05::CreateMessageResult,
wire::v2024_11_05::CompleteResult,
);
}
#[test]
fn every_newer_revision_exposes_the_same_core_types() {
use protocol_droid_schema::wire;
macro_rules! assert_module {
($($ty:ty),+ $(,)?) => {{
$(let _: Option<$ty> = None;)+
}};
}
assert_module!(
wire::v2025_03_26::CallToolResult,
wire::v2025_03_26::Implementation,
wire::v2025_03_26::JSONRPCRequest,
wire::v2025_03_26::JSONRPCResponse,
wire::v2025_03_26::Tool,
wire::v2025_03_26::ListToolsResult,
wire::v2025_03_26::GetPromptResult,
wire::v2025_03_26::ReadResourceResult,
wire::v2025_03_26::ToolListChangedNotification,
wire::v2025_03_26::ResourceListChangedNotification,
wire::v2025_03_26::ListResourcesResult,
wire::v2025_03_26::ListRootsResult,
wire::v2025_03_26::CreateMessageResult,
wire::v2025_03_26::CompleteResult,
);
assert_module!(
wire::v2025_06_18::CallToolResult,
wire::v2025_06_18::Implementation,
wire::v2025_06_18::JSONRPCRequest,
wire::v2025_06_18::JSONRPCResponse,
wire::v2025_06_18::Tool,
wire::v2025_06_18::ListToolsResult,
wire::v2025_06_18::GetPromptResult,
wire::v2025_06_18::ReadResourceResult,
wire::v2025_06_18::ToolListChangedNotification,
wire::v2025_06_18::ResourceListChangedNotification,
wire::v2025_06_18::ListResourcesResult,
wire::v2025_06_18::ListRootsResult,
wire::v2025_06_18::CreateMessageResult,
wire::v2025_06_18::CompleteResult,
);
assert_module!(
wire::v2025_11_25::CallToolResult,
wire::v2025_11_25::Implementation,
wire::v2025_11_25::JSONRPCRequest,
wire::v2025_11_25::JSONRPCResponse,
wire::v2025_11_25::Tool,
wire::v2025_11_25::ListToolsResult,
wire::v2025_11_25::GetPromptResult,
wire::v2025_11_25::ReadResourceResult,
wire::v2025_11_25::ToolListChangedNotification,
wire::v2025_11_25::ResourceListChangedNotification,
wire::v2025_11_25::ListResourcesResult,
wire::v2025_11_25::ListRootsResult,
wire::v2025_11_25::CreateMessageResult,
wire::v2025_11_25::CompleteResult,
);
assert_module!(
wire::v2026_07_28::CallToolResult,
wire::v2026_07_28::Implementation,
wire::v2026_07_28::JSONRPCRequest,
wire::v2026_07_28::JSONRPCResponse,
wire::v2026_07_28::Tool,
wire::v2026_07_28::ListToolsResult,
wire::v2026_07_28::GetPromptResult,
wire::v2026_07_28::ReadResourceResult,
wire::v2026_07_28::ToolListChangedNotification,
wire::v2026_07_28::ResourceListChangedNotification,
wire::v2026_07_28::ListResourcesResult,
wire::v2026_07_28::ListRootsResult,
wire::v2026_07_28::CreateMessageResult,
wire::v2026_07_28::CompleteResult,
);
assert_module!(
wire::draft::CallToolResult,
wire::draft::Implementation,
wire::draft::JSONRPCRequest,
wire::draft::JSONRPCResponse,
wire::draft::Tool,
wire::draft::ListToolsResult,
wire::draft::GetPromptResult,
wire::draft::ReadResourceResult,
wire::draft::ToolListChangedNotification,
wire::draft::ResourceListChangedNotification,
wire::draft::ListResourcesResult,
wire::draft::ListRootsResult,
wire::draft::CreateMessageResult,
wire::draft::CompleteResult,
);
}
#[test]
fn tool_definition_round_trips() {
let tool = json!({
"name": "search",
"description": "Search the corpus",
"inputSchema": {
"type": "object",
"properties": {
"query": { "type": "string" },
"limit": { "type": "integer" }
},
"required": ["query"]
}
});
round_trips::<wire::v2025_06_18::Tool>(&tool);
}