dir_structure/
data_formats.rs

1//! Serde [`ReadFrom`](crate::traits::sync::ReadFrom) and [`WriteTo`](crate::traits::sync::WriteTo) implementations.
2
3macro_rules! data_format_impl {
4    (
5        $(#[$mod_attr:meta])*
6        $mod_name:ident,
7        $(#[$main_ty_attrs:meta])*
8        $main_ty:ident,
9
10        $from_str_impl:expr,
11        $from_str_error:ty,
12
13        $(#[$to_str_ty_attrs:meta])*
14        $to_str_ty:ident,
15        $to_str_impl:expr,
16        $to_writer_impl:expr,
17        $to_str_error:ty,
18
19        $(#[$writer_ty_attrs:meta])*
20        $writer_ty:ident,
21
22        $extension:literal,
23        $text:literal $(,)?
24    ) => {
25        $(#[$mod_attr])*
26        pub mod $mod_name {
27            #![doc = concat!(r##"
28With the `"##, stringify!($mod_name), r##"` feature, this module provides the [`"##, stringify!($main_ty), r##"`] type.
29
30This allows us to read and parse `"##, stringify!($mod_name), r##"` files to some `serde::Deserialize` type,
31and write them back to disk."##
32            )]
33            //!
34            //! # Examples
35            //!
36            #![doc = concat!(r##"## Reading a "##, stringify!($mod_name), r##" file"##)]
37            //!
38            #![cfg_attr(feature = "derive", doc = "```rust")]
39            #![cfg_attr(not(feature = "derive"), doc = "```rust,compile_fail")]
40            //! use std::path::Path;
41            //!
42            //! use dir_structure::traits::sync::DirStructureItem;
43            #![doc = concat!(r##"use dir_structure::data_formats::"##, stringify!($mod_name), "::", stringify!($main_ty), r##";"##)]
44            //!
45            //! #[derive(dir_structure::DirStructure)]
46            //! struct Dir {
47            #![doc = concat!(r##"    #[dir_structure(path = "f"##, $extension, r##"", with_newtype = "##, stringify!($main_ty), r##"<Obj>)]"##)]
48            //!     f: Obj,
49            //! }
50            //!
51            //! #[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
52            //! struct Obj {
53            //!     name: String,
54            //!     age: u32,
55            //! }
56            //!
57            //! fn main() -> Result<(), Box<dyn std::error::Error>> {
58            //!     let d = Path::new("dir");
59            //!     std::fs::create_dir_all(&d)?;
60            #![doc = concat!(r##"    std::fs::write(d.join("f"##, $extension, r##""), "##, $text, r##")?;"##)]
61            //!     let dir = Dir::read(&d)?;
62            //!     assert_eq!(dir.f, Obj { name: "John".to_owned(), age: 30 });
63            //!     # std::fs::remove_dir_all(&d)?;
64            //!     Ok(())
65            //! }
66            //! ```
67            //!
68            #![doc = concat!(r##"## Writing a "##, stringify!($mod_name), r##" file"##)]
69            //!
70            #![cfg_attr(feature = "derive", doc = "```rust")]
71            #![cfg_attr(not(feature = "derive"), doc = "```rust,compile_fail")]
72            //! use std::path::Path;
73            //!
74            //! use dir_structure::traits::sync::DirStructureItem;
75            #![doc = concat!(r##"use dir_structure::data_formats::"##, stringify!($mod_name), "::", stringify!($main_ty), r##";"##)]
76            //!
77            //! #[derive(dir_structure::DirStructure)]
78            //! struct Dir {
79            #![doc = concat!(r##"    #[dir_structure(path = "f"##, $extension, r##"", with_newtype = "##, stringify!($main_ty), r##"<Obj>)]"##)]
80            //!     f: Obj,
81            //! }
82            //!
83            //! #[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
84            //! struct Obj {
85            //!     name: String,
86            //!     age: u32,
87            //! }
88            //!
89            //! fn main() -> Result<(), Box<dyn std::error::Error>> {
90            //!     let d = Path::new("dir");
91            //!     let dir = Dir {
92            //!         f: Obj {
93            //!             name: "John".to_owned(),
94            //!             age: 30,
95            //!         },
96            //!     };
97            //!     dir.write(&d)?;
98            #![doc = concat!(r##"    assert_eq!(std::fs::read_to_string(d.join("f"##, $extension, r##""))?,"##)]
99            #![doc = concat!(r##"        "##, $text)]
100            //!     );
101            //!     # std::fs::remove_dir_all(&d)?;
102            //!     Ok(())
103            //! }
104            //! ```
105
106            use std::fmt;
107            use std::fmt::Formatter;
108            use std::str::FromStr;
109
110            use std::pin::Pin;
111            use std::marker;
112            use std::result::Result as StdResult;
113
114            use crate::traits::sync::FromRefForWriter;
115            use crate::traits::vfs;
116            #[cfg(feature = "async")]
117            use crate::traits::async_vfs::VfsAsync;
118            #[cfg(feature = "async")]
119            use crate::traits::async_vfs::WriteSupportingVfsAsync;
120            #[cfg(feature = "async")]
121            use crate::traits::asy::FromRefForWriterAsync;
122            use crate::traits::sync::NewtypeToInner;
123            use crate::prelude::*;
124            use crate::std_types::FileString;
125            use crate::error::VfsResult;
126            use crate::error::Error;
127            #[cfg(feature = "async")]
128            use crate::traits::vfs::VfsCore;
129            use crate::traits::vfs::PathType;
130
131            $(#[$main_ty_attrs])*
132            #[derive(Debug, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, Hash)]
133            #[serde(transparent)]
134            #[cfg_attr(feature = "assert_eq", derive(assert_eq::AssertEq))]
135            pub struct $main_ty<T>(#[serde(bound = "")] pub T)
136            where
137                T: 'static + serde::Serialize + for<'d> serde::Deserialize<'d>;
138
139            impl<T> FromStr for $main_ty<T>
140            where
141                T: serde::Serialize + for<'d> serde::Deserialize<'d> + 'static,
142            {
143                type Err = $from_str_error;
144
145                fn from_str(s: &str) -> StdResult<Self, Self::Err> {
146                    $from_str_impl(s).map(Self)
147                }
148            }
149
150            $(#[$to_str_ty_attrs])*
151            struct $to_str_ty<'a, T>(&'a T)
152            where
153                T: serde::Serialize + 'a;
154
155            impl<'a, T> $to_str_ty<'a, T>
156            where
157                T: serde::Serialize + 'a
158            {
159                fn to_str(&self) -> StdResult<String, $to_str_error> {
160                    $to_str_impl(&self.0)
161                }
162
163                fn to_writer<W>(&self, writer: &mut W) -> StdResult<(), ToWriterError>
164                where
165                    W: std::io::Write,
166                {
167                    $to_writer_impl(&self.0, writer)
168                }
169            }
170
171            enum ToWriterError {
172                #[allow(clippy::allow_attributes, unused)]
173                Io(std::io::Error),
174                Serde($to_str_error),
175            }
176
177            impl<'a, T> fmt::Display for $to_str_ty<'a, T>
178            where
179                T: serde::Serialize + 'a,
180            {
181                fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
182                    let s = self.to_str().map_err(|_| fmt::Error)?;
183                    write!(f, "{s}")
184                }
185            }
186
187            impl<T> fmt::Display for $main_ty<T>
188            where
189                T: serde::Serialize + for<'d> serde::Deserialize<'d> + 'static,
190            {
191                fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
192                    $to_str_ty(&self.0).fmt(f)
193                }
194            }
195
196            impl<'a, T, Vfs: vfs::Vfs<'a>> ReadFrom<'a, Vfs> for $main_ty<T>
197            where
198                T: serde::Serialize + for<'d> serde::Deserialize<'d> + 'static,
199            {
200                fn read_from(path: &Vfs::Path, vfs: Pin<&'a Vfs>) -> VfsResult<Self, Vfs> {
201                    let contents = FileString::read_from(path, vfs)?.0;
202                    let v = contents
203                        .parse::<$main_ty<T>>()
204                        .map_err(|e| Error::Parse(path.owned(), e.into()))?;
205                    Ok(v)
206                }
207            }
208
209            #[cfg(feature = "async")]
210            #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
211            impl<'a, T, Vfs: VfsAsync + 'static> ReadFromAsync<'a, Vfs> for $main_ty<T>
212            where
213                T: serde::Serialize + for<'d> serde::Deserialize<'d> + 'static,
214            {
215                type Future = Pin<Box<dyn Future<Output = VfsResult<Self, Vfs>> + Send + 'a>>;
216
217                fn read_from_async(path: <<Vfs as VfsCore>::Path as PathType>::OwnedPath, vfs: Pin<&'a Vfs>) -> Self::Future {
218                    Box::pin(async move {
219                        let contents = FileString::read_from_async(path.clone(), vfs).await?.0;
220                        let v = contents
221                            .parse::<$main_ty<T>>()
222                            .map_err(|e| Error::Parse(path.clone(), e.into()))?;
223                        Ok(v)
224                    })
225                }
226            }
227
228            impl<'a, T, Vfs: vfs::WriteSupportingVfs<'a>> WriteTo<'a, Vfs> for $main_ty<T>
229            where
230                T: serde::Serialize + for<'d> serde::Deserialize<'d> + 'static,
231            {
232                fn write_to(&self, path: &Vfs::Path, vfs: Pin<&'a Vfs>) -> VfsResult<(), Vfs> {
233                    Self::from_ref_for_writer(&self.0).write_to(path, vfs)
234                }
235            }
236
237            #[cfg(feature = "async")]
238            #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
239            impl<'a, T, Vfs: WriteSupportingVfsAsync + 'static> WriteToAsync<'a, Vfs> for $main_ty<T>
240            where
241                T: serde::Serialize + for<'d> serde::Deserialize<'d> + Send + Sync + 'static,
242            {
243                type Future = Pin<Box<dyn Future<Output = VfsResult<(), Vfs>> + Send + 'a>>;
244
245                fn write_to_async(self, path: <<Vfs as VfsCore>::Path as PathType>::OwnedPath, vfs: Pin<&'a Vfs>) -> Self::Future {
246                    Box::pin(async move {
247                        let s = $to_str_ty(&self.0).to_str()
248                            .map_err(|e| Error::Serde(path.clone(), e.into()))?;
249                        FileString::new(s).write_to_async(path, vfs).await
250                    })
251                }
252            }
253
254            impl<T> NewtypeToInner for $main_ty<T>
255            where
256                T: serde::Serialize + for<'d> serde::Deserialize<'d> + 'static,
257            {
258                type Inner = T;
259
260                fn into_inner(self) -> Self::Inner {
261                    self.0
262                }
263            }
264
265            impl<'a, 'vfs, T, Vfs: vfs::WriteSupportingVfs<'vfs> + 'vfs> FromRefForWriter<'a, 'vfs, Vfs> for $main_ty<T>
266            where
267                T: serde::Serialize + for<'d> serde::Deserialize<'d> + 'static,
268                'vfs: 'a,
269            {
270                type Inner = T;
271                type Wr = $writer_ty<'a, 'vfs, T, Vfs>;
272
273                fn from_ref_for_writer(value: &'a Self::Inner) -> Self::Wr {
274                    $writer_ty(value, marker::PhantomData)
275                }
276            }
277
278            #[cfg(feature = "async")]
279            #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
280            impl<'a, T, Vfs: WriteSupportingVfsAsync + 'static> FromRefForWriterAsync<'a, Vfs> for $main_ty<T>
281            where
282                T: serde::Serialize + for<'d> serde::Deserialize<'d> + Send + Sync + 'static,
283            {
284                type Inner = T;
285                type Wr = $writer_ty<'a, 'a, T, Vfs>;
286
287                fn from_ref_for_writer_async(value: &'a <Self as FromRefForWriterAsync<'a, Vfs>>::Inner) -> Self::Wr {
288                    $writer_ty(value, marker::PhantomData)
289                }
290            }
291
292            $(#[$writer_ty_attrs])*
293            pub struct $writer_ty<'a, 'vfs, T, Vfs: 'vfs>(&'a T, marker::PhantomData<&'vfs Vfs>)
294            where
295                T: serde::Serialize + 'a,
296                'vfs: 'a;
297
298            impl<'a, 'vfs, T, Vfs: vfs::WriteSupportingVfs<'vfs>> WriteTo<'vfs, Vfs> for $writer_ty<'a, 'vfs, T, Vfs>
299            where
300                T: serde::Serialize + 'a,
301                'vfs: 'a,
302            {
303                fn write_to(&self, path: &Vfs::Path, vfs: Pin<&'vfs Vfs>) -> VfsResult<(), Vfs> {
304                    vfs.create_parent_dir(path)?;
305
306                    $to_str_ty(self.0).to_writer(&mut vfs.open_write(path)?)
307                        .map_err(|e| match e {
308                            ToWriterError::Io(e) => Error::Io(path.owned(), e),
309                            ToWriterError::Serde(e) => Error::Serde(path.owned(), e.into()),
310                        })?;
311
312                    Ok(())
313                }
314            }
315
316            #[cfg(feature = "async")]
317            #[cfg_attr(docsrs, doc(cfg(feature = "async")))]
318            impl<'a, T, Vfs: WriteSupportingVfsAsync + 'static> WriteToAsync<'a, Vfs> for $writer_ty<'a, 'a, T, Vfs>
319            where
320                T: serde::Serialize + Send + Sync + 'a,
321            {
322                type Future = Pin<Box<dyn Future<Output = VfsResult<(), Vfs>> + Send + 'a>> where Self: 'a, Vfs: 'a;
323
324                fn write_to_async(self, path: <<Vfs as VfsCore>::Path as PathType>::OwnedPath, vfs: Pin<&'a Vfs>) -> Self::Future {
325                    Box::pin(async move {
326                        let s = $to_str_ty(self.0).to_str()
327                            .map_err(|e| Error::Serde(path.clone(), e.into()))?;
328                        FileString::new(s).write_to_async(path, vfs).await
329                    })
330                }
331            }
332
333            impl<T> std::ops::Deref for $main_ty<T>
334            where
335                T: serde::Serialize + for<'d> serde::Deserialize<'d> + 'static,
336            {
337                type Target = T;
338
339                fn deref(&self) -> &Self::Target {
340                    &self.0
341                }
342            }
343
344            impl<T> std::ops::DerefMut for $main_ty<T>
345            where
346                T: serde::Serialize + for<'d> serde::Deserialize<'d> + 'static,
347            {
348                fn deref_mut(&mut self) -> &mut Self::Target {
349                    &mut self.0
350                }
351            }
352        }
353    };
354}
355
356data_format_impl!(
357    #[cfg(feature = "json")]
358    #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
359    #[allow(clippy::absolute_paths)]
360    json,
361    /// A wrapper around a type that implements [`serde::Serialize`] and [`serde::Deserialize`],
362    /// thus allowing us to parse and serialize it from / to json when we read / write a
363    /// directory structure.
364    Json,
365    |s| serde_json::from_str(s),
366    serde_json::Error,
367    JsonToStr,
368    |v| serde_json::to_string(&v),
369    |v, w| serde_json::to_writer(w, v).map_err(ToWriterError::Serde),
370    serde_json::Error,
371    /// [`FromRefForWriter`] implementation for [`Json`].
372    JsonRefWr,
373    ".json", r##"r#"{"name":"John","age":30}"#"##,
374);
375
376data_format_impl!(
377    #[cfg(feature = "json")]
378    #[cfg_attr(docsrs, doc(cfg(feature = "json")))]
379    #[allow(clippy::absolute_paths)]
380    json_pretty,
381    /// A wrapper around a type that implements [`serde::Serialize`] and [`serde::Deserialize`],
382    /// thus allowing us to parse and serialize it from / to json when we read / write a
383    /// directory structure.
384    ///
385    /// This is a pretty-printed version of [`Json`][crate::data_formats::json::Json].
386    JsonPretty,
387    |s| serde_json::from_str(s),
388    serde_json::Error,
389    JsonPrettyToStr,
390    |v| serde_json::to_string_pretty(&v),
391    |v, w| serde_json::to_writer_pretty(w, v).map_err(ToWriterError::Serde),
392    serde_json::Error,
393    /// [`FromRefForWriter`] implementation for [`JsonPretty`].
394    JsonPrettyRefWr,
395    ".json", r##"r#"{
396  "name": "John",
397  "age": 30
398}"#"##,
399);
400
401data_format_impl!(
402    #[cfg(feature = "toml")]
403    #[cfg_attr(docsrs, doc(cfg(feature = "toml")))]
404    #[allow(clippy::absolute_paths)]
405    toml,
406    /// A wrapper around a type that implements [`serde::Serialize`] and [`serde::Deserialize`],
407    /// thus allowing us to parse and serialize it from / to toml when we read / write a
408    /// directory structure.
409    Toml,
410    |s| toml::de::from_str(s),
411    toml::de::Error,
412    TomlToStr,
413    |v| toml::ser::to_string(&v),
414    |v, w: &mut dyn std::io::Write| {
415        let s = toml::ser::to_string(&v).map_err(ToWriterError::Serde)?;
416        w.write_all(s.as_bytes()).map_err(ToWriterError::Io)?;
417        Ok(())
418    },
419    toml::ser::Error,
420    /// [`FromRefForWriter`] implementation for [`Toml`].
421    TomlRefWr,
422    ".toml", r##"r#"
423name = "John"
424age = 30
425"#.trim_start()"##,
426);
427
428data_format_impl!(
429    #[cfg(feature = "yaml")]
430    #[cfg_attr(docsrs, doc(cfg(feature = "yaml")))]
431    #[allow(clippy::absolute_paths)]
432    yaml,
433    /// A wrapper around a type that implements [`serde::Serialize`] and [`serde::Deserialize`],
434    /// thus allowing us to parse and serialize it from / to yaml when we read / write a
435    /// directory structure.
436    Yaml,
437    |s| serde_yaml::from_str(s),
438    serde_yaml::Error,
439    YamlToStr,
440    |v| serde_yaml::to_string(&v),
441    |v, w| serde_yaml::to_writer(w, v).map_err(ToWriterError::Serde),
442    serde_yaml::Error,
443    /// [`FromRefForWriter`] implementation for [`Yaml`].
444    YamlRefWr,
445    ".yaml", r##"r#"
446name: John
447age: 30
448"#.trim_start()"##,
449);
450
451data_format_impl!(
452    #[cfg(feature = "ron")]
453    #[cfg_attr(docsrs, doc(cfg(feature = "ron")))]
454    #[allow(clippy::absolute_paths)]
455    ron,
456    /// A wrapper around a type that implements [`serde::Serialize`] and [`serde::Deserialize`],
457    /// thus allowing us to parse and serialize it from / to ron when we read / write a
458    /// directory structure.
459    Ron,
460    |s| ron::de::from_str(s),
461    ron::error::SpannedError,
462    RonToStr,
463    |v| ron::ser::to_string(&v),
464    |v, w| ron::options::Options::default().to_io_writer(w, v).map_err(ToWriterError::Serde),
465    ron::error::Error,
466    /// [`FromRefForWriter`] implementation for [`Ron`].
467    RonRefWr,
468    ".ron", r##"r#"(name:"John",age:30)"#"##,
469);
470
471data_format_impl!(
472    #[cfg(feature = "ron")]
473    #[cfg_attr(docsrs, doc(cfg(feature = "ron")))]
474    #[allow(clippy::absolute_paths)]
475    ron_pretty,
476    /// A wrapper around a type that implements [`serde::Serialize`] and [`serde::Deserialize`],
477    /// thus allowing us to parse and serialize it from / to ron when we read / write a
478    /// directory structure.
479    ///
480    /// This is a pretty-printed version of [`Ron`][crate::data_formats::ron::Ron].
481    RonPretty,
482    |s| ron::de::from_str(s),
483    ron::error::SpannedError,
484    RonToStr,
485    |v| ron::ser::to_string_pretty(&v, ron::ser::PrettyConfig::default()),
486    |v, w| ron::options::Options::default().to_io_writer_pretty(w, v, ron::ser::PrettyConfig::default()).map_err(ToWriterError::Serde),
487    ron::error::Error,
488    /// [`FromRefForWriter`] implementation for [`RonPretty`].
489    RonPrettyRefWr,
490    ".ron", r##"r#"(
491    name: "John",
492    age: 30,
493)"#"##,
494);