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 =
92            url::Url::parse(url.as_ref()).map_err(|err| Error::SerdeError(format!("{err}")))?;
93        let format = url
94            .path_segments()
95            .and_then(|segments| segments.last())
96            .and_then(|filename| Path::new(filename).extension())
97            .and_then(OsStr::to_str)
98            .with_context(|| "Could not parse file format from URL: {url:?}")
99            .map_err(|err| Error::SerdeError(format!("{err}")))?;
100        let mut response = ureq::get(url.as_ref())
101            .call()
102            .map_err(|err| Error::SerdeError(format!("{err}")))?
103            .into_reader();
104        Self::from_reader(&mut response, format, skip_init)
105    }
106
107    /// Write (serialize) an object to a file.
108    /// Supported file extensions are listed in [`ACCEPTED_BYTE_FORMATS`](`SerdeAPI::ACCEPTED_BYTE_FORMATS`).
109    /// Creates a new file if it does not already exist, otherwise truncates the existing file.
110    ///
111    /// # Arguments
112    ///
113    /// * `filepath` - The filepath at which to write the object
114    ///
115    fn to_file<P: AsRef<Path>>(&self, filepath: P) -> Result<(), Error> {
116        let filepath = filepath.as_ref();
117        let extension = filepath
118            .extension()
119            .and_then(OsStr::to_str)
120            .ok_or_else(|| {
121                Error::SerdeError(format!("File extension could not be parsed: {filepath:?}"))
122            })?;
123        self.to_writer(
124            File::create(filepath).map_err(|err| Error::SerdeError(format!("{err}")))?,
125            extension,
126        )
127    }
128
129    /// Read (deserialize) an object from a file.
130    /// Supported file extensions are listed in [`ACCEPTED_BYTE_FORMATS`](`SerdeAPI::ACCEPTED_BYTE_FORMATS`).
131    ///
132    /// # Arguments:
133    ///
134    /// * `filepath`: The filepath from which to read the object
135    ///
136    fn from_file<P: AsRef<Path>>(filepath: P, skip_init: bool) -> Result<Self, Error> {
137        let filepath = filepath.as_ref();
138        let extension = filepath
139            .extension()
140            .and_then(OsStr::to_str)
141            .ok_or_else(|| {
142                Error::SerdeError(format!("File extension could not be parsed: {filepath:?}"))
143            })?;
144        let mut file = File::open(filepath)
145            .with_context(|| {
146                if !filepath.exists() {
147                    format!("File not found: {filepath:?}")
148                } else {
149                    format!("Could not open file: {filepath:?}")
150                }
151            })
152            .map_err(|err| Error::SerdeError(format!("{err}")))?;
153        Self::from_reader(&mut file, extension, skip_init)
154    }
155
156    /// Write (serialize) an object into anything that implements [`std::io::Write`]
157    ///
158    /// # Arguments:
159    ///
160    /// * `wtr` - The writer into which to write object data
161    /// * `format` - The target format, any of those listed in [`ACCEPTED_BYTE_FORMATS`](`SerdeAPI::ACCEPTED_BYTE_FORMATS`)
162    ///
163    fn to_writer<W: std::io::Write>(&self, mut wtr: W, format: &str) -> Result<(), Error> {
164        match format.trim_start_matches('.').to_lowercase().as_str() {
165            #[cfg(feature = "yaml")]
166            "yaml" | "yml" => serde_yaml::to_writer(wtr, self)
167                .map_err(|err| Error::SerdeError(format!("{err}")))?,
168            #[cfg(feature = "json")]
169            "json" => serde_json::to_writer(wtr, self)
170                .map_err(|err| Error::SerdeError(format!("{err}")))?,
171            #[cfg(feature = "msgpack")]
172            "msgpack" => rmp_serde::encode::write(&mut wtr, self)
173                .map_err(|err| Error::SerdeError(format!("{err}")))?,
174            #[cfg(feature = "toml")]
175            "toml" => {
176                let toml_string = self
177                    .to_toml()
178                    .map_err(|err| Error::SerdeError(format!("{err}")))?;
179                wtr.write_all(toml_string.as_bytes())
180                    .map_err(|err| Error::SerdeError(format!("{err}")))?;
181            }
182            _ => Err(Error::SerdeError(format!(
183                "Unsupported format {format:?}, must be one of {:?}",
184                Self::ACCEPTED_BYTE_FORMATS
185            )))?,
186        }
187        Ok(())
188    }
189
190    /// Deserialize an object from anything that implements [`std::io::Read`]
191    ///
192    /// # Arguments:
193    ///
194    /// * `rdr` - The reader from which to read object data
195    /// * `format` - The source format, any of those listed in [`ACCEPTED_BYTE_FORMATS`](`SerdeAPI::ACCEPTED_BYTE_FORMATS`)
196    ///
197    fn from_reader<R: std::io::Read>(
198        rdr: &mut R,
199        format: &str,
200        skip_init: bool,
201    ) -> Result<Self, Error> {
202        let mut deserialized: Self =
203            match format.trim_start_matches('.').to_lowercase().as_str() {
204                #[cfg(feature = "yaml")]
205                "yaml" | "yml" => serde_yaml::from_reader(rdr)
206                    .map_err(|err| Error::SerdeError(format!("{err}")))?,
207                #[cfg(feature = "json")]
208                "json" => serde_json::from_reader(rdr)
209                    .map_err(|err| Error::SerdeError(format!("{err}")))?,
210                #[cfg(feature = "msgpack")]
211                "msgpack" => rmp_serde::decode::from_read(rdr)
212                    .map_err(|err| Error::SerdeError(format!("{err}")))?,
213                #[cfg(feature = "toml")]
214                "toml" => {
215                    let mut buf = String::new();
216                    rdr.read_to_string(&mut buf)
217                        .map_err(|err| Error::SerdeError(format!("{err}")))?;
218                    Self::from_toml(buf, skip_init)
219                        .map_err(|err| Error::SerdeError(format!("{err}")))?
220                }
221                _ => Err(Error::SerdeError(format!(
222                    "Unsupported format {format:?}, must be one of {:?}",
223                    Self::ACCEPTED_BYTE_FORMATS,
224                )))?,
225            };
226        if !skip_init {
227            deserialized.init()?;
228        }
229        Ok(deserialized)
230    }
231
232    /// Write (serialize) an object into a string
233    ///
234    /// # Arguments:
235    ///
236    /// * `format` - The target format, any of those listed in [`ACCEPTED_STR_FORMATS`](`SerdeAPI::ACCEPTED_STR_FORMATS`)
237    ///
238    fn to_str(&self, format: &str) -> anyhow::Result<String> {
239        match format.trim_start_matches('.').to_lowercase().as_str() {
240            #[cfg(feature = "yaml")]
241            "yaml" | "yml" => self.to_yaml(),
242            #[cfg(feature = "json")]
243            "json" => self.to_json(),
244            #[cfg(feature = "toml")]
245            "toml" => self.to_toml(),
246            _ => bail!(
247                "Unsupported format {format:?}, must be one of {:?}",
248                Self::ACCEPTED_STR_FORMATS
249            ),
250        }
251    }
252
253    /// Read (deserialize) an object from a string
254    ///
255    /// # Arguments:
256    ///
257    /// * `contents` - The string containing the object data
258    /// * `format` - The source format, any of those listed in [`ACCEPTED_STR_FORMATS`](`SerdeAPI::ACCEPTED_STR_FORMATS`)
259    ///
260    fn from_str<S: AsRef<str>>(contents: S, format: &str, skip_init: bool) -> anyhow::Result<Self> {
261        Ok(
262            match format.trim_start_matches('.').to_lowercase().as_str() {
263                #[cfg(feature = "yaml")]
264                "yaml" | "yml" => Self::from_yaml(contents, skip_init)?,
265                #[cfg(feature = "json")]
266                "json" => Self::from_json(contents, skip_init)?,
267                #[cfg(feature = "toml")]
268                "toml" => Self::from_toml(contents, skip_init)?,
269                _ => bail!(
270                    "Unsupported format {format:?}, must be one of {:?}",
271                    Self::ACCEPTED_STR_FORMATS
272                ),
273            },
274        )
275    }
276
277    /// Write (serialize) an object to a JSON string
278    #[cfg(feature = "json")]
279    fn to_json(&self) -> anyhow::Result<String> {
280        Ok(serde_json::to_string(&self)?)
281    }
282
283    /// Read (deserialize) an object from a JSON string
284    ///
285    /// # Arguments
286    ///
287    /// * `json_str` - JSON-formatted string to deserialize from
288    ///
289    #[cfg(feature = "json")]
290    fn from_json<S: AsRef<str>>(json_str: S, skip_init: bool) -> anyhow::Result<Self> {
291        let mut json_de: Self = serde_json::from_str(json_str.as_ref())?;
292        if !skip_init {
293            json_de.init()?;
294        }
295        Ok(json_de)
296    }
297
298    /// Write (serialize) an object to a message pack
299    #[cfg(feature = "msgpack")]
300    fn to_msg_pack(&self) -> anyhow::Result<Vec<u8>> {
301        Ok(rmp_serde::encode::to_vec_named(&self)?)
302    }
303
304    /// Read (deserialize) an object from a message pack
305    ///
306    /// # Arguments
307    ///
308    /// * `msg_pack` - message pack object
309    ///
310    #[cfg(feature = "msgpack")]
311    fn from_msg_pack(msg_pack: &[u8], skip_init: bool) -> anyhow::Result<Self> {
312        let mut msg_pack_de: Self = rmp_serde::decode::from_slice(msg_pack)?;
313        if !skip_init {
314            msg_pack_de.init()?;
315        }
316        Ok(msg_pack_de)
317    }
318
319    /// Write (serialize) an object to a TOML string
320    #[cfg(feature = "toml")]
321    fn to_toml(&self) -> anyhow::Result<String> {
322        Ok(toml::to_string(&self)?)
323    }
324
325    /// Read (deserialize) an object from a TOML string
326    ///
327    /// # Arguments
328    ///
329    /// * `toml_str` - TOML-formatted string to deserialize from
330    ///
331    #[cfg(feature = "toml")]
332    fn from_toml<S: AsRef<str>>(toml_str: S, skip_init: bool) -> anyhow::Result<Self> {
333        let mut toml_de: Self = toml::from_str(toml_str.as_ref())?;
334        if !skip_init {
335            toml_de.init()?;
336        }
337        Ok(toml_de)
338    }
339
340    /// Write (serialize) an object to a YAML string
341    #[cfg(feature = "yaml")]
342    fn to_yaml(&self) -> anyhow::Result<String> {
343        Ok(serde_yaml::to_string(&self)?)
344    }
345
346    /// Read (deserialize) an object from a YAML string
347    ///
348    /// # Arguments
349    ///
350    /// * `yaml_str` - YAML-formatted string to deserialize from
351    ///
352    #[cfg(feature = "yaml")]
353    fn from_yaml<S: AsRef<str>>(yaml_str: S, skip_init: bool) -> anyhow::Result<Self> {
354        let mut yaml_de: Self = serde_yaml::from_str(yaml_str.as_ref())?;
355        if !skip_init {
356            yaml_de.init()?;
357        }
358        Ok(yaml_de)
359    }
360}
361
362impl<T: SerdeAPI> SerdeAPI for Vec<T> {}
363impl<T: Init> Init for Vec<T> {
364    fn init(&mut self) -> Result<(), Error> {
365        for val in self {
366            val.init()?
367        }
368        Ok(())
369    }
370}