fastsim_core/traits/
serde_api.rs

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