rust_docs_mcp/docs/
outputs.rs

1//! Output types for documentation tools
2//!
3//! These types are used as the return values from docs tool methods.
4//! They are serialized to JSON strings for the MCP protocol, and can be
5//! deserialized in tests for type-safe validation.
6
7use serde::{Deserialize, Serialize};
8
9/// Simplified item information for API responses
10#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
11pub struct ItemInfo {
12    pub id: String,
13    pub name: String,
14    pub kind: String,
15    pub path: Vec<String>,
16    pub docs: Option<String>,
17    pub visibility: String,
18}
19
20/// Preview item info for lightweight responses
21#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
22pub struct ItemPreview {
23    pub id: String,
24    pub name: String,
25    pub kind: String,
26    pub path: Vec<String>,
27}
28
29/// Pagination information
30#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
31pub struct PaginationInfo {
32    pub total: usize,
33    pub limit: usize,
34    pub offset: usize,
35    pub has_more: bool,
36}
37
38/// Output from list_crate_items operation
39#[derive(Debug, Serialize, Deserialize, PartialEq)]
40pub struct ListCrateItemsOutput {
41    pub items: Vec<ItemInfo>,
42    pub pagination: PaginationInfo,
43}
44
45impl ListCrateItemsOutput {
46    /// Convert to JSON string for MCP response
47    pub fn to_json(&self) -> String {
48        serde_json::to_string(self)
49            .unwrap_or_else(|_| r#"{"error":"Failed to serialize response"}"#.to_string())
50    }
51}
52
53/// Output from search_items operation (full details)
54#[derive(Debug, Serialize, Deserialize, PartialEq)]
55pub struct SearchItemsOutput {
56    pub items: Vec<ItemInfo>,
57    pub pagination: PaginationInfo,
58    #[serde(skip_serializing_if = "Option::is_none")]
59    pub warning: Option<String>,
60}
61
62impl SearchItemsOutput {
63    /// Convert to JSON string for MCP response
64    pub fn to_json(&self) -> String {
65        serde_json::to_string(self)
66            .unwrap_or_else(|_| r#"{"error":"Failed to serialize response"}"#.to_string())
67    }
68}
69
70/// Output from search_items_preview operation (lightweight)
71#[derive(Debug, Serialize, Deserialize, PartialEq)]
72pub struct SearchItemsPreviewOutput {
73    pub items: Vec<ItemPreview>,
74    pub pagination: PaginationInfo,
75}
76
77impl SearchItemsPreviewOutput {
78    /// Convert to JSON string for MCP response
79    pub fn to_json(&self) -> String {
80        serde_json::to_string(self)
81            .unwrap_or_else(|_| r#"{"error":"Failed to serialize response"}"#.to_string())
82    }
83}
84
85/// Source location information
86#[derive(Debug, Serialize, Deserialize, PartialEq, Clone)]
87pub struct SourceLocation {
88    pub filename: String,
89    pub line_start: usize,
90    pub column_start: usize,
91    pub line_end: usize,
92    pub column_end: usize,
93}
94
95/// Detailed item information including signatures
96#[derive(Debug, Serialize, Deserialize, PartialEq)]
97pub struct DetailedItem {
98    pub info: ItemInfo,
99    pub signature: Option<String>,
100    pub generics: Option<serde_json::Value>,
101    pub fields: Option<Vec<ItemInfo>>,
102    pub variants: Option<Vec<ItemInfo>>,
103    pub methods: Option<Vec<ItemInfo>>,
104    pub source_location: Option<SourceLocation>,
105}
106
107/// Output from get_item_details operation
108#[derive(Debug, Serialize, Deserialize, PartialEq)]
109#[serde(untagged)]
110pub enum GetItemDetailsOutput {
111    Success(Box<DetailedItem>),
112    Error { error: String },
113}
114
115impl GetItemDetailsOutput {
116    /// Convert to JSON string for MCP response
117    pub fn to_json(&self) -> String {
118        serde_json::to_string(self)
119            .unwrap_or_else(|_| r#"{"error":"Failed to serialize response"}"#.to_string())
120    }
121
122    /// Check if this is a success response
123    pub fn is_success(&self) -> bool {
124        matches!(self, GetItemDetailsOutput::Success(_))
125    }
126
127    /// Check if this is an error response
128    pub fn is_error(&self) -> bool {
129        matches!(self, GetItemDetailsOutput::Error { .. })
130    }
131}
132
133/// Output from get_item_docs operation
134#[derive(Debug, Serialize, Deserialize, PartialEq)]
135pub struct GetItemDocsOutput {
136    pub documentation: Option<String>,
137    #[serde(skip_serializing_if = "Option::is_none")]
138    pub message: Option<String>,
139}
140
141impl GetItemDocsOutput {
142    /// Convert to JSON string for MCP response
143    pub fn to_json(&self) -> String {
144        serde_json::to_string(self)
145            .unwrap_or_else(|_| r#"{"error":"Failed to serialize response"}"#.to_string())
146    }
147}
148
149/// Source code information for an item
150#[derive(Debug, Serialize, Deserialize, PartialEq)]
151pub struct SourceInfo {
152    pub location: SourceLocation,
153    pub code: String,
154    pub context_lines: Option<usize>,
155}
156
157/// Output from get_item_source operation
158#[derive(Debug, Serialize, Deserialize, PartialEq)]
159#[serde(untagged)]
160pub enum GetItemSourceOutput {
161    Success(SourceInfo),
162    Error { error: String },
163}
164
165impl GetItemSourceOutput {
166    /// Convert to JSON string for MCP response
167    pub fn to_json(&self) -> String {
168        serde_json::to_string(self)
169            .unwrap_or_else(|_| r#"{"error":"Failed to serialize response"}"#.to_string())
170    }
171
172    /// Check if this is a success response
173    pub fn is_success(&self) -> bool {
174        matches!(self, GetItemSourceOutput::Success(_))
175    }
176
177    /// Check if this is an error response
178    pub fn is_error(&self) -> bool {
179        matches!(self, GetItemSourceOutput::Error { .. })
180    }
181}
182
183/// Generic error output for docs tools
184#[derive(Debug, Serialize, Deserialize, PartialEq)]
185pub struct DocsErrorOutput {
186    pub error: String,
187}
188
189impl DocsErrorOutput {
190    /// Create a new error output
191    pub fn new(message: impl Into<String>) -> Self {
192        Self {
193            error: message.into(),
194        }
195    }
196
197    /// Convert to JSON string for MCP response
198    pub fn to_json(&self) -> String {
199        serde_json::to_string(self)
200            .unwrap_or_else(|_| r#"{"error":"Failed to serialize error"}"#.to_string())
201    }
202}
203
204#[cfg(test)]
205mod tests {
206    use super::*;
207
208    #[test]
209    fn test_list_items_output_serialization() {
210        let output = ListCrateItemsOutput {
211            items: vec![ItemInfo {
212                id: "1".to_string(),
213                name: "test_fn".to_string(),
214                kind: "function".to_string(),
215                path: vec!["test".to_string()],
216                docs: Some("Test function".to_string()),
217                visibility: "public".to_string(),
218            }],
219            pagination: PaginationInfo {
220                total: 1,
221                limit: 100,
222                offset: 0,
223                has_more: false,
224            },
225        };
226
227        let json = output.to_json();
228        let deserialized: ListCrateItemsOutput = serde_json::from_str(&json).unwrap();
229        assert_eq!(output, deserialized);
230    }
231
232    #[test]
233    fn test_search_preview_output() {
234        let output = SearchItemsPreviewOutput {
235            items: vec![ItemPreview {
236                id: "42".to_string(),
237                name: "MyStruct".to_string(),
238                kind: "struct".to_string(),
239                path: vec!["my_mod".to_string()],
240            }],
241            pagination: PaginationInfo {
242                total: 1,
243                limit: 10,
244                offset: 0,
245                has_more: false,
246            },
247        };
248
249        let json = output.to_json();
250        let deserialized: SearchItemsPreviewOutput = serde_json::from_str(&json).unwrap();
251        assert_eq!(output, deserialized);
252    }
253
254    #[test]
255    fn test_item_details_output() {
256        let success = GetItemDetailsOutput::Success(Box::new(DetailedItem {
257            info: ItemInfo {
258                id: "1".to_string(),
259                name: "test".to_string(),
260                kind: "function".to_string(),
261                path: vec![],
262                docs: None,
263                visibility: "public".to_string(),
264            },
265            signature: Some("fn test()".to_string()),
266            generics: None,
267            fields: None,
268            variants: None,
269            methods: None,
270            source_location: None,
271        }));
272
273        assert!(success.is_success());
274        assert!(!success.is_error());
275
276        let error = GetItemDetailsOutput::Error {
277            error: "Not found".to_string(),
278        };
279
280        assert!(!error.is_success());
281        assert!(error.is_error());
282    }
283}