Skip to main content

turbomcp_types/
traits.rs

1//! Conversion traits for ergonomic result handling.
2//!
3//! These traits allow tool, resource, and prompt handlers to return
4//! various types that are automatically converted to the appropriate result type.
5//!
6//! # Example
7//!
8//! ```
9//! use turbomcp_types::{IntoToolResult, ToolResult};
10//!
11//! // All of these work as tool return types:
12//! fn returns_string() -> impl IntoToolResult { "Hello".to_string() }
13//! fn returns_i64() -> impl IntoToolResult { 42i64 }
14//! fn returns_result() -> impl IntoToolResult { Ok::<_, String>("Success") }
15//! fn returns_tool_result() -> impl IntoToolResult { ToolResult::text("Direct") }
16//! ```
17
18use core::fmt::Display;
19
20use serde::Serialize;
21
22#[cfg(not(feature = "std"))]
23use alloc::{
24    format,
25    string::{String, ToString},
26    vec,
27    vec::Vec,
28};
29
30use crate::content::Message;
31use crate::results::{PromptResult, ResourceResult, ToolResult};
32
33/// Convert any type to a `ToolResult`.
34///
35/// This trait is implemented for common types, allowing handlers to return
36/// simple types that are automatically wrapped in `ToolResult`.
37///
38/// # Implementations
39///
40/// - `String`, `&str` → text result
41/// - Numeric types → text result with string representation
42/// - `bool` → text result ("true" or "false")
43/// - `()` → empty result
44/// - `ToolResult` → pass through
45/// - `Result<T, E>` → success result or error result
46/// - `Option<T>` → result or empty
47/// - `Vec<T>` → JSON result
48///
49/// # Example
50///
51/// ```
52/// use turbomcp_types::{IntoToolResult, ToolResult};
53///
54/// // String becomes text result
55/// let result: ToolResult = "Hello".to_string().into_tool_result();
56/// assert_eq!(result.first_text(), Some("Hello"));
57///
58/// // Numbers become text
59/// let result: ToolResult = 42i64.into_tool_result();
60/// assert_eq!(result.first_text(), Some("42"));
61///
62/// // Results are handled properly
63/// let ok: Result<&str, &str> = Ok("success");
64/// let result = ok.into_tool_result();
65/// assert!(!result.is_error());
66///
67/// let err: Result<&str, &str> = Err("failed");
68/// let result = err.into_tool_result();
69/// assert!(result.is_error());
70/// ```
71pub trait IntoToolResult {
72    /// Convert this value into a `ToolResult`.
73    fn into_tool_result(self) -> ToolResult;
74}
75
76// String types
77impl IntoToolResult for String {
78    fn into_tool_result(self) -> ToolResult {
79        ToolResult::text(self)
80    }
81}
82
83impl IntoToolResult for &str {
84    fn into_tool_result(self) -> ToolResult {
85        ToolResult::text(self)
86    }
87}
88
89impl IntoToolResult for &String {
90    fn into_tool_result(self) -> ToolResult {
91        ToolResult::text(self.clone())
92    }
93}
94
95// Numeric types
96impl IntoToolResult for i8 {
97    fn into_tool_result(self) -> ToolResult {
98        ToolResult::text(self.to_string())
99    }
100}
101
102impl IntoToolResult for i16 {
103    fn into_tool_result(self) -> ToolResult {
104        ToolResult::text(self.to_string())
105    }
106}
107
108impl IntoToolResult for i32 {
109    fn into_tool_result(self) -> ToolResult {
110        ToolResult::text(self.to_string())
111    }
112}
113
114impl IntoToolResult for i64 {
115    fn into_tool_result(self) -> ToolResult {
116        ToolResult::text(self.to_string())
117    }
118}
119
120impl IntoToolResult for i128 {
121    fn into_tool_result(self) -> ToolResult {
122        ToolResult::text(self.to_string())
123    }
124}
125
126impl IntoToolResult for isize {
127    fn into_tool_result(self) -> ToolResult {
128        ToolResult::text(self.to_string())
129    }
130}
131
132impl IntoToolResult for u8 {
133    fn into_tool_result(self) -> ToolResult {
134        ToolResult::text(self.to_string())
135    }
136}
137
138impl IntoToolResult for u16 {
139    fn into_tool_result(self) -> ToolResult {
140        ToolResult::text(self.to_string())
141    }
142}
143
144impl IntoToolResult for u32 {
145    fn into_tool_result(self) -> ToolResult {
146        ToolResult::text(self.to_string())
147    }
148}
149
150impl IntoToolResult for u64 {
151    fn into_tool_result(self) -> ToolResult {
152        ToolResult::text(self.to_string())
153    }
154}
155
156impl IntoToolResult for u128 {
157    fn into_tool_result(self) -> ToolResult {
158        ToolResult::text(self.to_string())
159    }
160}
161
162impl IntoToolResult for usize {
163    fn into_tool_result(self) -> ToolResult {
164        ToolResult::text(self.to_string())
165    }
166}
167
168impl IntoToolResult for f32 {
169    fn into_tool_result(self) -> ToolResult {
170        ToolResult::text(self.to_string())
171    }
172}
173
174impl IntoToolResult for f64 {
175    fn into_tool_result(self) -> ToolResult {
176        ToolResult::text(self.to_string())
177    }
178}
179
180// Boolean
181impl IntoToolResult for bool {
182    fn into_tool_result(self) -> ToolResult {
183        ToolResult::text(self.to_string())
184    }
185}
186
187// Unit type (empty result)
188impl IntoToolResult for () {
189    fn into_tool_result(self) -> ToolResult {
190        ToolResult::empty()
191    }
192}
193
194// Pass through
195impl IntoToolResult for ToolResult {
196    fn into_tool_result(self) -> ToolResult {
197        self
198    }
199}
200
201// Result handling
202impl<T: IntoToolResult, E: Display> IntoToolResult for Result<T, E> {
203    fn into_tool_result(self) -> ToolResult {
204        match self {
205            Ok(v) => v.into_tool_result(),
206            Err(e) => ToolResult::error(e.to_string()),
207        }
208    }
209}
210
211// Option handling
212impl<T: IntoToolResult> IntoToolResult for Option<T> {
213    fn into_tool_result(self) -> ToolResult {
214        match self {
215            Some(v) => v.into_tool_result(),
216            None => ToolResult::empty(),
217        }
218    }
219}
220
221// Vec as JSON (for serializable types)
222impl<T: Serialize> IntoToolResult for Vec<T> {
223    fn into_tool_result(self) -> ToolResult {
224        ToolResult::json(&self).unwrap_or_else(|e| ToolResult::error(e.to_string()))
225    }
226}
227
228// JSON Value
229impl IntoToolResult for serde_json::Value {
230    fn into_tool_result(self) -> ToolResult {
231        ToolResult::json(&self).unwrap_or_else(|e| ToolResult::error(e.to_string()))
232    }
233}
234
235/// Convert any type to a `ResourceResult`.
236///
237/// This trait allows resource handlers to return simple types that are
238/// automatically wrapped in `ResourceResult`.
239///
240/// # Example
241///
242/// ```
243/// use turbomcp_types::{IntoResourceResult, ResourceResult};
244///
245/// // String becomes text resource
246/// let result: ResourceResult = "Content".to_string().into_resource_result("file:///test");
247/// assert_eq!(result.first_text(), Some("Content"));
248/// ```
249pub trait IntoResourceResult {
250    /// Convert this value into a `ResourceResult`.
251    ///
252    /// The `uri` parameter is used to set the resource URI.
253    fn into_resource_result(self, uri: &str) -> ResourceResult;
254}
255
256impl IntoResourceResult for String {
257    fn into_resource_result(self, uri: &str) -> ResourceResult {
258        ResourceResult::text(uri, self)
259    }
260}
261
262impl IntoResourceResult for &str {
263    fn into_resource_result(self, uri: &str) -> ResourceResult {
264        ResourceResult::text(uri, self)
265    }
266}
267
268impl IntoResourceResult for ResourceResult {
269    fn into_resource_result(self, _uri: &str) -> ResourceResult {
270        self
271    }
272}
273
274impl<T: IntoResourceResult, E: Display> IntoResourceResult for Result<T, E> {
275    fn into_resource_result(self, uri: &str) -> ResourceResult {
276        match self {
277            Ok(v) => v.into_resource_result(uri),
278            Err(e) => ResourceResult::text(uri, format!("Error: {e}")),
279        }
280    }
281}
282
283impl<T: IntoResourceResult> IntoResourceResult for Option<T> {
284    fn into_resource_result(self, uri: &str) -> ResourceResult {
285        match self {
286            Some(v) => v.into_resource_result(uri),
287            None => ResourceResult::empty(),
288        }
289    }
290}
291
292/// Convert any type to a `PromptResult`.
293///
294/// This trait allows prompt handlers to return various message types
295/// that are automatically wrapped in `PromptResult`.
296///
297/// # Example
298///
299/// ```
300/// use turbomcp_types::{IntoPromptResult, PromptResult, Message};
301///
302/// // Vec of messages becomes prompt
303/// let messages = vec![Message::user("Hello"), Message::assistant("Hi!")];
304/// let result: PromptResult = messages.into_prompt_result();
305/// assert_eq!(result.len(), 2);
306/// ```
307pub trait IntoPromptResult {
308    /// Convert this value into a `PromptResult`.
309    fn into_prompt_result(self) -> PromptResult;
310}
311
312impl IntoPromptResult for Vec<Message> {
313    fn into_prompt_result(self) -> PromptResult {
314        PromptResult::new(self)
315    }
316}
317
318impl IntoPromptResult for PromptResult {
319    fn into_prompt_result(self) -> PromptResult {
320        self
321    }
322}
323
324impl IntoPromptResult for Message {
325    fn into_prompt_result(self) -> PromptResult {
326        PromptResult::new(vec![self])
327    }
328}
329
330impl IntoPromptResult for String {
331    fn into_prompt_result(self) -> PromptResult {
332        PromptResult::user(self)
333    }
334}
335
336impl IntoPromptResult for &str {
337    fn into_prompt_result(self) -> PromptResult {
338        PromptResult::user(self)
339    }
340}
341
342impl<T: IntoPromptResult, E: Display> IntoPromptResult for Result<T, E> {
343    fn into_prompt_result(self) -> PromptResult {
344        match self {
345            Ok(v) => v.into_prompt_result(),
346            // Note: Errors are converted to user messages for compatibility.
347            // For proper error propagation, return McpResult from handlers.
348            Err(e) => PromptResult::user(format!("Error: {e}")),
349        }
350    }
351}
352
353impl<T: IntoPromptResult> IntoPromptResult for Option<T> {
354    fn into_prompt_result(self) -> PromptResult {
355        match self {
356            Some(v) => v.into_prompt_result(),
357            None => PromptResult::empty(),
358        }
359    }
360}
361
362#[cfg(test)]
363mod tests {
364    use super::*;
365
366    #[test]
367    fn test_string_into_tool_result() {
368        let result = "Hello".to_string().into_tool_result();
369        assert_eq!(result.first_text(), Some("Hello"));
370        assert!(!result.is_error());
371    }
372
373    #[test]
374    fn test_str_into_tool_result() {
375        let result = "Hello".into_tool_result();
376        assert_eq!(result.first_text(), Some("Hello"));
377    }
378
379    #[test]
380    fn test_i64_into_tool_result() {
381        let result = 42i64.into_tool_result();
382        assert_eq!(result.first_text(), Some("42"));
383    }
384
385    #[test]
386    fn test_bool_into_tool_result() {
387        let result = true.into_tool_result();
388        assert_eq!(result.first_text(), Some("true"));
389    }
390
391    #[test]
392    fn test_unit_into_tool_result() {
393        let result = ().into_tool_result();
394        assert!(result.content.is_empty());
395    }
396
397    #[test]
398    fn test_result_ok_into_tool_result() {
399        let r: Result<&str, &str> = Ok("success");
400        let result = r.into_tool_result();
401        assert_eq!(result.first_text(), Some("success"));
402        assert!(!result.is_error());
403    }
404
405    #[test]
406    fn test_result_err_into_tool_result() {
407        let r: Result<&str, &str> = Err("failed");
408        let result = r.into_tool_result();
409        assert_eq!(result.first_text(), Some("failed"));
410        assert!(result.is_error());
411    }
412
413    #[test]
414    fn test_option_some_into_tool_result() {
415        let r: Option<&str> = Some("value");
416        let result = r.into_tool_result();
417        assert_eq!(result.first_text(), Some("value"));
418    }
419
420    #[test]
421    fn test_option_none_into_tool_result() {
422        let r: Option<&str> = None;
423        let result = r.into_tool_result();
424        assert!(result.content.is_empty());
425    }
426
427    #[test]
428    fn test_vec_into_tool_result() {
429        let v = vec!["a", "b", "c"];
430        let result = v.into_tool_result();
431        assert!(result.structured_content.is_some());
432    }
433
434    #[test]
435    fn test_string_into_resource_result() {
436        let result = "content".to_string().into_resource_result("file:///test");
437        assert_eq!(result.first_text(), Some("content"));
438        match &result.contents[0] {
439            crate::content::ResourceContents::Text(t) => assert_eq!(t.uri, "file:///test"),
440            _ => panic!("Expected text resource contents"),
441        }
442    }
443
444    #[test]
445    fn test_messages_into_prompt_result() {
446        let messages = vec![Message::user("Hello")];
447        let result = messages.into_prompt_result();
448        assert_eq!(result.len(), 1);
449    }
450}