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}