altrios_core/traits/
serde_api.rs

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