* fix: address PR review - schema_for_output no longer validates or returns Result - Add strip_output() that strips title/description without validating type (Dale #1) - Change schema_for_output to return Arc<JsonObject> instead of Result (Dale #2) - Cache only Arc<JsonObject> success values, not Result (Dale #3) - Remove dead unwrap_or_else panic paths in with_output_schema, ToolBase, and macros - Tighten test assertions from contains to assert_eq on type field (Dale #4) - Update test_schema_for_output_rejects_primitive to accept_primitive (SEP-2106) Co-authored-by: Orca <help@stably.ai> * test(rmcp): add non-object output schema tests for SEP-2106 Add tests verifying schema_for_output accepts non-object types: - test_tool_builder_methods: primitive (i32), array (Vec<String>), option - test_structured_output: tool returning Json<Vec<T>> and Json<i32> - test_json_schema_detection: Json<Vec<T>>, Result<Json<Vec<T>>,E>, Json<String> - tool_traits: ToolBase::output_schema with Vec<AddOutput> output type * test(rmcp): add missing edge case tests from code review Add tests identified during code review: - description stripping for primitive types - composition types (Option<String> with anyOf/oneOf/null) - cache correctness (Arc::ptr_eq for repeated calls) - schema_for_input rejecting array types (not just primitives) - schema_for_output accepting unit type () * feat!: mark schema_for_output return-type change as breaking This introduces SEP-2106: schema_for_output no longer validates or returns Result. The public signature changed, so bump major. * fix: address Dale's PR review - direct schema.get assertions, remove ArrayTool - Replace loose schema_str.contains(...) assertions with direct schema.get("type") equality checks in test_tool_builder_methods.rs and test_structured_output.rs - Remove redundant ArrayTool fixture and its round-trip serde_json::from_str test from tool_traits.rs since schema is already Arc<JsonObject> - Drop dead schema_str variable in test_structured_output.rs --------- Co-authored-by: Brandon Bennett <brandonbennett@macbookair.myfiosgateway.com> Co-authored-by: Orca <help@stably.ai> Co-authored-by: Brandon Bennett <brandonbennett@Pursuits-Air.lan>
105 lines
3.4 KiB
Rust
105 lines
3.4 KiB
Rust
#![allow(clippy::exhaustive_structs)]
|
|
//cargo test --test test_tool_builder_methods --features "client server macros"
|
|
use rmcp::model::{JsonObject, Tool};
|
|
use schemars::JsonSchema;
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
#[derive(Serialize, Deserialize, JsonSchema)]
|
|
pub struct InputData {
|
|
pub name: String,
|
|
pub age: u32,
|
|
}
|
|
|
|
#[derive(Serialize, Deserialize, JsonSchema)]
|
|
pub struct OutputData {
|
|
pub greeting: String,
|
|
pub is_adult: bool,
|
|
}
|
|
|
|
#[test]
|
|
fn test_with_output_schema() {
|
|
let tool = Tool::new("test", "Test tool", JsonObject::new()).with_output_schema::<OutputData>();
|
|
|
|
assert!(tool.output_schema.is_some());
|
|
|
|
let schema = tool.output_schema.as_ref().unwrap();
|
|
assert_eq!(schema.get("type"), Some(&serde_json::json!("object")));
|
|
}
|
|
|
|
#[test]
|
|
fn test_with_input_schema() {
|
|
let tool = Tool::new("test", "Test tool", JsonObject::new()).with_input_schema::<InputData>();
|
|
|
|
// Verify the schema contains expected fields
|
|
let schema_str = serde_json::to_string(&tool.input_schema).unwrap();
|
|
assert!(schema_str.contains("name"));
|
|
assert!(schema_str.contains("age"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_chained_builder_methods() {
|
|
let tool = Tool::new("test", "Test tool", JsonObject::new())
|
|
.with_input_schema::<InputData>()
|
|
.with_output_schema::<OutputData>()
|
|
.annotate(rmcp::model::ToolAnnotations::new().read_only(true));
|
|
|
|
assert!(tool.output_schema.is_some());
|
|
assert!(tool.annotations.is_some());
|
|
assert_eq!(
|
|
tool.annotations.as_ref().unwrap().read_only_hint,
|
|
Some(true)
|
|
);
|
|
|
|
// Verify both schemas are set correctly
|
|
let input_schema_str = serde_json::to_string(&tool.input_schema).unwrap();
|
|
assert!(input_schema_str.contains("name"));
|
|
assert!(input_schema_str.contains("age"));
|
|
|
|
let output_schema = tool.output_schema.as_ref().unwrap();
|
|
assert_eq!(
|
|
output_schema.get("type"),
|
|
Some(&serde_json::json!("object"))
|
|
);
|
|
}
|
|
|
|
#[test]
|
|
fn test_with_output_schema_primitive() {
|
|
let tool = Tool::new("test", "Test tool", JsonObject::new()).with_output_schema::<i32>();
|
|
|
|
assert!(tool.output_schema.is_some());
|
|
|
|
let schema = tool.output_schema.as_ref().unwrap();
|
|
assert_eq!(schema.get("type"), Some(&serde_json::json!("integer")));
|
|
// title should be stripped from output schema
|
|
assert!(schema.get("title").is_none());
|
|
}
|
|
|
|
#[test]
|
|
fn test_with_output_schema_array() {
|
|
let tool =
|
|
Tool::new("test", "Test tool", JsonObject::new()).with_output_schema::<Vec<String>>();
|
|
|
|
assert!(tool.output_schema.is_some());
|
|
|
|
let schema_str = serde_json::to_string(tool.output_schema.as_ref().unwrap()).unwrap();
|
|
assert!(schema_str.contains("\"type\":\"array\""));
|
|
assert!(schema_str.contains("items"));
|
|
// title should be stripped from output schema
|
|
assert!(!schema_str.contains("title"));
|
|
}
|
|
|
|
#[test]
|
|
fn test_with_output_schema_option() {
|
|
let tool =
|
|
Tool::new("test", "Test tool", JsonObject::new()).with_output_schema::<Option<String>>();
|
|
|
|
assert!(tool.output_schema.is_some());
|
|
|
|
let schema_str = serde_json::to_string(tool.output_schema.as_ref().unwrap()).unwrap();
|
|
// Option<String> generates a composition schema (anyOf/oneOf/type array with null)
|
|
assert!(
|
|
schema_str.contains("anyOf") || schema_str.contains("oneOf") || schema_str.contains("null"),
|
|
"Expected composition schema for Option<String>, got: {schema_str}"
|
|
);
|
|
assert!(!schema_str.contains("title"));
|
|
}
|