Skip to main content

Mountain/Track/Effect/CreateEffectForRequest/
FileSystem.rs

1//! # FileSystem Effect (CreateEffectForRequest)
2//!
3//! Effect constructors for the `FileSystem.*` RPC family. Each handler
4//! delegates to the `FileSystemReader` or `FileSystemWriter` provider trait on
5//! `MountainEnvironment`. All methods accept `file://` URIs from Cocoon and
6//! strip the scheme before passing a native `PathBuf` to the provider.
7//!
8//! ## Methods handled
9//!
10//! | Method | Provider | Description |
11//! |---|---|---|
12//! | `FileSystem.ReadFile` | `FileSystemReader` | Read raw bytes from a file |
13//! | `FileSystem.WriteFile` | `FileSystemWriter` | Write bytes to a file |
14//! | `FileSystem.ReadDirectory` | `FileSystemReader` | List directory entries |
15//! | `FileSystem.Stat` | `FileSystemReader` | Get file metadata |
16//! | `FileSystem.CreateDirectory` | `FileSystemWriter` | Create a directory (optionally recursive) |
17//! | `FileSystem.Delete` | `FileSystemWriter` | Delete a file or directory |
18//! | `FileSystem.Rename` | `FileSystemWriter` | Rename/move a file or directory |
19//! | `FileSystem.Copy` | `FileSystemWriter` | Copy a file or directory tree |
20//!
21//! ## VS Code reference
22//!
23//! `vs/platform/files/common/fileService.ts`,
24//! `vs/base/parts/ipc/common/ipc.net.ts`
25
26use std::sync::Arc;
27
28use base64::{Engine as _, engine::general_purpose::STANDARD};
29use CommonLibrary::{
30	Environment::Requires::Requires,
31	FileSystem::{FileSystemReader::FileSystemReader, FileSystemWriter::FileSystemWriter},
32};
33use serde_json::{Value, json};
34use tauri::Runtime;
35
36use crate::Track::Effect::{
37	CreateEffectForRequest::Utilities::Params::{bool_at, str_at, strip_file_uri},
38	MappedEffectType::MappedEffect,
39};
40
41pub fn CreateEffect<R:Runtime>(MethodName:&str, Parameters:Value) -> Option<Result<MappedEffect, String>> {
42	match MethodName {
43		"FileSystem.ReadFile" => {
44			crate::effect!(run_time, {
45				let path_str = str_at(&Parameters, 0);
46
47				// Empty-path guard: extensions occasionally
48				// pass `""` to `vscode.workspace.fs.readFile`
49				// when probing optional config files. Stock VS
50				// Code's FileSystemProvider would return
51				// `FileNotFound`; replicating that contract
52				// here avoids a panic in `PathBuf::from("")`-
53				// rooted FS calls (which can confuse Mountain's
54				// path-security guard into emitting a "path
55				// outside workspace" rejection that trips the
56				// breaker cascade).
57				if path_str.is_empty() {
58					return Err("FileSystem.ReadFile: empty path (resource not found)".to_string());
59				}
60
61				if path_str.starts_with("vscode://schemas-associations/") {
62					let payload =
63						serde_json::to_vec(&json!({ "schemas": [] })).unwrap_or_else(|_| b"{\"schemas\":[]}".to_vec());
64
65					return Ok(json!(payload));
66				}
67
68				let fs_reader:Arc<dyn FileSystemReader> = run_time.Environment.Require();
69
70				let path = std::path::PathBuf::from(strip_file_uri(path_str));
71
72				fs_reader
73					.ReadFile(&path)
74					.await
75					.map(|bytes| json!(bytes))
76					.map_err(|e| e.to_string())
77			})
78		},
79
80		"FileSystem.WriteFile" => {
81			crate::effect!(run_time, {
82				let fs_writer:Arc<dyn FileSystemWriter> = run_time.Environment.Require();
83
84				let path_str = str_at(&Parameters, 0);
85
86				if path_str.is_empty() {
87					return Err("FileSystem.WriteFile: empty path (resource not found)".to_string());
88				}
89
90				let path = std::path::PathBuf::from(strip_file_uri(path_str));
91
92				let content = Parameters.get(1).cloned();
93
94				let content_bytes = match content {
95					Some(Value::Array(arr)) => arr.into_iter().filter_map(|v| v.as_u64().map(|n| n as u8)).collect(),
96					Some(Value::String(s)) => STANDARD.decode(&s).unwrap_or_default(),
97					_ => vec![],
98				};
99
100				fs_writer
101					.WriteFile(&path, content_bytes, true, true)
102					.await
103					.map(|_| json!(null))
104					.map_err(|e| e.to_string())
105			})
106		},
107
108		"FileSystem.ReadDirectory" => {
109			crate::effect!(run_time, {
110				let path_str = str_at(&Parameters, 0);
111
112				// Empty-path guard: same contract as ReadFile and
113				// Stat. An empty string from an extension probe
114				// must return "resource not found" so the
115				// LooksLike404 classifier in
116				// MountainVinegRPCService downgrades the log level
117				// and uses error code -32004 instead of tripping
118				// the circuit breaker with a -32000.
119				if path_str.is_empty() {
120					return Err("FileSystem.ReadDirectory: empty path (resource not found)".to_string());
121				}
122
123				let fs_reader:Arc<dyn FileSystemReader> = run_time.Environment.Require();
124
125				let path = std::path::PathBuf::from(strip_file_uri(path_str));
126
127				fs_reader
128					.ReadDirectory(&path)
129					.await
130					.map(|entries| json!(entries))
131					.map_err(|e| e.to_string())
132			})
133		},
134
135		"FileSystem.Stat" => {
136			crate::effect!(run_time, {
137				let fs_reader:Arc<dyn FileSystemReader> = run_time.Environment.Require();
138
139				let path_str = str_at(&Parameters, 0);
140
141				// Empty-path guard: same rationale as
142				// `FileSystem.ReadFile` above. Returning
143				// `not found` matches VS Code's
144				// `FileSystemProvider.stat()` contract for
145				// probes of paths the extension hasn't
146				// validated upstream.
147				if path_str.is_empty() {
148					return Err("FileSystem.Stat: empty path (resource not found)".to_string());
149				}
150
151				let path = std::path::PathBuf::from(strip_file_uri(path_str));
152
153				fs_reader
154					.StatFile(&path)
155					.await
156					.map(|stat| json!(stat))
157					.map_err(|e| e.to_string())
158			})
159		},
160
161		"FileSystem.CreateDirectory" => {
162			crate::effect!(run_time, {
163				let fs_writer:Arc<dyn FileSystemWriter> = run_time.Environment.Require();
164
165				let path_str = str_at(&Parameters, 0);
166
167				let path = std::path::PathBuf::from(strip_file_uri(path_str));
168
169				fs_writer
170					.CreateDirectory(&path, true)
171					.await
172					.map(|_| json!(null))
173					.map_err(|e| e.to_string())
174			})
175		},
176
177		"FileSystem.Delete" => {
178			crate::effect!(run_time, {
179				let fs_writer:Arc<dyn FileSystemWriter> = run_time.Environment.Require();
180
181				let path_str = str_at(&Parameters, 0);
182
183				let path = std::path::PathBuf::from(strip_file_uri(path_str));
184
185				let recursive = bool_at(&Parameters, 1);
186
187				fs_writer
188					.Delete(&path, recursive, false)
189					.await
190					.map(|_| json!(null))
191					.map_err(|e| e.to_string())
192			})
193		},
194
195		"FileSystem.Rename" => {
196			crate::effect!(run_time, {
197				let fs_writer:Arc<dyn FileSystemWriter> = run_time.Environment.Require();
198
199				let source = str_at(&Parameters, 0);
200
201				let target = str_at(&Parameters, 1);
202
203				fs_writer
204					.Rename(
205						&std::path::PathBuf::from(strip_file_uri(source)),
206						&std::path::PathBuf::from(strip_file_uri(target)),
207						true,
208					)
209					.await
210					.map(|_| json!(null))
211					.map_err(|e| e.to_string())
212			})
213		},
214
215		"FileSystem.Copy" => {
216			crate::effect!(run_time, {
217				let fs_writer:Arc<dyn FileSystemWriter> = run_time.Environment.Require();
218
219				let source = str_at(&Parameters, 0);
220
221				let target = str_at(&Parameters, 1);
222
223				fs_writer
224					.Copy(
225						&std::path::PathBuf::from(strip_file_uri(source)),
226						&std::path::PathBuf::from(strip_file_uri(target)),
227						true,
228					)
229					.await
230					.map(|_| json!(null))
231					.map_err(|e| e.to_string())
232			})
233		},
234
235		_ => None,
236	}
237}