Skip to main content

opencode_sdk_rs/resources/
file.rs

1//! File resource types and methods mirroring the JS SDK's `resources/file.ts`.
2
3use serde::{Deserialize, Serialize};
4
5use crate::{client::Opencode, error::OpencodeError};
6
7// ---------------------------------------------------------------------------
8// Types
9// ---------------------------------------------------------------------------
10
11/// The status of a file in the project.
12#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
13#[serde(rename_all = "lowercase")]
14pub enum FileStatus {
15    /// The file was newly added.
16    Added,
17    /// The file was deleted.
18    Deleted,
19    /// The file was modified.
20    Modified,
21}
22
23/// Information about a single file.
24///
25/// Named `FileInfo` instead of `File` to avoid collision with `std::fs::File`.
26#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
27pub struct FileInfo {
28    /// Number of lines added.
29    pub added: i64,
30    /// The file path.
31    pub path: String,
32    /// Number of lines removed.
33    pub removed: i64,
34    /// Current status of the file.
35    pub status: FileStatus,
36}
37
38/// The type of content returned when reading a file.
39#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
40#[serde(rename_all = "lowercase")]
41pub enum FileReadType {
42    /// Raw file content.
43    Raw,
44    /// A patch / diff.
45    Patch,
46}
47
48/// Response from reading a file.
49#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
50pub struct FileReadResponse {
51    /// The file content (raw text or patch).
52    pub content: String,
53    /// The type of content returned.
54    #[serde(rename = "type")]
55    pub file_type: FileReadType,
56}
57
58/// Query parameters for reading a file.
59#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
60pub struct FileReadParams {
61    /// Path of the file to read.
62    pub path: String,
63}
64
65/// Response type for the file status endpoint.
66pub type FileStatusResponse = Vec<FileInfo>;
67
68// ---------------------------------------------------------------------------
69// Resource
70// ---------------------------------------------------------------------------
71
72/// Accessor for the `/file` endpoints.
73pub struct FileResource<'a> {
74    client: &'a Opencode,
75}
76
77impl<'a> FileResource<'a> {
78    pub(crate) const fn new(client: &'a Opencode) -> Self {
79        Self { client }
80    }
81
82    /// Read a file's content.
83    ///
84    /// `GET /file?path=<path>`
85    pub async fn read(&self, params: &FileReadParams) -> Result<FileReadResponse, OpencodeError> {
86        self.client.get_with_query("/file", Some(params), None).await
87    }
88
89    /// Get the status of all files in the project.
90    ///
91    /// `GET /file/status`
92    pub async fn status(&self) -> Result<FileStatusResponse, OpencodeError> {
93        self.client.get("/file/status", None).await
94    }
95}
96
97// ---------------------------------------------------------------------------
98// Tests
99// ---------------------------------------------------------------------------
100
101#[cfg(test)]
102mod tests {
103    use serde_json;
104
105    use super::*;
106
107    #[test]
108    fn file_status_round_trip() {
109        for (variant, expected) in [
110            (FileStatus::Added, "\"added\""),
111            (FileStatus::Deleted, "\"deleted\""),
112            (FileStatus::Modified, "\"modified\""),
113        ] {
114            let json = serde_json::to_string(&variant).unwrap();
115            assert_eq!(json, expected);
116            let parsed: FileStatus = serde_json::from_str(&json).unwrap();
117            assert_eq!(parsed, variant);
118        }
119    }
120
121    #[test]
122    fn file_info_round_trip() {
123        let info = FileInfo {
124            added: 10,
125            path: "src/main.rs".to_string(),
126            removed: 3,
127            status: FileStatus::Modified,
128        };
129        let json = serde_json::to_string(&info).unwrap();
130        let parsed: FileInfo = serde_json::from_str(&json).unwrap();
131        assert_eq!(parsed, info);
132    }
133
134    #[test]
135    fn file_info_deserialize_from_js() {
136        let json = r#"{
137            "added": 5,
138            "path": "README.md",
139            "removed": 0,
140            "status": "added"
141        }"#;
142        let info: FileInfo = serde_json::from_str(json).unwrap();
143        assert_eq!(info.added, 5);
144        assert_eq!(info.path, "README.md");
145        assert_eq!(info.removed, 0);
146        assert_eq!(info.status, FileStatus::Added);
147    }
148
149    #[test]
150    fn file_read_type_round_trip() {
151        for (variant, expected) in
152            [(FileReadType::Raw, "\"raw\""), (FileReadType::Patch, "\"patch\"")]
153        {
154            let json = serde_json::to_string(&variant).unwrap();
155            assert_eq!(json, expected);
156            let parsed: FileReadType = serde_json::from_str(&json).unwrap();
157            assert_eq!(parsed, variant);
158        }
159    }
160
161    #[test]
162    fn file_read_response_round_trip() {
163        let resp =
164            FileReadResponse { content: "fn main() {}".to_string(), file_type: FileReadType::Raw };
165        let json = serde_json::to_string(&resp).unwrap();
166        assert!(json.contains(r#""type":"raw""#));
167        let parsed: FileReadResponse = serde_json::from_str(&json).unwrap();
168        assert_eq!(parsed, resp);
169    }
170
171    #[test]
172    fn file_read_response_deserialize_from_js() {
173        let json = r#"{"content": "diff --git a/file", "type": "patch"}"#;
174        let resp: FileReadResponse = serde_json::from_str(json).unwrap();
175        assert_eq!(resp.content, "diff --git a/file");
176        assert_eq!(resp.file_type, FileReadType::Patch);
177    }
178
179    #[test]
180    fn file_read_params_round_trip() {
181        let params = FileReadParams { path: "src/lib.rs".to_string() };
182        let json = serde_json::to_string(&params).unwrap();
183        let parsed: FileReadParams = serde_json::from_str(&json).unwrap();
184        assert_eq!(parsed, params);
185    }
186
187    #[test]
188    fn file_status_response_round_trip() {
189        let response: FileStatusResponse = vec![
190            FileInfo { added: 1, path: "a.rs".to_string(), removed: 0, status: FileStatus::Added },
191            FileInfo {
192                added: 0,
193                path: "b.rs".to_string(),
194                removed: 10,
195                status: FileStatus::Deleted,
196            },
197        ];
198        let json = serde_json::to_string(&response).unwrap();
199        let parsed: FileStatusResponse = serde_json::from_str(&json).unwrap();
200        assert_eq!(parsed, response);
201    }
202}