ricecoder_tools/
result.rs

1//! Result types for tool operations
2//!
3//! Provides structured result handling with metadata about operation execution.
4
5use crate::error::ToolError;
6use chrono::Utc;
7use serde::{Deserialize, Serialize};
8
9/// Metadata about a tool operation result
10#[derive(Debug, Clone, Serialize, Deserialize)]
11pub struct ResultMetadata {
12    /// Duration of the operation in milliseconds
13    pub duration_ms: u64,
14    /// Provider that executed the operation ("mcp" or "builtin")
15    pub provider: String,
16    /// Timestamp when the operation completed (ISO 8601 format)
17    pub timestamp: String,
18}
19
20impl ResultMetadata {
21    /// Create new result metadata
22    pub fn new(duration_ms: u64, provider: impl Into<String>) -> Self {
23        Self {
24            duration_ms,
25            provider: provider.into(),
26            timestamp: Utc::now().to_rfc3339(),
27        }
28    }
29}
30
31/// Structured result for tool operations
32///
33/// Contains success status, optional data, optional error, and metadata about execution.
34#[derive(Debug, Clone, Serialize, Deserialize)]
35pub struct ToolResult<T> {
36    /// Whether the operation succeeded
37    pub success: bool,
38    /// Operation result data (if successful)
39    pub data: Option<T>,
40    /// Error information (if failed)
41    pub error: Option<ToolErrorInfo>,
42    /// Metadata about the operation
43    pub metadata: ResultMetadata,
44}
45
46/// Serializable error information
47#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct ToolErrorInfo {
49    /// Machine-readable error code
50    pub code: String,
51    /// Human-readable error message
52    pub message: String,
53    /// Additional context
54    pub details: Option<String>,
55    /// Suggested corrective action
56    pub suggestion: Option<String>,
57}
58
59impl From<&ToolError> for ToolErrorInfo {
60    fn from(err: &ToolError) -> Self {
61        Self {
62            code: err.code.clone(),
63            message: err.message.clone(),
64            details: err.details.clone(),
65            suggestion: err.suggestion.clone(),
66        }
67    }
68}
69
70impl<T> ToolResult<T> {
71    /// Create a successful result
72    pub fn ok(data: T, duration_ms: u64, provider: impl Into<String>) -> Self {
73        Self {
74            success: true,
75            data: Some(data),
76            error: None,
77            metadata: ResultMetadata::new(duration_ms, provider),
78        }
79    }
80
81    /// Create a failed result
82    pub fn err(error: ToolError, duration_ms: u64, provider: impl Into<String>) -> Self {
83        Self {
84            success: false,
85            data: None,
86            error: Some(ToolErrorInfo::from(&error)),
87            metadata: ResultMetadata::new(duration_ms, provider),
88        }
89    }
90}
91
92#[cfg(test)]
93mod tests {
94    use super::*;
95
96    #[test]
97    fn test_result_metadata_creation() {
98        let metadata = ResultMetadata::new(100, "builtin");
99        assert_eq!(metadata.duration_ms, 100);
100        assert_eq!(metadata.provider, "builtin");
101        assert!(!metadata.timestamp.is_empty());
102    }
103
104    #[test]
105    fn test_tool_result_ok() {
106        let result = ToolResult::ok("test data", 50, "mcp");
107        assert!(result.success);
108        assert_eq!(result.data, Some("test data"));
109        assert!(result.error.is_none());
110        assert_eq!(result.metadata.duration_ms, 50);
111        assert_eq!(result.metadata.provider, "mcp");
112    }
113
114    #[test]
115    fn test_tool_result_err() {
116        let error = ToolError::new("TEST_ERROR", "Test message");
117        let result = ToolResult::<String>::err(error, 25, "builtin");
118        assert!(!result.success);
119        assert!(result.data.is_none());
120        assert!(result.error.is_some());
121        assert_eq!(result.metadata.duration_ms, 25);
122    }
123
124    #[test]
125    fn test_tool_error_info_conversion() {
126        let error = ToolError::new("TEST_ERROR", "Test message")
127            .with_details("Details")
128            .with_suggestion("Suggestion");
129        let error_info = ToolErrorInfo::from(&error);
130        assert_eq!(error_info.code, "TEST_ERROR");
131        assert_eq!(error_info.message, "Test message");
132        assert_eq!(error_info.details, Some("Details".to_string()));
133        assert_eq!(error_info.suggestion, Some("Suggestion".to_string()));
134    }
135
136    #[test]
137    fn test_tool_result_serialization() {
138        let result = ToolResult::ok("test data", 50, "mcp");
139        let json = serde_json::to_string(&result).unwrap();
140        assert!(json.contains("\"success\":true"));
141        assert!(json.contains("\"data\":\"test data\""));
142    }
143}