Skip to main content

fastsim_core/traits/
serde_api.rs

1use super::*;
2#[cfg(feature = "resources")]
3use include_dir::{include_dir, Dir};
4
5pub trait SerdeAPI: Serialize + for<'a> Deserialize<'a> + Init {
6    const ACCEPTED_BYTE_FORMATS: &'static [&'static str] = &[
7        #[cfg(feature = "yaml")]
8        "yaml",
9        #[cfg(feature = "json")]
10        "json",
11        #[cfg(feature = "msgpack")]
12        "msgpack",
13        #[cfg(feature = "toml")]
14        "toml",
15    ];
16    const ACCEPTED_STR_FORMATS: &'static [&'static str] = &[
17        #[cfg(feature = "yaml")]
18        "yaml",
19        #[cfg(feature = "json")]
20        "json",
21        #[cfg(feature = "toml")]
22        "toml",
23    ];
24    #[cfg(feature = "resources")]
25    const RESOURCES_DIR: &'static Dir<'_> = &include_dir!("$CARGO_MANIFEST_DIR/resources");
26    #[cfg(feature = "resources")]
27    const RESOURCES_SUBDIR: &'static str = "";
28
29    /// Read (deserialize) an object from a resource file packaged with the `fastsim-core` crate
30    ///
31    /// # Arguments:
32    ///
33    /// * `filepath` - Filepath, relative to the top of the `resources` folder (excluding any relevant prefix), from which to read the object
34    #[cfg(feature = "resources")]
35    fn from_resource<P: AsRef<Path>>(filepath: P, skip_init: bool) -> Result<Self, Error> {
36        let filepath = Path::new(Self::RESOURCES_SUBDIR).join(filepath);
37        let extension = filepath
38            .extension()
39            .and_then(OsStr::to_str)
40            .ok_or_else(|| {
41                Error::SerdeError(format!("File extension could not be parsed: {filepath:?}"))
42            })?;
43        let file = Self::RESOURCES_DIR.get_file(&filepath).ok_or_else(|| {
44            Error::SerdeError(format!("File not found in resources: {filepath:?}"))
45        })?;
46        Self::from_reader(&mut file.contents(), extension, skip_init)
47    }
48
49    /// List the available resources in the resources directory
50    ///
51    /// RETURNS: a vector of strings for resources that can be loaded
52    #[cfg(feature = "resources")]
53    fn list_resources() -> Result<Vec<PathBuf>, Error> {
54        // Recursive function to walk the directory
55        fn collect_paths(dir: &Dir, paths: &mut Vec<PathBuf>) {
56            for entry in dir.entries() {
57                match entry {
58                    include_dir::DirEntry::Dir(subdir) => {
59                        // Recursively process subdirectory
60                        collect_paths(subdir, paths);
61                    }
62                    include_dir::DirEntry::File(file) => {
63                        // Add file path
64                        paths.push(file.path().to_path_buf());
65                    }
66                }
67            }
68        }
69
70        let mut paths = Vec::new();
71        if let Some(resources_subdir) = Self::RESOURCES_DIR.get_dir(Self::RESOURCES_SUBDIR) {
72            collect_paths(resources_subdir, &mut paths);
73            for p in paths.iter_mut() {
74                *p = p
75                    .strip_prefix(Self::RESOURCES_SUBDIR)
76                    .map_err(|err| Error::SerdeError(format!("{err}")))?
77                    .to_path_buf();
78            }
79            paths.sort();
80        }
81        Ok(paths)
82    }
83
84    /// Instantiates an object from a url.  Accepts yaml and json file types  
85    /// # Arguments  
86    /// - url: URL (either as a string or url type) to object  
87    ///
88    /// Note: The URL needs to be a URL pointing directly to a file, for example
89    /// a raw github URL.
90    #[cfg(feature = "web")]
91    fn from_url<S: AsRef<str>>(url: S, skip_init: bool) -> Result<Self, Error> {
92        let url =
93            url::Url::parse(url.as_ref()).map_err(|err| Error::SerdeError(format!("{err}")))?;
94        let format = url
95            .path_segments()
96            .and_then(|segments| segments.last())
97            .and_then(|filename| Path::new(filename).extension())
98            .and_then(OsStr::to_str)
99            .with_context(|| "Could not parse file format from URL: {url:?}")
100            .map_err(|err| Error::SerdeError(format!("{err}")))?;
101        let mut response = ureq::get(url.as_ref())
102            .call()
103            .map_err(|err| Error::SerdeError(format!("{err}")))?
104            .into_reader();
105        Self::from_reader(&mut response, format, skip_init)
106    }
107
108    /// Write (serialize) an object to a file.
109    /// Supported file extensions are listed in [`ACCEPTED_BYTE_FORMATS`](`SerdeAPI::ACCEPTED_BYTE_FORMATS`).
110    /// Creates a new file if it does not already exist, otherwise truncates the existing file.
111    ///
112    /// # Arguments
113    ///
114    /// * `filepath` - The filepath at which to write the object
115    ///
116    fn to_file<P: AsRef<Path>>(&self, filepath: P) -> Result<(), Error> {
117        let filepath = filepath.as_ref();
118        let extension = filepath
119            .extension()
120            .and_then(OsStr::to_str)
121            .ok_or_else(|| {
122                Error::SerdeError(format!("File extension could not be parsed: {filepath:?}"))
123            })?;
124        self.to_writer(
125            File::create(filepath).map_err(|err| Error::SerdeError(format!("{err}")))?,
126            extension,
127        )
128    }
129
130    /// Read (deserialize) an object from a file.
131    /// Supported file extensions are listed in [`ACCEPTED_BYTE_FORMATS`](`SerdeAPI::ACCEPTED_BYTE_FORMATS`).
132    ///
133    /// # Arguments:
134    ///
135    /// * `filepath`: The filepath from which to read the object
136    ///
137    fn from_file<P: AsRef<Path>>(filepath: P, skip_init: bool) -> Result<Self, Error> {
138        let filepath = filepath.as_ref();
139        // Expand a leading `~` so all callers can pass shell-style home-relative paths.
140        let filepath = match filepath.to_str() {
141            Some("~") => home::home_dir().unwrap_or_else(|| filepath.to_path_buf()),
142            Some(path_str) if path_str.starts_with("~/") || path_str.starts_with("~\\") => {
143                match home::home_dir() {
144                    Some(home_path) => home_path.join(&path_str[2..]),
145                    None => filepath.to_path_buf(),
146                }
147            }
148            _ => filepath.to_path_buf(),
149        };
150        let extension = filepath
151            .extension()
152            .and_then(OsStr::to_str)
153            .ok_or_else(|| {
154                Error::SerdeError(format!("File extension could not be parsed: {filepath:?}"))
155            })?;
156        let mut file = File::open(&filepath)
157            .with_context(|| {
158                if !filepath.exists() {
159                    format!("File not found: {filepath:?}")
160                } else {
161                    format!("Could not open file: {filepath:?}")
162                }
163            })
164            .map_err(|err| Error::SerdeError(format!("{err}")))?;
165        Self::from_reader(&mut file, extension, skip_init)
166    }
167
168    /// Write (serialize) an object into anything that implements [`std::io::Write`]
169    ///
170    /// # Arguments:
171    ///
172    /// * `wtr` - The writer into which to write object data
173    /// * `format` - The target format, any of those listed in [`ACCEPTED_BYTE_FORMATS`](`SerdeAPI::ACCEPTED_BYTE_FORMATS`)
174    ///
175    fn to_writer<W: std::io::Write>(&self, mut wtr: W, format: &str) -> Result<(), Error> {
176        match format.trim_start_matches('.').to_lowercase().as_str() {
177            #[cfg(feature = "yaml")]
178            "yaml" | "yml" => serde_yaml::to_writer(wtr, self)
179                .map_err(|err| Error::SerdeError(format!("{err}")))?,
180            #[cfg(feature = "json")]
181            "json" => serde_json::to_writer(wtr, self)
182                .map_err(|err| Error::SerdeError(format!("{err}")))?,
183            #[cfg(feature = "msgpack")]
184            "msgpack" => rmp_serde::encode::write(&mut wtr, self)
185                .map_err(|err| Error::SerdeError(format!("{err}")))?,
186            #[cfg(feature = "toml")]
187            "toml" => {
188                let toml_string = self
189                    .to_toml()
190                    .map_err(|err| Error::SerdeError(format!("{err}")))?;
191                wtr.write_all(toml_string.as_bytes())
192                    .map_err(|err| Error::SerdeError(format!("{err}")))?;
193            }
194            _ => Err(Error::SerdeError(format!(
195                "Unsupported format {format:?}, must be one of {:?}",
196                Self::ACCEPTED_BYTE_FORMATS
197            )))?,
198        }
199        Ok(())
200    }
201
202    /// Deserialize an object from anything that implements [`std::io::Read`]
203    ///
204    /// # Arguments:
205    ///
206    /// * `rdr` - The reader from which to read object data
207    /// * `format` - The source format, any of those listed in [`ACCEPTED_BYTE_FORMATS`](`SerdeAPI::ACCEPTED_BYTE_FORMATS`)
208    ///
209    fn from_reader<R: std::io::Read>(
210        rdr: &mut R,
211        format: &str,
212        skip_init: bool,
213    ) -> Result<Self, Error> {
214        let mut deserialized: Self =
215            match format.trim_start_matches('.').to_lowercase().as_str() {
216                #[cfg(feature = "yaml")]
217                "yaml" | "yml" => serde_yaml::from_reader(rdr)
218                    .map_err(|err| Error::SerdeError(format!("{err}")))?,
219                #[cfg(feature = "json")]
220                "json" => serde_json::from_reader(rdr)
221                    .map_err(|err| Error::SerdeError(format!("{err}")))?,
222                #[cfg(feature = "msgpack")]
223                "msgpack" => rmp_serde::decode::from_read(rdr)
224                    .map_err(|err| Error::SerdeError(format!("{err}")))?,
225                #[cfg(feature = "toml")]
226                "toml" => {
227                    let mut buf = String::new();
228                    rdr.read_to_string(&mut buf)
229                        .map_err(|err| Error::SerdeError(format!("{err}")))?;
230                    Self::from_toml(buf, true).map_err(|err| Error::SerdeError(format!("{err}")))?
231                }
232                _ => Err(Error::SerdeError(format!(
233                    "Unsupported format {format:?}, must be one of {:?}",
234                    Self::ACCEPTED_BYTE_FORMATS,
235                )))?,
236            };
237        if !skip_init {
238            deserialized.init()?;
239        }
240        Ok(deserialized)
241    }
242
243    /// Write (serialize) an object into a string
244    ///
245    /// # Arguments:
246    ///
247    /// * `format` - The target format, any of those listed in [`ACCEPTED_STR_FORMATS`](`SerdeAPI::ACCEPTED_STR_FORMATS`)
248    ///
249    fn to_str(&self, format: &str) -> anyhow::Result<String> {
250        match format.trim_start_matches('.').to_lowercase().as_str() {
251            #[cfg(feature = "yaml")]
252            "yaml" | "yml" => self.to_yaml(),
253            #[cfg(feature = "json")]
254            "json" => self.to_json(),
255            #[cfg(feature = "toml")]
256            "toml" => self.to_toml(),
257            _ => bail!(
258                "Unsupported format {format:?}, must be one of {:?}",
259                Self::ACCEPTED_STR_FORMATS
260            ),
261        }
262    }
263
264    /// Read (deserialize) an object from a string
265    ///
266    /// # Arguments:
267    ///
268    /// * `contents` - The string containing the object data
269    /// * `format` - The source format, any of those listed in [`ACCEPTED_STR_FORMATS`](`SerdeAPI::ACCEPTED_STR_FORMATS`)
270    ///
271    fn from_str<S: AsRef<str>>(contents: S, format: &str, skip_init: bool) -> anyhow::Result<Self> {
272        Ok(
273            match format.trim_start_matches('.').to_lowercase().as_str() {
274                #[cfg(feature = "yaml")]
275                "yaml" | "yml" => Self::from_yaml(contents, skip_init)?,
276                #[cfg(feature = "json")]
277                "json" => Self::from_json(contents, skip_init)?,
278                #[cfg(feature = "toml")]
279                "toml" => Self::from_toml(contents, skip_init)?,
280                _ => bail!(
281                    "Unsupported format {format:?}, must be one of {:?}",
282                    Self::ACCEPTED_STR_FORMATS
283                ),
284            },
285        )
286    }
287
288    /// Write (serialize) an object to a JSON string
289    #[cfg(feature = "json")]
290    fn to_json(&self) -> anyhow::Result<String> {
291        Ok(serde_json::to_string(&self)?)
292    }
293
294    /// Read (deserialize) an object from a JSON string
295    ///
296    /// # Arguments
297    ///
298    /// * `json_str` - JSON-formatted string to deserialize from
299    ///
300    #[cfg(feature = "json")]
301    fn from_json<S: AsRef<str>>(json_str: S, skip_init: bool) -> anyhow::Result<Self> {
302        let mut json_de: Self = serde_json::from_str(json_str.as_ref())?;
303        if !skip_init {
304            json_de.init()?;
305        }
306        Ok(json_de)
307    }
308
309    /// Write (serialize) an object to a message pack
310    #[cfg(feature = "msgpack")]
311    fn to_msg_pack(&self) -> anyhow::Result<Vec<u8>> {
312        Ok(rmp_serde::encode::to_vec_named(&self)?)
313    }
314
315    /// Read (deserialize) an object from a message pack
316    ///
317    /// # Arguments
318    ///
319    /// * `msg_pack` - message pack object
320    ///
321    #[cfg(feature = "msgpack")]
322    fn from_msg_pack(msg_pack: &[u8], skip_init: bool) -> anyhow::Result<Self> {
323        let mut msg_pack_de: Self = rmp_serde::decode::from_slice(msg_pack)?;
324        if !skip_init {
325            msg_pack_de.init()?;
326        }
327        Ok(msg_pack_de)
328    }
329
330    /// Write (serialize) an object to a TOML string
331    #[cfg(feature = "toml")]
332    fn to_toml(&self) -> anyhow::Result<String> {
333        Ok(toml::to_string(&self)?)
334    }
335
336    /// Read (deserialize) an object from a TOML string
337    ///
338    /// # Arguments
339    ///
340    /// * `toml_str` - TOML-formatted string to deserialize from
341    ///
342    #[cfg(feature = "toml")]
343    fn from_toml<S: AsRef<str>>(toml_str: S, skip_init: bool) -> anyhow::Result<Self> {
344        let mut toml_de: Self = toml::from_str(toml_str.as_ref())?;
345        if !skip_init {
346            toml_de.init()?;
347        }
348        Ok(toml_de)
349    }
350
351    /// Write (serialize) an object to a YAML string
352    #[cfg(feature = "yaml")]
353    fn to_yaml(&self) -> anyhow::Result<String> {
354        Ok(serde_yaml::to_string(&self)?)
355    }
356
357    /// Read (deserialize) an object from a YAML string
358    ///
359    /// # Arguments
360    ///
361    /// * `yaml_str` - YAML-formatted string to deserialize from
362    ///
363    #[cfg(feature = "yaml")]
364    fn from_yaml<S: AsRef<str>>(yaml_str: S, skip_init: bool) -> anyhow::Result<Self> {
365        let mut yaml_de: Self = serde_yaml::from_str(yaml_str.as_ref())?;
366        if !skip_init {
367            yaml_de.init()?;
368        }
369        Ok(yaml_de)
370    }
371}
372
373impl<T: SerdeAPI> SerdeAPI for Vec<T> {}
374impl<T: Init> Init for Vec<T> {
375    fn init(&mut self) -> Result<(), Error> {
376        for val in self {
377            val.init()?
378        }
379        Ok(())
380    }
381}