rust_script_ext/
io.rs

1use crate::prelude::{Context, Deserialize, Result, Serialize};
2use std::{
3    borrow::Borrow,
4    io::{Read, Write},
5};
6
7/// Defines a _structured_ format which can be used with [`ReadAs`]/[`WriteAs`].
8///
9/// A format needs to describe (de)serialisation mechanism, along with the input/output types.
10/// Note the use of GATs, this can be leveraged to work with containerised types.
11pub trait Format {
12    /// The resulting type after _deserialisation_.
13    type Output<T>;
14    /// The input for _serialisation_.
15    ///
16    /// Note that this is passed through as a reference to `serialise`.
17    type Input<T>: ?Sized;
18
19    /// Deserialise from `rdr` into `Output`.
20    fn deserialise<T>(rdr: &mut dyn Read) -> Result<Self::Output<T>>
21    where
22        for<'de> T: Deserialize<'de>;
23
24    /// Serialise `val` into `wtr`.
25    fn serialise<T>(wtr: &mut dyn Write, val: &Self::Input<T>) -> Result<()>
26    where
27        T: Serialize;
28}
29
30/// A trait which gives any [`Read`]er the `read_as` function which can be used to read with a
31/// specific format.
32///
33/// Noteable examples of using `read_as` would be to read a file directly as CSV/json/toml.
34///
35/// # Example
36/// ```rust
37/// # use rust_script_ext::prelude::*;
38/// #[derive(Deserialize, Debug, PartialEq)]
39/// struct City {
40///     city: String,
41///     pop: u32,
42/// }
43///
44/// let csv = "city,pop\nBrisbane,100000\nSydney,200000\n";
45///
46/// // read_as on anything that is Read
47/// let x = csv.as_bytes().read_as::<CSV, City>().unwrap();
48///
49/// assert_eq!(
50///     x,
51///     vec![
52///         City {
53///             city: "Brisbane".to_string(),
54///             pop: 100_000,
55///         },
56///         City {
57///             city: "Sydney".to_string(),
58///             pop: 200_000,
59///         }
60///     ]
61/// );
62/// ```
63pub trait ReadAs {
64    /// Read the data as if it is structured with format `F`, deserialising into `F::Output<T>`.
65    fn read_as<F, T>(&mut self) -> Result<F::Output<T>>
66    where
67        F: Format,
68        for<'de> T: Deserialize<'de>;
69}
70
71impl<R: Read> ReadAs for R {
72    fn read_as<F, T>(&mut self) -> Result<F::Output<T>>
73    where
74        F: Format,
75        for<'de> T: Deserialize<'de>,
76    {
77        F::deserialise(self)
78    }
79}
80
81/// A trait which can supply `write_as` on a type to serialise it into a specific format and write it into a
82/// [`Write`]r.
83///
84/// Its counterpart [`ReadAs`] works on any reader, `WriteAs` works differently, being available on
85/// any _type_ which matches the _format's input_.
86///
87/// # Example
88/// ```rust
89/// # use rust_script_ext::prelude::*;
90/// #[derive(Serialize)]
91/// struct City {
92///     city: String,
93///     pop: u32,
94/// }
95///
96/// let mut buf = Vec::new();
97///
98/// let sydney = City {
99///     city: "Sydney".to_string(),
100///     pop: 200_000
101/// };
102///
103/// // we serialise as JSON
104/// sydney.write_as(JSON, &mut buf).unwrap();
105///
106/// assert_eq!(buf, r#"{
107///   "city": "Sydney",
108///   "pop": 200000
109/// }"#.as_bytes());
110///
111/// // but we could also easily serialise as TOML
112/// buf.clear();
113/// sydney.write_as(TOML, &mut buf).unwrap();
114///
115/// assert_eq!(buf, r#"city = "Sydney"
116/// pop = 200000
117/// "#.as_bytes());
118/// ```
119pub trait WriteAs<F, T> {
120    /// Serialise this with the format `F` into the `wtr`.
121    fn write_as(&self, fmt: F, wtr: &mut dyn Write) -> Result<()>;
122}
123
124impl<F, T, A> WriteAs<F, T> for A
125where
126    F: Format,
127    T: Serialize,
128    A: Borrow<F::Input<T>>,
129{
130    fn write_as(&self, _: F, wtr: &mut dyn Write) -> Result<()> {
131        F::serialise(wtr, self.borrow())
132    }
133}
134
135/// A CSV [`Format`].
136///
137/// - The _output_ is `Vec<T>` (`T: Deserialize`).
138/// - The _input_ is `[T]` (`T: Serialize`).
139pub struct CSV;
140impl Format for CSV {
141    type Output<T> = Vec<T>;
142    type Input<T> = [T];
143
144    fn deserialise<T>(rdr: &mut dyn Read) -> Result<Self::Output<T>>
145    where
146        for<'de> T: Deserialize<'de>,
147    {
148        let mut v = Vec::new();
149        for r in ::csv::Reader::from_reader(rdr).into_deserialize() {
150            let r: T = r?;
151            v.push(r);
152        }
153
154        Ok(v)
155    }
156
157    fn serialise<T>(wtr: &mut dyn Write, val: &[T]) -> Result<()>
158    where
159        T: Serialize,
160    {
161        let mut csv = ::csv::Writer::from_writer(wtr);
162        for x in val {
163            csv.serialize(x)?;
164        }
165
166        Ok(())
167    }
168}
169
170/// A json [`Format`].
171///
172/// - The _output_ is `T` (`T: Deserialize`).
173/// - The _input_ is `T` (`T: Serialize`).
174pub struct JSON;
175impl Format for JSON {
176    type Output<T> = T;
177    type Input<T> = T;
178
179    fn deserialise<T>(rdr: &mut dyn Read) -> Result<Self::Output<T>>
180    where
181        for<'de> T: Deserialize<'de>,
182    {
183        serde_json::from_reader(rdr).with_context(|| {
184            format!(
185                "failed to deserialise {} from JSON",
186                std::any::type_name::<T>()
187            )
188        })
189    }
190
191    fn serialise<T>(wtr: &mut dyn Write, val: &T) -> Result<()>
192    where
193        T: Serialize,
194    {
195        serde_json::to_writer_pretty(wtr, &val)
196            .with_context(|| format!("failed to serialise {} as JSON", std::any::type_name::<T>()))
197    }
198}
199
200/// A toml [`Format`].
201///
202/// - The _output_ is `T` (`T: Deserialize`).
203/// - The _input_ is `T` (`T: Serialize`).
204pub struct TOML;
205impl Format for TOML {
206    type Output<T> = T;
207    type Input<T> = T;
208
209    fn deserialise<T>(rdr: &mut dyn Read) -> Result<Self::Output<T>>
210    where
211        for<'de> T: Deserialize<'de>,
212    {
213        let mut s = String::new();
214        rdr.read_to_string(&mut s)
215            .context("failed reading TOML data to string")?;
216
217        toml::from_str(&s).with_context(|| {
218            format!(
219                "failed to deserialise {} from TOML",
220                std::any::type_name::<T>()
221            )
222        })
223    }
224
225    fn serialise<T>(wtr: &mut dyn Write, val: &T) -> Result<()>
226    where
227        T: Serialize,
228    {
229        let s = toml::to_string_pretty(val).with_context(|| {
230            format!("failed to serialise {} as TOML", std::any::type_name::<T>())
231        })?;
232
233        wtr.write_all(s.as_bytes())
234            .context("failed to write TOML to writer")
235    }
236}
237
238#[cfg(test)]
239mod tests {
240    use super::*;
241
242    #[derive(Serialize, Deserialize, Debug, PartialEq)]
243    struct City {
244        city: String,
245        pop: u32,
246    }
247
248    #[test]
249    fn structured_api_csv() {
250        let csv = "city,pop\nBrisbane,100000\nSydney,200000\n";
251
252        let x = csv.as_bytes().read_as::<CSV, City>().unwrap();
253
254        assert_eq!(
255            x,
256            vec![
257                City {
258                    city: "Brisbane".to_string(),
259                    pop: 100_000,
260                },
261                City {
262                    city: "Sydney".to_string(),
263                    pop: 200_000,
264                }
265            ]
266        );
267
268        let mut buf = Vec::new();
269        x.as_slice().write_as(CSV, &mut buf).unwrap();
270
271        assert_eq!(buf, csv.as_bytes());
272
273        // check that vec can work without as_slice
274        let mut buf = Vec::new();
275        x.write_as(CSV, &mut buf).unwrap();
276
277        assert_eq!(buf, csv.as_bytes());
278    }
279
280    #[test]
281    fn structured_api_json() {
282        let data = serde_json::json!({
283            "city": "Brisbane",
284            "pop": 100_000
285        });
286
287        let x = data.to_string().as_bytes().read_as::<JSON, City>().unwrap();
288
289        assert_eq!(
290            x,
291            City {
292                city: "Brisbane".to_string(),
293                pop: 100_000,
294            }
295        );
296
297        let mut buf = Vec::new();
298        x.write_as(JSON, &mut buf).unwrap();
299
300        assert_eq!(
301            buf,
302            r#"{
303  "city": "Brisbane",
304  "pop": 100000
305}"#
306            .as_bytes()
307        );
308    }
309}