pforge_runtime/handler.rs
1use async_trait::async_trait;
2use schemars::JsonSchema;
3use serde::{de::DeserializeOwned, Serialize};
4
5/// Core handler abstraction - zero-cost, compatible with pmcp TypedTool.
6///
7/// The `Handler` trait provides a type-safe interface for implementing MCP tools.
8/// It leverages Rust's type system for compile-time validation and automatic
9/// JSON Schema generation.
10///
11/// # Type Parameters
12///
13/// - `Input`: The input type, must be deserializable and have a JSON schema
14/// - `Output`: The output type, must be serializable and have a JSON schema
15/// - `Error`: Error type that can be converted into `pforge_runtime::Error`
16///
17/// # Examples
18///
19/// ## Basic Handler
20///
21/// ```rust
22/// use pforge_runtime::{Handler, Result};
23/// use serde::{Deserialize, Serialize};
24/// use schemars::JsonSchema;
25///
26/// #[derive(Debug, Deserialize, JsonSchema)]
27/// struct GreetInput {
28/// name: String,
29/// }
30///
31/// #[derive(Debug, Serialize, JsonSchema)]
32/// struct GreetOutput {
33/// message: String,
34/// }
35///
36/// struct GreetHandler;
37///
38/// #[async_trait::async_trait]
39/// impl Handler for GreetHandler {
40/// type Input = GreetInput;
41/// type Output = GreetOutput;
42/// type Error = pforge_runtime::Error;
43///
44/// async fn handle(&self, input: Self::Input) -> Result<Self::Output> {
45/// Ok(GreetOutput {
46/// message: format!("Hello, {}!", input.name),
47/// })
48/// }
49/// }
50/// ```
51///
52/// ## Handler with Validation
53///
54/// ```rust
55/// use pforge_runtime::{Handler, Result, Error};
56/// use serde::{Deserialize, Serialize};
57/// use schemars::JsonSchema;
58///
59/// #[derive(Debug, Deserialize, JsonSchema)]
60/// struct AgeInput {
61/// age: i32,
62/// }
63///
64/// #[derive(Debug, Serialize, JsonSchema)]
65/// struct AgeOutput {
66/// category: String,
67/// }
68///
69/// struct AgeHandler;
70///
71/// #[async_trait::async_trait]
72/// impl Handler for AgeHandler {
73/// type Input = AgeInput;
74/// type Output = AgeOutput;
75/// type Error = Error;
76///
77/// async fn handle(&self, input: Self::Input) -> Result<Self::Output> {
78/// if input.age < 0 {
79/// return Err(Error::Handler("Age cannot be negative".to_string()));
80/// }
81///
82/// let category = match input.age {
83/// 0..=12 => "child",
84/// 13..=19 => "teenager",
85/// 20..=64 => "adult",
86/// _ => "senior",
87/// }.to_string();
88///
89/// Ok(AgeOutput { category })
90/// }
91/// }
92/// ```
93#[async_trait]
94pub trait Handler: Send + Sync + 'static {
95 /// Input type for the handler
96 type Input: JsonSchema + DeserializeOwned + Send;
97
98 /// Output type for the handler
99 type Output: JsonSchema + Serialize + Send;
100
101 /// Error type that can be converted to `pforge_runtime::Error`
102 type Error: Into<crate::Error>;
103
104 /// Execute the handler with type-safe input.
105 ///
106 /// This is the core method where tool logic is implemented.
107 /// Input is automatically deserialized and output is automatically serialized.
108 async fn handle(&self, input: Self::Input) -> Result<Self::Output, Self::Error>;
109
110 /// Generate JSON schema for input (override for custom schemas).
111 ///
112 /// By default, this derives the schema from the `Input` type using schemars.
113 /// Override this method to provide custom validation rules or documentation.
114 fn input_schema() -> schemars::schema::RootSchema {
115 schemars::schema_for!(Self::Input)
116 }
117
118 /// Generate JSON schema for output.
119 ///
120 /// By default, this derives the schema from the `Output` type using schemars.
121 fn output_schema() -> schemars::schema::RootSchema {
122 schemars::schema_for!(Self::Output)
123 }
124}
125
126#[cfg(test)]
127mod tests {
128 use super::*;
129 use schemars::JsonSchema;
130 use serde::{Deserialize, Serialize};
131
132 #[derive(Debug, Deserialize, JsonSchema)]
133 struct TestInput {
134 value: i32,
135 }
136
137 #[derive(Debug, Serialize, JsonSchema)]
138 struct TestOutput {
139 result: i32,
140 }
141
142 struct TestHandler;
143
144 #[async_trait]
145 impl Handler for TestHandler {
146 type Input = TestInput;
147 type Output = TestOutput;
148 type Error = crate::Error;
149
150 async fn handle(&self, input: Self::Input) -> crate::Result<Self::Output> {
151 Ok(TestOutput {
152 result: input.value * 2,
153 })
154 }
155 }
156
157 #[tokio::test]
158 async fn test_handler_schema_not_default() {
159 // Test that schemas are NOT Default::default() - kills the mutant
160 let input_schema = TestHandler::input_schema();
161 let default_schema = schemars::schema::RootSchema::default();
162
163 assert_ne!(
164 serde_json::to_string(&input_schema).unwrap(),
165 serde_json::to_string(&default_schema).unwrap(),
166 "Handler::input_schema() must not return Default::default()"
167 );
168
169 let output_schema = TestHandler::output_schema();
170 assert_ne!(
171 serde_json::to_string(&output_schema).unwrap(),
172 serde_json::to_string(&default_schema).unwrap(),
173 "Handler::output_schema() must not return Default::default()"
174 );
175 }
176
177 #[tokio::test]
178 async fn test_handler_execution() {
179 let handler = TestHandler;
180 let input = TestInput { value: 21 };
181 let result = handler.handle(input).await.unwrap();
182 assert_eq!(result.result, 42);
183 }
184}