gmt_dos_actors_clients_interface/
filing.rs

1//! # Serialization interface
2//!
3//! Interface to serialize and to deserialize `dos-actors` objects.
4
5use std::{
6    env::{self, VarError},
7    fmt::Debug,
8    fs::File,
9    io::{Read, Write},
10    path::{Path, PathBuf},
11};
12
13use serde::{Deserialize, Serialize};
14
15#[derive(Debug, thiserror::Error)]
16pub enum FilingError {
17    #[error("filing error")]
18    IO(#[from] std::io::Error),
19    #[error("can't create file {0:?}")]
20    Create(#[source] std::io::Error, PathBuf),
21    #[error("can't open file {0:?}")]
22    Open(#[source] std::io::Error, PathBuf),
23    #[cfg(not(feature = "serde-pickle"))]
24    #[error("decoder error")]
25    Decoder(#[from] bincode::error::DecodeError),
26    #[cfg(feature = "serde-pickle")]
27    #[error("decoder error")]
28    PickleCodec(#[from] serde_pickle::error::Error),
29    #[cfg(not(feature = "serde-pickle"))]
30    #[error("encoder error")]
31    Encoder(#[from] bincode::error::EncodeError),
32    #[error("builder error: {0}")]
33    Builder(String),
34    #[error("DATA_REPO not set")]
35    DataRepo(#[from] VarError),
36}
37
38pub type Result<T> = std::result::Result<T, FilingError>;
39
40/// Encoding and decoding
41pub trait Codec
42where
43    Self: Sized + serde::ser::Serialize + for<'de> serde::de::Deserialize<'de>,
44{
45    /// Decodes object from [std::io::Read]
46    #[inline]
47    #[cfg(not(feature = "serde-pickle"))]
48    fn decode<R>(reader: &mut R) -> Result<Self>
49    where
50        R: Read,
51    {
52        Ok(bincode::serde::decode_from_std_read(
53            reader,
54            bincode::config::standard(),
55        )?)
56    }
57    #[inline]
58    #[cfg(feature = "serde-pickle")]
59    fn decode<R>(reader: &mut R) -> Result<Self>
60    where
61        R: Read,
62    {
63        Ok(serde_pickle::from_reader(reader, Default::default())?)
64    }
65
66    /// Encodes object to [std::io::Write]
67    #[inline]
68    #[cfg(not(feature = "serde-pickle"))]
69    fn encode<W>(&self, writer: &mut W) -> Result<()>
70    where
71        W: Write,
72    {
73        bincode::serde::encode_into_std_write(self, writer, bincode::config::standard())?;
74        Ok(())
75    }
76    #[inline]
77    #[cfg(feature = "serde-pickle")]
78    fn encode<W>(&self, writer: &mut W) -> Result<()>
79    where
80        W: Write,
81    {
82        serde_pickle::to_writer(writer, self, Default::default())?;
83        Ok(())
84    }
85}
86
87impl<T> Filing for T where
88    T: Sized + Codec + serde::ser::Serialize + for<'de> serde::de::Deserialize<'de>
89{
90}
91
92/// Encoding and decoding to/from [File]
93pub trait Filing: Codec
94where
95    Self: Sized + serde::ser::Serialize + for<'de> serde::de::Deserialize<'de>,
96{
97    /// Decodes object from given path
98    fn from_path<P>(path: P) -> Result<Self>
99    where
100        P: AsRef<Path> + Debug,
101    {
102        log::info!("decoding from {path:?}");
103        let file =
104            File::open(&path).map_err(|e| FilingError::Open(e, path.as_ref().to_path_buf()))?;
105        let mut buffer = std::io::BufReader::new(file);
106        Self::decode(&mut buffer)
107    }
108
109    /// Decodes object from given file
110    /// The file is read from the directory specified by the `DATA_REPO` environment variable.
111    fn from_data_repo(file_name: impl AsRef<Path>) -> Result<Self> {
112        let data_repo = env::var("DATA_REPO")?;
113        let path = Path::new(&data_repo).join(file_name);
114        Self::from_path(path)
115    }
116
117    /// Encodes object from given path
118    fn to_path<P>(&self, path: P) -> Result<()>
119    where
120        P: AsRef<Path> + Debug,
121    {
122        log::info!("encoding to {path:?}");
123        let file =
124            File::create(&path).map_err(|e| FilingError::Create(e, path.as_ref().to_path_buf()))?;
125        let mut buffer = std::io::BufWriter::new(file);
126        self.encode(&mut buffer)?;
127        Ok(())
128    }
129
130    /// Encodes object from given file.
131    /// The file is written to the directory specified by the `DATA_REPO` environment variable.
132    fn to_data_repo(&self, file_name: impl AsRef<Path>) -> Result<()> {
133        let data_repo = env::var("DATA_REPO")?;
134        let path = Path::new(&data_repo).join(file_name);
135        Self::to_path(self, path)
136    }
137
138    /// Decodes object from given path or creates a new object from the builder and encodes the object to the given path
139    fn from_path_or_else<P, F, B>(path: P, builder: F) -> Result<Self>
140    where
141        P: AsRef<Path> + Debug,
142        F: FnOnce() -> B,
143        Self: TryFrom<B>,
144        <Self as TryFrom<B>>::Error: std::fmt::Debug,
145    {
146        Self::from_path(&path).or_else(|e| {
147            log::warn!("{e:?}");
148            let this =
149                Self::try_from(builder()).map_err(|e| FilingError::Builder(format!("{e:?}")))?;
150            this.to_path(path)?;
151            Ok(this)
152        })
153    }
154
155    /// Decodes object from given file or creates a new object from the builder and encodes the object to the given file.
156    /// The file is read from the directory specified by the `DATA_REPO` environment variable.
157    fn from_data_repo_or_else<P, F, B>(file_name: P, builder: F) -> Result<Self>
158    where
159        P: AsRef<Path>,
160        F: FnOnce() -> B,
161        Self: TryFrom<B>,
162        <Self as TryFrom<B>>::Error: std::fmt::Debug,
163    {
164        let data_repo = env::var("DATA_REPO")?;
165        let path = Path::new(&data_repo).join(file_name);
166        Self::from_path_or_else(path, builder)
167    }
168
169    /// Decodes object from given path or creates a new object from [Default] and encodes the object to the given path
170    fn from_path_or_default<P, F, B>(path: P) -> Result<Self>
171    where
172        P: AsRef<Path> + Debug,
173        B: Default,
174        F: FnOnce() -> B,
175        Self: TryFrom<B>,
176        <Self as TryFrom<B>>::Error: std::fmt::Debug,
177    {
178        Self::from_path_or_else(path, Default::default)
179    }
180
181    /// Decodes object from given file or creates a new object from [Default] and encodes the object to the given file.
182    /// The file is read from the directory specified by the `DATA_REPO` environment variable.
183    fn from_data_repo_or_default<P, F, B>(file_name: P) -> Result<Self>
184    where
185        P: AsRef<Path>,
186        B: Default,
187        F: FnOnce() -> B,
188        Self: TryFrom<B>,
189        <Self as TryFrom<B>>::Error: std::fmt::Debug,
190    {
191        Self::from_data_repo_or_else(file_name, Default::default)
192    }
193    /// Loads an object and builder pair from a given path and returns the object
194    /// only if the builder match the current one, or creates a new object from the
195    /// current builder then encodes the new object and builder pair to the given path
196    /// and fin ject.
197    fn from_path_or<P, B>(path: P, current_builder: B) -> Result<Self>
198    where
199        P: AsRef<Path> + Debug,
200        Self: TryFrom<B> + serde::ser::Serialize + for<'de> serde::de::Deserialize<'de>,
201        B: Clone + PartialEq + serde::ser::Serialize + for<'de> serde::de::Deserialize<'de>,
202        <Self as TryFrom<B>>::Error: std::fmt::Debug,
203    {
204        match <ObjectAndBuilder<Self, B> as Filing>::from_path(&path) {
205            Ok(ObjectAndBuilder { object, builder }) if builder == current_builder => Ok(object),
206            _ => {
207                let object = Self::try_from(current_builder.clone())
208                    .map_err(|e| FilingError::Builder(format!("{e:?}")))?;
209                let this = ObjectAndBuilder {
210                    object,
211                    builder: current_builder,
212                };
213                this.to_path(path)?;
214                let ObjectAndBuilder { object, .. } = this;
215                Ok(object)
216            }
217        }
218    }
219    /// Loads an object and builder pair from a given file and returns the object
220    /// only if the builder match the current one, or creates a new object from the
221    /// current builder then encodes the new object and builder pair to the given file
222    /// and fin ject.
223    /// The file is read from the directory specified by the `DATA_REPO` environment variable.
224    fn from_data_repo_or<P, B>(file_name: P, current_builder: B) -> Result<Self>
225    where
226        P: AsRef<Path>,
227        Self: TryFrom<B> + serde::ser::Serialize + for<'de> serde::de::Deserialize<'de>,
228        B: Clone + PartialEq + serde::ser::Serialize + for<'de> serde::de::Deserialize<'de>,
229        <Self as TryFrom<B>>::Error: std::fmt::Debug,
230    {
231        let data_repo = env::var("DATA_REPO")?;
232        let path = Path::new(&data_repo).join(file_name);
233        Self::from_path_or(path, current_builder)
234    }
235}
236
237/// Object and builder pair
238#[derive(Serialize, Deserialize)]
239struct ObjectAndBuilder<T, B>
240where
241    T: TryFrom<B>,
242{
243    object: T,
244    builder: B,
245}
246
247impl<T, B> Codec for ObjectAndBuilder<T, B>
248where
249    T: TryFrom<B> + serde::ser::Serialize + for<'de> serde::de::Deserialize<'de>,
250    B: serde::ser::Serialize + for<'de> serde::de::Deserialize<'de>,
251{
252}