Skip to main content

optative_process_pool/
resource.rs

1use std::io::Write;
2use tempfile::NamedTempFile;
3
4#[derive(Clone, Debug, PartialEq)]
5#[non_exhaustive]
6pub enum Resource {
7    String(String),
8    File { content: String },
9}
10
11impl From<&str> for Resource {
12    fn from(s: &str) -> Self {
13        Resource::String(s.to_string())
14    }
15}
16
17impl From<String> for Resource {
18    fn from(s: String) -> Self {
19        Resource::String(s)
20    }
21}
22
23pub(crate) struct ResolvedResource {
24    pub value: String,
25    pub handle: Option<NamedTempFile>,
26}
27
28impl Resource {
29    pub(crate) fn resolve(&self) -> Result<ResolvedResource, std::io::Error> {
30        match self {
31            Resource::String(s) => Ok(ResolvedResource {
32                value: s.clone(),
33                handle: None,
34            }),
35            Resource::File { content } => {
36                let mut file = NamedTempFile::new()?;
37                file.write_all(content.as_bytes())?;
38                file.flush()?;
39                let path = file.path().to_string_lossy().into_owned();
40                Ok(ResolvedResource {
41                    value: path,
42                    handle: Some(file),
43                })
44            }
45        }
46    }
47}
48
49#[cfg(test)]
50mod tests {
51    use super::*;
52
53    #[test]
54    fn from_str_creates_string_variant() {
55        let r: Resource = "--verbose".into();
56        assert_eq!(r, Resource::String("--verbose".into()));
57    }
58
59    #[test]
60    fn from_owned_string_creates_string_variant() {
61        let r: Resource = String::from("hello").into();
62        assert_eq!(r, Resource::String("hello".into()));
63    }
64
65    #[test]
66    fn string_resolves_to_itself() {
67        let r = Resource::String("value".into());
68        let resolved = r.resolve().unwrap();
69        assert_eq!(resolved.value, "value");
70        assert!(resolved.handle.is_none());
71    }
72
73    #[test]
74    fn file_resolves_to_a_readable_path() {
75        let r = Resource::File {
76            content: "hello from file".into(),
77        };
78        let resolved = r.resolve().unwrap();
79        assert!(
80            std::path::Path::new(&resolved.value).exists(),
81            "resolved path should exist on disk"
82        );
83        assert_eq!(
84            std::fs::read_to_string(&resolved.value).unwrap(),
85            "hello from file"
86        );
87        assert!(resolved.handle.is_some());
88    }
89
90    #[test]
91    fn file_is_cleaned_up_when_handle_drops() {
92        let r = Resource::File {
93            content: "ephemeral".into(),
94        };
95        let resolved = r.resolve().unwrap();
96        let path = resolved.value.clone();
97        assert!(std::path::Path::new(&path).exists());
98        drop(resolved.handle);
99        assert!(
100            !std::path::Path::new(&path).exists(),
101            "file should be deleted after handle is dropped"
102        );
103    }
104}