singlefile_formats/
lib.rs

1//! This library provides a number of default [`FileFormat`] implementations
2//! for use within [`singlefile`](https://crates.io/crates/singlefile).
3//!
4//! # Features
5//! By default, no features are enabled.
6//!
7//! - `cbor-serde`: Enables the [`Cbor`][crate::cbor_serde::Cbor] file format for use with [`serde`] types.
8//! - `json-serde`: Enables the [`Json`][crate::json_serde::Json] file format for use with [`serde`] types.
9//! - `toml-serde`: Enables the [`Toml`][crate::toml_serde::Toml] file format for use with [`serde`] types.
10//! - `bzip`: Enables the [`BZip2`][crate::bzip::BZip2] compression format. See [`CompressionFormat`] for more info.
11//! - `flate`: Enables the [`Deflate`][crate::flate::Deflate], [`Gz`][crate::flate::Gz],
12//!   and [`ZLib`][crate::flate::ZLib] compression formats. See [`CompressionFormat`] for more info.
13//! - `xz`: Enables the [`Xz`][crate::xz::Xz] compression format. See [`CompressionFormat`] for more info.
14
15#![cfg_attr(docsrs, feature(doc_cfg))]
16#![forbid(unsafe_code)]
17#![warn(
18  future_incompatible,
19  missing_copy_implementations,
20  missing_debug_implementations,
21  missing_docs,
22  unreachable_pub
23)]
24
25pub extern crate singlefile;
26
27use singlefile::FileFormat;
28
29use std::io::{Read, Write};
30
31/// Combines a [`FileFormat`] and a [`CompressionFormat`], making the contents emitted by
32/// the format compressed before writing to disk, and decompressed before parsing.
33#[derive(Debug, Clone, Copy, PartialEq, Eq)]
34pub struct Compressed<C, F> {
35  /// The [`FileFormat`] to be used.
36  pub format: F,
37  /// The [`CompressionFormat`] to be used.
38  pub compression: C,
39  /// The level of compression to use.
40  /// This value may have different meanings for different compression formats.
41  pub level: u32
42}
43
44impl<C, F> Compressed<C, F> {
45  /// Create a new [`Compressed`], given a compression level.
46  #[inline]
47  pub const fn with_level(format: F, compression: C, level: u32) -> Self {
48    Compressed { format, compression, level }
49  }
50}
51
52impl<C, F> Compressed<C, F> where C: CompressionFormatLevels {
53  /// Creates a new [`Compressed`] with the default compression level.
54  #[inline]
55  pub const fn new(format: F, compression: C) -> Self {
56    Compressed::with_level(format, compression, C::COMPRESSION_LEVEL_DEFAULT)
57  }
58
59  /// Creates a new [`Compressed`] with the 'fast' compression level.
60  #[inline]
61  pub const fn new_fast_compression(format: F, compression: C) -> Self {
62    Compressed::with_level(format, compression, C::COMPRESSION_LEVEL_FAST)
63  }
64
65  /// Creates a new [`Compressed`] with the 'best' compression level.
66  #[inline]
67  pub const fn new_best_compression(format: F, compression: C) -> Self {
68    Compressed::with_level(format, compression, C::COMPRESSION_LEVEL_BEST)
69  }
70}
71
72impl<C, F> Default for Compressed<C, F>
73where C: Default + CompressionFormatLevels, F: Default {
74  #[inline]
75  fn default() -> Self {
76    Compressed::new(F::default(), C::default())
77  }
78}
79
80impl<T, C, F> FileFormat<T> for Compressed<C, F>
81where C: CompressionFormat, F: FileFormat<T> {
82  type FormatError = F::FormatError;
83
84  fn from_reader<R: Read>(&self, reader: R) -> Result<T, Self::FormatError> {
85    self.format.from_reader(self.compression.decode_reader(reader))
86  }
87
88  fn to_writer<W: Write>(&self, writer: W, value: &T) -> Result<(), Self::FormatError> {
89    self.format.to_writer(self.compression.encode_writer(writer, self.level), value)
90  }
91}
92
93/// Defines a format for lossless compression of arbitrary data.
94///
95/// In order to use a [`CompressionFormat`], you may consider using the [`Compressed`] struct.
96pub trait CompressionFormat {
97  /// The encoder wrapper type that compresses data sent to the contained writer.
98  type Encoder<W: Write>: Write;
99  /// The decoder wrapper type that decompresses data sent from the contained reader.
100  type Decoder<R: Read>: Read;
101
102  /// Wraps a writer that takes uncompressed data, producing a new writer that outputs compressed data.
103  fn encode_writer<W: Write>(&self, writer: W, level: u32) -> Self::Encoder<W>;
104  /// Wraps a reader that takes compressed data, producing a new reader that outputs uncompressed data.
105  fn decode_reader<R: Read>(&self, reader: R) -> Self::Decoder<R>;
106}
107
108/// Defines compression level presets for a [`CompressionFormat`].
109pub trait CompressionFormatLevels: CompressionFormat {
110  /// The level for no compression.
111  const COMPRESSION_LEVEL_NONE: u32;
112  /// The level for 'fast' compression.
113  const COMPRESSION_LEVEL_FAST: u32;
114  /// The level for 'best' compression.
115  const COMPRESSION_LEVEL_BEST: u32;
116  /// The level for default compression.
117  const COMPRESSION_LEVEL_DEFAULT: u32;
118}
119
120/// Defines a [`FileFormat`] using the CBOR binary data format.
121#[cfg_attr(docsrs, doc(cfg(feature = "cbor-serde")))]
122#[cfg(feature = "cbor-serde")]
123pub mod cbor_serde {
124  pub extern crate ciborium;
125
126  use serde::ser::Serialize;
127  use serde::de::DeserializeOwned;
128  use singlefile::FileFormat;
129  use thiserror::Error;
130
131  use std::io::{Read, Write};
132
133  /// An error that can occur while using [`Cbor`].
134  #[derive(Debug, Error)]
135  pub enum CborError {
136    /// An error occurred while serializing.
137    #[error(transparent)]
138    SerializeError(#[from] ciborium::ser::Error<std::io::Error>),
139    /// An error occurred while deserializing.
140    #[error(transparent)]
141    DeserializeError(#[from] ciborium::de::Error<std::io::Error>)
142  }
143
144  /// A [`FileFormat`] corresponding to the CBOR binary data format.
145  /// Implemented using the [`ciborium`] crate, only compatible with [`serde`] types.
146  #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
147  pub struct Cbor;
148
149  impl<T> FileFormat<T> for Cbor
150  where T: Serialize + DeserializeOwned {
151    type FormatError = CborError;
152
153    fn from_reader<R: Read>(&self, reader: R) -> Result<T, Self::FormatError> {
154      ciborium::de::from_reader(reader).map_err(From::from)
155    }
156
157    fn to_writer<W: Write>(&self, writer: W, value: &T) -> Result<(), Self::FormatError> {
158      ciborium::ser::into_writer(value, writer).map_err(From::from)
159    }
160  }
161
162  /// A shortcut type to a [`Compressed`][crate::Compressed] [`Cbor`].
163  /// Provides a single parameter for compression format.
164  pub type CompressedCbor<C> = crate::Compressed<C, Cbor>;
165}
166
167/// Defines a [`FileFormat`] using the JSON data format.
168#[cfg_attr(docsrs, doc(cfg(feature = "json-serde")))]
169#[cfg(feature = "json-serde")]
170pub mod json_serde {
171  pub extern crate serde_json;
172
173  use serde::ser::Serialize;
174  use serde::de::DeserializeOwned;
175  use singlefile::FileFormat;
176
177  use std::io::{Read, Write};
178
179  /// An error that can occur while using [`Json`].
180  pub type JsonError = serde_json::Error;
181
182  /// A [`FileFormat`] corresponding to the JSON data format.
183  /// Implemented using the [`serde_json`] crate, only compatible with [`serde`] types.
184  ///
185  /// This type provides an optional constant generic parameter for configuring pretty-print.
186  #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
187  pub struct Json<const PRETTY: bool = true>;
188
189  impl<T, const PRETTY: bool> FileFormat<T> for Json<PRETTY>
190  where T: Serialize + DeserializeOwned {
191    type FormatError = JsonError;
192
193    fn from_reader<R: Read>(&self, reader: R) -> Result<T, Self::FormatError> {
194      serde_json::from_reader(reader)
195    }
196
197    fn to_writer<W: Write>(&self, writer: W, value: &T) -> Result<(), Self::FormatError> {
198      match PRETTY {
199        true => serde_json::to_writer_pretty(writer, value),
200        false => serde_json::to_writer(writer, value)
201      }
202    }
203
204    fn to_buffer(&self, value: &T) -> Result<Vec<u8>, Self::FormatError> {
205      match PRETTY {
206        true => serde_json::to_vec_pretty(value),
207        false => serde_json::to_vec(value)
208      }
209    }
210  }
211
212  /// A shortcut type to a [`Json`] with pretty-print enabled.
213  pub type PrettyJson = Json<true>;
214  /// A shortcut type to a [`Json`] with pretty-print disabled.
215  pub type RegularJson = Json<false>;
216
217  /// A shortcut type to a [`Compressed`][crate::Compressed] [`Json`].
218  /// Provides parameters for compression format and pretty-print configuration (defaulting to off).
219  pub type CompressedJson<C, const PRETTY: bool = false> = crate::Compressed<C, Json<PRETTY>>;
220}
221
222/// Defines a [`FileFormat`] using the TOML data format.
223#[cfg_attr(docsrs, doc(cfg(feature = "toml-serde")))]
224#[cfg(feature = "toml-serde")]
225pub mod toml_serde {
226  pub extern crate toml;
227
228  use serde::ser::Serialize;
229  use serde::de::DeserializeOwned;
230  use singlefile::FileFormat;
231  use thiserror::Error;
232
233  use std::io::{Read, Write};
234
235  /// An error that can occur while using [`Toml`].
236  #[derive(Debug, Error)]
237  pub enum TomlError {
238    /// An error occured while reading data to the string buffer.
239    #[error(transparent)]
240    IoError(#[from] std::io::Error),
241    /// An error occurred while serializing.
242    #[error(transparent)]
243    SerializeError(#[from] toml::ser::Error),
244    /// An error occurred while deserializing.
245    #[error(transparent)]
246    DeserializeError(#[from] toml::de::Error)
247  }
248
249  /// A [`FileFormat`] corresponding to the TOML data format.
250  /// Implemented using the [`toml`] crate, only compatible with [`serde`] types.
251  ///
252  /// This type provides an optional constant generic parameter for configuring pretty-print.
253  #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
254  pub struct Toml<const PRETTY: bool = true>;
255
256  /// Since the [`toml`] crate exposes no writer-based operations, all operations within this implementation are buffered.
257  impl<T, const PRETTY: bool> FileFormat<T> for Toml<PRETTY>
258  where T: Serialize + DeserializeOwned {
259    type FormatError = TomlError;
260
261    fn from_reader<R: Read>(&self, mut reader: R) -> Result<T, Self::FormatError> {
262      let mut buf = String::new();
263      reader.read_to_string(&mut buf)?;
264      toml::de::from_str(&buf).map_err(From::from)
265    }
266
267    #[inline]
268    fn from_reader_buffered<R: Read>(&self, reader: R) -> Result<T, Self::FormatError> {
269      // no need to pass `reader` in with a `BufReader` as that would cause things to be buffered twice
270      self.from_reader(reader)
271    }
272
273    fn to_writer<W: Write>(&self, mut writer: W, value: &T) -> Result<(), Self::FormatError> {
274      let buf = self.to_buffer(value)?;
275      writer.write_all(&buf).map_err(From::from)
276    }
277
278    #[inline]
279    fn to_writer_buffered<W: Write>(&self, writer: W, value: &T) -> Result<(), Self::FormatError> {
280      // no need to pass `writer` in with a `BufWriter` as that would cause things to be buffered twice
281      self.to_writer(writer, value)
282    }
283
284    fn to_buffer(&self, value: &T) -> Result<Vec<u8>, Self::FormatError> {
285      Ok(match PRETTY {
286        true => toml::ser::to_string_pretty(value),
287        false => toml::ser::to_string(value)
288      }?.into_bytes())
289    }
290  }
291
292  /// A shortcut type to a [`Toml`] with pretty-print enabled.
293  pub type PrettyToml = Toml<true>;
294  /// A shortcut type to a [`Toml`] with pretty-print disabled.
295  pub type RegularToml = Toml<false>;
296
297  /// A shortcut type to a [`Compressed`][crate::Compressed] [`Toml`].
298  /// Provides parameters for compression format and pretty-print configuration (defaulting to off).
299  pub type CompressedToml<C, const PRETTY: bool = false> = crate::Compressed<C, Toml<PRETTY>>;
300}
301
302/// Defines a [`CompressionFormat`] for the bzip compression algorithm.
303#[cfg_attr(docsrs, doc(cfg(feature = "bzip")))]
304#[cfg(feature = "bzip")]
305pub mod bzip {
306  pub extern crate bzip2;
307
308  use crate::{CompressionFormat, CompressionFormatLevels};
309
310  use std::io::{Read, Write};
311
312  /// A [`CompressionFormat`] corresponding to the bzip compression algorithm.
313  /// Implemented using the [`bzip2`] crate.
314  #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
315  pub struct BZip2;
316
317  impl CompressionFormat for BZip2 {
318    type Encoder<W: Write> = bzip2::write::BzEncoder::<W>;
319    type Decoder<R: Read> = bzip2::read::BzDecoder::<R>;
320
321    fn encode_writer<W: Write>(&self, writer: W, level: u32) -> Self::Encoder<W> {
322      Self::Encoder::new(writer, bzip2::Compression::new(level))
323    }
324
325    fn decode_reader<R: Read>(&self, reader: R) -> Self::Decoder<R> {
326      Self::Decoder::new(reader)
327    }
328  }
329
330  impl CompressionFormatLevels for BZip2 {
331    const COMPRESSION_LEVEL_NONE: u32 = 0;
332    const COMPRESSION_LEVEL_FAST: u32 = 1;
333    const COMPRESSION_LEVEL_BEST: u32 = 9;
334    const COMPRESSION_LEVEL_DEFAULT: u32 = 6;
335  }
336}
337
338/// Defines [`CompressionFormat`]s for the DEFLATE, gzip and zlib compression algorithms.
339#[cfg_attr(docsrs, doc(cfg(feature = "flate")))]
340#[cfg(feature = "flate")]
341pub mod flate {
342  pub extern crate flate2;
343
344  use crate::{CompressionFormat, CompressionFormatLevels};
345
346  use std::io::{Read, Write};
347
348  /// A [`CompressionFormat`] corresponding to the DEFLATE compression algorithm.
349  /// Implemented using the [`flate2`] crate.
350  #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
351  pub struct Deflate;
352
353  impl CompressionFormat for Deflate {
354    type Encoder<W: Write> = flate2::write::DeflateEncoder::<W>;
355    type Decoder<R: Read> = flate2::read::DeflateDecoder::<R>;
356
357    fn encode_writer<W: Write>(&self, writer: W, compression: u32) -> Self::Encoder<W> {
358      Self::Encoder::new(writer, flate2::Compression::new(compression))
359    }
360
361    fn decode_reader<R: Read>(&self, reader: R) -> Self::Decoder<R> {
362      Self::Decoder::new(reader)
363    }
364  }
365
366  impl CompressionFormatLevels for Deflate {
367    const COMPRESSION_LEVEL_NONE: u32 = 0;
368    const COMPRESSION_LEVEL_FAST: u32 = 1;
369    const COMPRESSION_LEVEL_BEST: u32 = 9;
370    const COMPRESSION_LEVEL_DEFAULT: u32 = 6;
371  }
372
373  /// A [`CompressionFormat`] corresponding to the gzip compression algorithm.
374  /// Implemented using the [`flate2`] crate.
375  #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
376  pub struct Gz;
377
378  impl CompressionFormat for Gz {
379    type Encoder<W: Write> = flate2::write::GzEncoder::<W>;
380    type Decoder<R: Read> = flate2::read::GzDecoder::<R>;
381
382    fn encode_writer<W: Write>(&self, writer: W, compression: u32) -> Self::Encoder<W> {
383      Self::Encoder::new(writer, flate2::Compression::new(compression))
384    }
385
386    fn decode_reader<R: Read>(&self, reader: R) -> Self::Decoder<R> {
387      Self::Decoder::new(reader)
388    }
389  }
390
391  impl CompressionFormatLevels for Gz {
392    const COMPRESSION_LEVEL_NONE: u32 = 0;
393    const COMPRESSION_LEVEL_FAST: u32 = 1;
394    const COMPRESSION_LEVEL_BEST: u32 = 9;
395    const COMPRESSION_LEVEL_DEFAULT: u32 = 6;
396  }
397
398  /// A [`CompressionFormat`] corresponding to the zlib compression algorithm.
399  /// Implemented using the [`flate2`] crate.
400  #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
401  pub struct ZLib;
402
403  impl CompressionFormat for ZLib {
404    type Encoder<W: Write> = flate2::write::ZlibEncoder::<W>;
405    type Decoder<R: Read> = flate2::read::ZlibDecoder::<R>;
406
407    fn encode_writer<W: Write>(&self, writer: W, compression: u32) -> Self::Encoder<W> {
408      Self::Encoder::new(writer, flate2::Compression::new(compression))
409    }
410
411    fn decode_reader<R: Read>(&self, reader: R) -> Self::Decoder<R> {
412      Self::Decoder::new(reader)
413    }
414  }
415
416  impl CompressionFormatLevels for ZLib {
417    const COMPRESSION_LEVEL_NONE: u32 = 0;
418    const COMPRESSION_LEVEL_FAST: u32 = 1;
419    const COMPRESSION_LEVEL_BEST: u32 = 9;
420    const COMPRESSION_LEVEL_DEFAULT: u32 = 6;
421  }
422}
423
424/// Defines a [`CompressionFormat`] for the LZMA/XZ compression algorithm.
425#[cfg_attr(docsrs, doc(cfg(feature = "xz")))]
426#[cfg(feature = "xz")]
427pub mod xz {
428  pub extern crate xz2;
429
430  use crate::{CompressionFormat, CompressionFormatLevels};
431
432  use std::io::{Read, Write};
433
434  /// A [`CompressionFormat`] corresponding to the LZMA/XZ compression algorithm.
435  /// Implemented using the [`xz2`] crate.
436  #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
437  pub struct Xz;
438
439  impl CompressionFormat for Xz {
440    type Encoder<W: Write> = xz2::write::XzEncoder::<W>;
441    type Decoder<R: Read> = xz2::read::XzDecoder::<R>;
442
443    fn encode_writer<W: Write>(&self, writer: W, compression: u32) -> Self::Encoder<W> {
444      Self::Encoder::new(writer, compression)
445    }
446
447    fn decode_reader<R: Read>(&self, reader: R) -> Self::Decoder<R> {
448      Self::Decoder::new(reader)
449    }
450  }
451
452  impl CompressionFormatLevels for Xz {
453    const COMPRESSION_LEVEL_NONE: u32 = 0;
454    const COMPRESSION_LEVEL_FAST: u32 = 1;
455    const COMPRESSION_LEVEL_BEST: u32 = 9;
456    const COMPRESSION_LEVEL_DEFAULT: u32 = 6;
457  }
458}