aws_types/
os_shim_internal.rs1use std::collections::HashMap;
11use std::env::VarError;
12use std::ffi::OsString;
13use std::fmt::Debug;
14use std::path::{Path, PathBuf};
15use std::sync::{Arc, Mutex};
16
17use crate::os_shim_internal::fs::Fake;
18
19#[derive(Clone, Debug)]
39pub struct Fs(fs::Inner);
40
41impl Default for Fs {
42 fn default() -> Self {
43 Fs::real()
44 }
45}
46
47impl Fs {
48 pub fn real() -> Self {
50 Fs(fs::Inner::Real)
51 }
52
53 pub fn from_raw_map(fs: HashMap<OsString, Vec<u8>>) -> Self {
55 Fs(fs::Inner::Fake(Arc::new(Fake::MapFs(Mutex::new(fs)))))
56 }
57
58 pub fn from_map(data: HashMap<String, impl Into<Vec<u8>>>) -> Self {
60 let fs = data
61 .into_iter()
62 .map(|(k, v)| (k.into(), v.into()))
63 .collect();
64 Self::from_raw_map(fs)
65 }
66
67 pub fn from_test_dir(
88 test_directory: impl Into<PathBuf>,
89 namespaced_to: impl Into<PathBuf>,
90 ) -> Self {
91 Self(fs::Inner::Fake(Arc::new(Fake::NamespacedFs {
92 real_path: test_directory.into(),
93 namespaced_to: namespaced_to.into(),
94 })))
95 }
96
97 pub fn from_slice<'a>(files: &[(&'a str, &'a str)]) -> Self {
110 let fs: HashMap<String, Vec<u8>> = files
111 .iter()
112 .map(|(k, v)| {
113 let k = (*k).to_owned();
114 let v = v.as_bytes().to_vec();
115 (k, v)
116 })
117 .collect();
118
119 Self::from_map(fs)
120 }
121
122 pub async fn read_to_end(&self, path: impl AsRef<Path>) -> std::io::Result<Vec<u8>> {
128 use fs::Inner;
129 let path = path.as_ref();
130 match &self.0 {
131 Inner::Real => std::fs::read(path),
133 Inner::Fake(fake) => match fake.as_ref() {
134 Fake::MapFs(fs) => fs
135 .lock()
136 .unwrap()
137 .get(path.as_os_str())
138 .cloned()
139 .ok_or_else(|| std::io::ErrorKind::NotFound.into()),
140 Fake::NamespacedFs {
141 real_path,
142 namespaced_to,
143 } => {
144 let actual_path = path
145 .strip_prefix(namespaced_to)
146 .map_err(|_| std::io::Error::from(std::io::ErrorKind::NotFound))?;
147 std::fs::read(real_path.join(actual_path))
148 }
149 },
150 }
151 }
152
153 pub async fn write(
157 &self,
158 path: impl AsRef<Path>,
159 contents: impl AsRef<[u8]>,
160 ) -> std::io::Result<()> {
161 use fs::Inner;
162 match &self.0 {
163 Inner::Real => {
165 std::fs::write(&path, contents)?;
166
167 #[cfg(unix)]
168 {
169 use std::os::unix::fs::PermissionsExt;
170 std::fs::set_permissions(path, std::fs::Permissions::from_mode(0o600))?;
171 }
172 }
173 Inner::Fake(fake) => match fake.as_ref() {
174 Fake::MapFs(fs) => {
175 fs.lock()
176 .unwrap()
177 .insert(path.as_ref().as_os_str().into(), contents.as_ref().to_vec());
178 }
179 Fake::NamespacedFs {
180 real_path,
181 namespaced_to,
182 } => {
183 let actual_path = path
184 .as_ref()
185 .strip_prefix(namespaced_to)
186 .map_err(|_| std::io::Error::from(std::io::ErrorKind::NotFound))?;
187 std::fs::write(real_path.join(actual_path), contents)?;
188 }
189 },
190 }
191 Ok(())
192 }
193}
194
195mod fs {
196 use std::collections::HashMap;
197 use std::ffi::OsString;
198 use std::path::PathBuf;
199 use std::sync::{Arc, Mutex};
200
201 #[derive(Clone, Debug)]
202 pub(super) enum Inner {
203 Real,
204 Fake(Arc<Fake>),
205 }
206
207 #[derive(Debug)]
208 pub(super) enum Fake {
209 MapFs(Mutex<HashMap<OsString, Vec<u8>>>),
210 NamespacedFs {
211 real_path: PathBuf,
212 namespaced_to: PathBuf,
213 },
214 }
215}
216
217#[derive(Clone, Debug)]
227pub struct Env(env::Inner);
228
229impl Default for Env {
230 fn default() -> Self {
231 Self::real()
232 }
233}
234
235impl Env {
236 pub fn get(&self, k: &str) -> Result<String, VarError> {
238 use env::Inner;
239 match &self.0 {
240 Inner::Real => std::env::var(k),
241 Inner::Fake(map) => map.get(k).cloned().ok_or(VarError::NotPresent),
242 }
243 }
244
245 pub fn from_slice<'a>(vars: &[(&'a str, &'a str)]) -> Self {
257 let map: HashMap<_, _> = vars
258 .iter()
259 .map(|(k, v)| (k.to_string(), v.to_string()))
260 .collect();
261 Self::from(map)
262 }
263
264 pub fn real() -> Self {
268 Self(env::Inner::Real)
269 }
270}
271
272impl From<HashMap<String, String>> for Env {
273 fn from(hash_map: HashMap<String, String>) -> Self {
274 Self(env::Inner::Fake(Arc::new(hash_map)))
275 }
276}
277
278mod env {
279 use std::collections::HashMap;
280 use std::sync::Arc;
281
282 #[derive(Clone, Debug)]
283 pub(super) enum Inner {
284 Real,
285 Fake(Arc<HashMap<String, String>>),
286 }
287}
288
289#[cfg(test)]
290mod test {
291 use std::env::VarError;
292
293 use crate::os_shim_internal::{Env, Fs};
294
295 #[test]
296 fn env_works() {
297 let env = Env::from_slice(&[("FOO", "BAR")]);
298 assert_eq!(env.get("FOO").unwrap(), "BAR");
299 assert_eq!(
300 env.get("OTHER").expect_err("no present"),
301 VarError::NotPresent
302 )
303 }
304
305 #[tokio::test]
306 async fn fs_from_test_dir_works() {
307 let fs = Fs::from_test_dir(".", "/users/test-data");
308 let _ = fs
309 .read_to_end("/users/test-data/Cargo.toml")
310 .await
311 .expect("file exists");
312
313 let _ = fs
314 .read_to_end("doesntexist")
315 .await
316 .expect_err("file doesnt exists");
317 }
318
319 #[tokio::test]
320 async fn fs_round_trip_file_with_real() {
321 let temp = tempfile::tempdir().unwrap();
322 let path = temp.path().join("test-file");
323
324 let fs = Fs::real();
325 fs.read_to_end(&path)
326 .await
327 .expect_err("file doesn't exist yet");
328
329 fs.write(&path, b"test").await.expect("success");
330
331 let result = fs.read_to_end(&path).await.expect("success");
332 assert_eq!(b"test", &result[..]);
333 }
334
335 #[cfg(unix)]
336 #[tokio::test]
337 async fn real_fs_write_sets_owner_only_permissions_on_unix() {
338 use std::os::unix::fs::PermissionsExt;
339
340 let dir = tempfile::tempdir().expect("create temp dir");
341 let path = dir.path().join("secret.txt");
342 let fs = Fs::real();
343
344 fs.write(&path, b"sensitive").await.expect("write succeeds");
345
346 let mode = std::fs::metadata(&path)
347 .expect("metadata")
348 .permissions()
349 .mode()
350 & 0o777; assert_eq!(mode, 0o600, "file should be owner read/write only");
352 }
353}