dts_core/
ser.rs

1//! This module provides a `Serializer` which supports serializing values into various output
2//! encodings.
3
4use crate::{key::flatten_keys, value::ValueExt, Encoding, Error, Result};
5use serde_json::Value;
6
7/// Options for the `Serializer`. The options are context specific and may only be honored when
8/// serializing into a certain `Encoding`.
9#[derive(Debug, Default, Clone, PartialEq)]
10pub struct SerializeOptions {
11    /// Emit output data in a compact format. This will disable pretty printing for encodings that
12    /// support it.
13    pub compact: bool,
14    /// Append a trailing newline to the serialized data.
15    pub newline: bool,
16    /// When the input is an array of objects and the output encoding is CSV, the field names of
17    /// the first object will be used as CSV headers. Field values of all following objects will
18    /// be matched to the right CSV column based on their key. Missing fields produce empty columns
19    /// while excess fields are ignored.
20    pub keys_as_csv_headers: bool,
21    /// Optional custom delimiter for CSV output.
22    pub csv_delimiter: Option<u8>,
23    /// Optional seprator to join text output with.
24    pub text_join_separator: Option<String>,
25}
26
27impl SerializeOptions {
28    /// Creates new `SerializeOptions`.
29    pub fn new() -> Self {
30        Self::default()
31    }
32}
33
34/// A `SerializerBuilder` can be used to build a `Serializer` with certain
35/// `SerializeOptions`.
36///
37/// ## Example
38///
39/// ```
40/// use dts_core::{ser::SerializerBuilder, Encoding};
41///
42/// let writer = std::io::stdout();
43/// let mut serializer = SerializerBuilder::new()
44///     .newline(true)
45///     .build(writer);
46/// ```
47#[derive(Debug, Default, Clone)]
48pub struct SerializerBuilder {
49    opts: SerializeOptions,
50}
51
52impl SerializerBuilder {
53    /// Creates a new `SerializerBuilder`.
54    pub fn new() -> Self {
55        Self::default()
56    }
57
58    /// Emit output data in a compact format. This will disable pretty printing for encodings that
59    /// support it.
60    pub fn compact(&mut self, yes: bool) -> &mut Self {
61        self.opts.compact = yes;
62        self
63    }
64
65    /// Append a trailing newline to the serialized data.
66    pub fn newline(&mut self, yes: bool) -> &mut Self {
67        self.opts.newline = yes;
68        self
69    }
70
71    /// When the input is an array of objects and the output encoding is CSV, the field names of
72    /// the first object will be used as CSV headers. Field values of all following objects will
73    /// be matched to the right CSV column based on their key. Missing fields produce empty columns
74    /// while excess fields are ignored.
75    pub fn keys_as_csv_headers(&mut self, yes: bool) -> &mut Self {
76        self.opts.keys_as_csv_headers = yes;
77        self
78    }
79
80    /// Sets a custom CSV delimiter.
81    pub fn csv_delimiter(&mut self, delim: u8) -> &mut Self {
82        self.opts.csv_delimiter = Some(delim);
83        self
84    }
85
86    /// Sets a custom separator to join text output with.
87    pub fn text_join_separator<S>(&mut self, sep: S) -> &mut Self
88    where
89        S: AsRef<str>,
90    {
91        self.opts.text_join_separator = Some(sep.as_ref().to_owned());
92        self
93    }
94
95    /// Builds the `Serializer` for the given writer.
96    pub fn build<W>(&self, writer: W) -> Serializer<W>
97    where
98        W: std::io::Write,
99    {
100        Serializer::with_options(writer, self.opts.clone())
101    }
102}
103
104/// A `Serializer` can serialize a `Value` into an encoded byte stream.
105pub struct Serializer<W> {
106    writer: W,
107    opts: SerializeOptions,
108}
109
110impl<W> Serializer<W>
111where
112    W: std::io::Write,
113{
114    /// Creates a new `Serializer` for writer with default options.
115    pub fn new(writer: W) -> Self {
116        Self::with_options(writer, Default::default())
117    }
118
119    /// Creates a new `Serializer` for writer with options.
120    pub fn with_options(writer: W, opts: SerializeOptions) -> Self {
121        Self { writer, opts }
122    }
123
124    /// Serializes the given `Value` and writes the encoded data to the writer.
125    ///
126    /// ## Example
127    ///
128    /// ```
129    /// use dts_core::{ser::SerializerBuilder, Encoding};
130    /// use serde_json::json;
131    /// # use std::error::Error;
132    /// #
133    /// # fn main() -> Result<(), Box<dyn Error>> {
134    /// let mut buf = Vec::new();
135    /// let mut ser = SerializerBuilder::new().compact(true).build(&mut buf);
136    /// ser.serialize(Encoding::Json, json!(["foo"]))?;
137    ///
138    /// assert_eq!(&buf, r#"["foo"]"#.as_bytes());
139    /// #     Ok(())
140    /// # }
141    /// ```
142    pub fn serialize(&mut self, encoding: Encoding, value: Value) -> Result<()> {
143        match encoding {
144            Encoding::Yaml => self.serialize_yaml(value)?,
145            Encoding::Json => self.serialize_json(value)?,
146            Encoding::Toml => self.serialize_toml(value)?,
147            Encoding::Csv => self.serialize_csv(value)?,
148            Encoding::QueryString => self.serialize_query_string(value)?,
149            Encoding::Xml => self.serialize_xml(value)?,
150            Encoding::Text => self.serialize_text(value)?,
151            Encoding::Gron => self.serialize_gron(value)?,
152            encoding => return Err(Error::UnsupportedEncoding(encoding)),
153        };
154
155        if self.opts.newline {
156            self.writer.write_all(b"\n")?;
157        }
158
159        Ok(())
160    }
161
162    fn serialize_yaml(&mut self, value: Value) -> Result<()> {
163        Ok(serde_yaml::to_writer(&mut self.writer, &value)?)
164    }
165
166    fn serialize_json(&mut self, value: Value) -> Result<()> {
167        if self.opts.compact {
168            serde_json::to_writer(&mut self.writer, &value)?
169        } else {
170            serde_json::to_writer_pretty(&mut self.writer, &value)?
171        }
172
173        Ok(())
174    }
175
176    fn serialize_toml(&mut self, value: Value) -> Result<()> {
177        let value = toml::Value::try_from(value)?;
178
179        let s = if self.opts.compact {
180            toml::ser::to_string(&value)?
181        } else {
182            toml::ser::to_string_pretty(&value)?
183        };
184
185        Ok(self.writer.write_all(s.as_bytes())?)
186    }
187
188    fn serialize_csv(&mut self, value: Value) -> Result<()> {
189        // Because individual row items may produce errors during serialization because they are of
190        // unexpected type, write into a buffer first and only flush out to the writer only if
191        // serialization of all rows succeeded. This avoids writing out partial data.
192        let mut buf = Vec::new();
193        {
194            let mut csv_writer = csv::WriterBuilder::new()
195                .delimiter(self.opts.csv_delimiter.unwrap_or(b','))
196                .from_writer(&mut buf);
197
198            let mut headers: Option<Vec<String>> = None;
199            let empty_value = Value::String("".into());
200
201            for row in value.into_array().into_iter() {
202                let row_data = if !self.opts.keys_as_csv_headers {
203                    row.into_array()
204                        .into_iter()
205                        .map(Value::into_string)
206                        .collect::<Vec<_>>()
207                } else {
208                    let row = row.into_object("csv");
209
210                    // The first row dictates the header fields.
211                    if headers.is_none() {
212                        let header_data = row.keys().cloned().collect();
213                        csv_writer.serialize(&header_data)?;
214                        headers = Some(header_data);
215                    }
216
217                    headers
218                        .as_ref()
219                        .unwrap()
220                        .iter()
221                        .map(|header| row.get(header).unwrap_or(&empty_value))
222                        .cloned()
223                        .map(Value::into_string)
224                        .collect::<Vec<_>>()
225                };
226
227                csv_writer.serialize(row_data)?;
228            }
229        }
230
231        Ok(self.writer.write_all(&buf)?)
232    }
233
234    fn serialize_query_string(&mut self, value: Value) -> Result<()> {
235        Ok(serde_qs::to_writer(&value, &mut self.writer)?)
236    }
237
238    fn serialize_xml(&mut self, value: Value) -> Result<()> {
239        Ok(serde_xml_rs::to_writer(&mut self.writer, &value)?)
240    }
241
242    fn serialize_text(&mut self, value: Value) -> Result<()> {
243        let sep = self
244            .opts
245            .text_join_separator
246            .clone()
247            .unwrap_or_else(|| String::from('\n'));
248
249        let text = value
250            .into_array()
251            .into_iter()
252            .map(Value::into_string)
253            .collect::<Vec<String>>()
254            .join(&sep);
255
256        Ok(self.writer.write_all(text.as_bytes())?)
257    }
258
259    fn serialize_gron(&mut self, value: Value) -> Result<()> {
260        let output = flatten_keys(value, "json")
261            .as_object()
262            .unwrap()
263            .into_iter()
264            .map(|(k, v)| format!("{} = {};\n", k, v))
265            .collect::<String>();
266
267        Ok(self.writer.write_all(output.as_bytes())?)
268    }
269}
270
271#[cfg(test)]
272mod test {
273    use super::*;
274    use pretty_assertions::assert_eq;
275    use serde_json::json;
276    use std::str;
277
278    #[track_caller]
279    fn assert_serializes_to(encoding: Encoding, value: Value, expected: &str) {
280        assert_builder_serializes_to(&mut SerializerBuilder::new(), encoding, value, expected)
281    }
282
283    #[track_caller]
284    fn assert_builder_serializes_to(
285        builder: &mut SerializerBuilder,
286        encoding: Encoding,
287        value: Value,
288        expected: &str,
289    ) {
290        let mut buf = Vec::new();
291        let mut ser = builder.build(&mut buf);
292
293        ser.serialize(encoding, value).unwrap();
294        assert_eq!(str::from_utf8(&buf).unwrap(), expected);
295    }
296
297    #[test]
298    fn test_serialize_json() {
299        assert_builder_serializes_to(
300            &mut SerializerBuilder::new().compact(true),
301            Encoding::Json,
302            json!(["one", "two"]),
303            "[\"one\",\"two\"]",
304        );
305        assert_serializes_to(
306            Encoding::Json,
307            json!(["one", "two"]),
308            "[\n  \"one\",\n  \"two\"\n]",
309        );
310    }
311
312    #[test]
313    fn test_serialize_csv() {
314        assert_serializes_to(
315            Encoding::Csv,
316            json!([["one", "two"], ["three", "four"]]),
317            "one,two\nthree,four\n",
318        );
319        assert_builder_serializes_to(
320            &mut SerializerBuilder::new().keys_as_csv_headers(true),
321            Encoding::Csv,
322            json!([
323                {"one": "val1", "two": "val2"},
324                {"one": "val3", "three": "val4"},
325                {"two": "val5"}
326            ]),
327            "one,two\nval1,val2\nval3,\n,val5\n",
328        );
329        assert_builder_serializes_to(
330            &mut SerializerBuilder::new().keys_as_csv_headers(true),
331            Encoding::Csv,
332            json!({"one": "val1", "two": "val2"}),
333            "one,two\nval1,val2\n",
334        );
335        assert_serializes_to(Encoding::Csv, json!("non-array"), "non-array\n");
336        assert_serializes_to(
337            Encoding::Csv,
338            json!([{"non-array": "row"}]),
339            "\"{\"\"non-array\"\":\"\"row\"\"}\"\n",
340        );
341        assert_builder_serializes_to(
342            &mut SerializerBuilder::new().keys_as_csv_headers(true),
343            Encoding::Csv,
344            json!([["non-object-row"]]),
345            "csv\n\"[\"\"non-object-row\"\"]\"\n",
346        );
347    }
348
349    #[test]
350    fn test_serialize_text() {
351        assert_serializes_to(Encoding::Text, json!(["one", "two"]), "one\ntwo");
352        assert_serializes_to(
353            Encoding::Text,
354            json!([{"foo": "bar"}, "baz"]),
355            "{\"foo\":\"bar\"}\nbaz",
356        );
357        assert_serializes_to(Encoding::Text, json!({"foo": "bar"}), "{\"foo\":\"bar\"}");
358    }
359}