Skip to main content

opencode_sdk/types/
api.rs

1//! HTTP API response types for opencode_rs.
2//!
3//! Typed response wrappers for HTTP endpoints, replacing serde_json::Value returns.
4
5use serde::{Deserialize, Serialize};
6
7// ==================== Messages API Responses ====================
8
9/// Response from command endpoint.
10#[derive(Debug, Clone, Serialize, Deserialize)]
11#[serde(rename_all = "camelCase")]
12pub struct CommandResponse {
13    /// Status of the command.
14    #[serde(default, skip_serializing_if = "Option::is_none")]
15    pub status: Option<String>,
16    /// Additional fields.
17    #[serde(flatten)]
18    pub extra: serde_json::Value,
19}
20
21/// Response from shell endpoint.
22pub type ShellResponse = CommandResponse;
23
24// ==================== Find API Responses ====================
25
26/// Response from find text/files/symbols endpoints.
27#[derive(Debug, Clone, Serialize, Deserialize)]
28#[serde(rename_all = "camelCase")]
29pub struct FindResponse {
30    /// Search results (structure varies by endpoint).
31    #[serde(default, skip_serializing_if = "Option::is_none")]
32    pub results: Option<serde_json::Value>,
33    /// Additional fields.
34    #[serde(flatten)]
35    pub extra: serde_json::Value,
36}
37
38// ==================== Provider API Responses ====================
39
40/// Response from OAuth callback endpoint.
41#[derive(Debug, Clone, Serialize, Deserialize)]
42#[serde(rename_all = "camelCase")]
43pub struct OAuthCallbackResponse {
44    /// Whether the operation succeeded.
45    #[serde(default, skip_serializing_if = "Option::is_none")]
46    pub ok: Option<bool>,
47    /// Message from server.
48    #[serde(default, skip_serializing_if = "Option::is_none")]
49    pub message: Option<String>,
50    /// Additional fields.
51    #[serde(flatten)]
52    pub extra: serde_json::Value,
53}
54
55/// Response from set auth endpoint.
56pub type SetAuthResponse = OAuthCallbackResponse;
57
58// ==================== MCP API Responses ====================
59
60/// Response from MCP action endpoints (add, auth_callback, authenticate, connect, disconnect).
61#[derive(Debug, Clone, Serialize, Deserialize)]
62#[serde(rename_all = "camelCase")]
63pub struct McpActionResponse {
64    /// Whether the operation succeeded.
65    #[serde(default, skip_serializing_if = "Option::is_none")]
66    pub ok: Option<bool>,
67    /// Whether connected.
68    #[serde(default, skip_serializing_if = "Option::is_none")]
69    pub connected: Option<bool>,
70    /// Server name.
71    #[serde(default, skip_serializing_if = "Option::is_none")]
72    pub name: Option<String>,
73    /// Additional fields.
74    #[serde(flatten)]
75    pub extra: serde_json::Value,
76}
77
78// ==================== Misc API Responses ====================
79
80/// Status of an LSP server.
81///
82/// The `/lsp` endpoint returns an array of these.
83#[derive(Debug, Clone, Serialize, Deserialize)]
84#[serde(rename_all = "camelCase")]
85pub struct LspServerStatus {
86    /// Server ID.
87    pub id: String,
88    /// Server name.
89    pub name: String,
90    /// Root directory path (relative to instance directory).
91    pub root: String,
92    /// Connection status.
93    pub status: LspConnectionStatus,
94}
95
96/// LSP server connection status.
97#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
98#[serde(rename_all = "lowercase")]
99pub enum LspConnectionStatus {
100    /// Connected and running.
101    Connected,
102    /// Error state.
103    Error,
104    /// Unknown status (forward compatibility).
105    #[serde(other)]
106    Unknown,
107}
108
109/// Status of a formatter.
110///
111/// The `/formatter` endpoint returns an array of these.
112#[derive(Debug, Clone, Serialize, Deserialize)]
113#[serde(rename_all = "camelCase")]
114pub struct FormatterInfo {
115    /// Formatter name.
116    pub name: String,
117    /// File extensions this formatter handles.
118    #[serde(default)]
119    pub extensions: Vec<String>,
120    /// Whether formatter is enabled.
121    #[serde(default)]
122    pub enabled: bool,
123}
124
125/// Response from OpenAPI doc endpoint.
126#[derive(Debug, Clone, Serialize, Deserialize)]
127#[serde(rename_all = "camelCase")]
128pub struct OpenApiDoc {
129    /// The OpenAPI spec (full document).
130    #[serde(flatten)]
131    pub spec: serde_json::Value,
132}
133
134// ==================== Parts API Responses ====================
135
136/// Response from part update endpoint.
137#[derive(Debug, Clone, Serialize, Deserialize)]
138#[serde(rename_all = "camelCase")]
139pub struct UpdatePartResponse {
140    /// Updated part.
141    #[serde(default, skip_serializing_if = "Option::is_none")]
142    pub part: Option<crate::types::message::Part>,
143    /// Delta text if streaming.
144    #[serde(default, skip_serializing_if = "Option::is_none")]
145    pub delta: Option<String>,
146    /// Additional fields.
147    #[serde(flatten)]
148    pub extra: serde_json::Value,
149}
150
151// ==================== Permission API Responses ====================
152
153/// Response from permission reply endpoint.
154#[derive(Debug, Clone, Serialize, Deserialize)]
155#[serde(rename_all = "camelCase")]
156pub struct PermissionReplyResponse {
157    /// Session ID.
158    #[serde(default, skip_serializing_if = "Option::is_none")]
159    pub session_id: Option<String>,
160    /// Request ID that was replied to.
161    pub request_id: String,
162    /// The reply that was sent.
163    pub reply: crate::types::permission::PermissionReply,
164    /// Additional fields.
165    #[serde(flatten)]
166    pub extra: serde_json::Value,
167}
168
169#[cfg(test)]
170mod tests {
171    use super::*;
172
173    #[test]
174    fn test_command_response_deserialize() {
175        let json = r#"{"status":"executed"}"#;
176        let resp: CommandResponse = serde_json::from_str(json).unwrap();
177        assert_eq!(resp.status, Some("executed".to_string()));
178    }
179
180    #[test]
181    fn test_find_response_deserialize() {
182        let json = r#"{"results":[{"file":"test.rs","line":10}]}"#;
183        let resp: FindResponse = serde_json::from_str(json).unwrap();
184        assert!(resp.results.is_some());
185    }
186
187    #[test]
188    fn test_oauth_callback_response_deserialize() {
189        let json = r#"{"ok":true,"message":"Authentication successful"}"#;
190        let resp: OAuthCallbackResponse = serde_json::from_str(json).unwrap();
191        assert_eq!(resp.ok, Some(true));
192        assert_eq!(resp.message, Some("Authentication successful".to_string()));
193    }
194
195    #[test]
196    fn test_mcp_action_response_deserialize() {
197        let json = r#"{"ok":true,"connected":true,"name":"my-server"}"#;
198        let resp: McpActionResponse = serde_json::from_str(json).unwrap();
199        assert_eq!(resp.ok, Some(true));
200        assert_eq!(resp.connected, Some(true));
201        assert_eq!(resp.name, Some("my-server".to_string()));
202    }
203
204    #[test]
205    fn test_lsp_server_status_deserialize() {
206        let json = r#"{"id":"ra-1","name":"rust-analyzer","root":"./","status":"connected"}"#;
207        let resp: LspServerStatus = serde_json::from_str(json).unwrap();
208        assert_eq!(resp.id, "ra-1");
209        assert_eq!(resp.name, "rust-analyzer");
210        assert_eq!(resp.status, LspConnectionStatus::Connected);
211    }
212
213    #[test]
214    fn test_lsp_server_status_array_deserialize() {
215        let json = r#"[{"id":"ra-1","name":"rust-analyzer","root":"./","status":"connected"}]"#;
216        let resp: Vec<LspServerStatus> = serde_json::from_str(json).unwrap();
217        assert_eq!(resp.len(), 1);
218        assert_eq!(resp[0].name, "rust-analyzer");
219    }
220
221    #[test]
222    fn test_formatter_info_deserialize() {
223        let json = r#"{"name":"rustfmt","extensions":[".rs"],"enabled":true}"#;
224        let resp: FormatterInfo = serde_json::from_str(json).unwrap();
225        assert_eq!(resp.name, "rustfmt");
226        assert!(resp.enabled);
227        assert_eq!(resp.extensions, vec![".rs"]);
228    }
229
230    #[test]
231    fn test_formatter_info_array_deserialize() {
232        let json = r#"[{"name":"rustfmt","extensions":[".rs"],"enabled":true}]"#;
233        let resp: Vec<FormatterInfo> = serde_json::from_str(json).unwrap();
234        assert_eq!(resp.len(), 1);
235        assert_eq!(resp[0].name, "rustfmt");
236    }
237
238    #[test]
239    fn test_update_part_response_deserialize() {
240        let json = r#"{"delta":"Hello"}"#;
241        let resp: UpdatePartResponse = serde_json::from_str(json).unwrap();
242        assert_eq!(resp.delta, Some("Hello".to_string()));
243    }
244
245    #[test]
246    fn test_permission_reply_response_deserialize() {
247        let json = r#"{"sessionId":"sess-123","requestId":"req-456","reply":"always"}"#;
248        let resp: PermissionReplyResponse = serde_json::from_str(json).unwrap();
249        assert_eq!(resp.session_id, Some("sess-123".to_string()));
250        assert_eq!(resp.request_id, "req-456");
251        assert_eq!(
252            resp.reply,
253            crate::types::permission::PermissionReply::Always
254        );
255    }
256}