1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
//! This library provides a number of default [`FileFormat`] implementations
//! for use within [`singlefile`](https://crates.io/crates/singlefile).
//!
//! # Features
//! By default, no features are enabled.
//!
//! - `cbor-serde`: Enables the [`Cbor`][crate::cbor_serde::Cbor] file format for use with [`serde`] types.
//! - `json-serde`: Enables the [`Json`][crate::json_serde::Json] file format for use with [`serde`] types.
//! - `toml-serde`: Enables the [`Toml`][crate::toml_serde::Toml] file format for use with [`serde`] types.
//! - `bzip`: Enables the [`BZip2`][crate::bzip::BZip2] compression format. See [`CompressionFormat`] for more info.
//! - `flate`: Enables the [`Deflate`][crate::flate::Deflate], [`Gz`][crate::flate::Gz],
//!   and [`ZLib`][crate::flate::ZLib] compression formats. See [`CompressionFormat`] for more info.
//! - `xz`: Enables the [`Xz`][crate::xz::Xz] compression format. See [`CompressionFormat`] for more info.

#![cfg_attr(docsrs, feature(doc_cfg))]
#![forbid(unsafe_code)]
#![warn(
  future_incompatible,
  missing_copy_implementations,
  missing_debug_implementations,
  missing_docs,
  unreachable_pub
)]

pub extern crate singlefile;

use singlefile::FileFormat;

use std::io::{Read, Write};

/// Combines a [`FileFormat`] and a [`CompressionFormat`], making the contents emitted by
/// the format compressed before writing to disk, and decompressed before parsing.
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Compressed<C, F> {
  /// The [`FileFormat`] to be used.
  pub format: F,
  /// The [`CompressionFormat`] to be used.
  pub compression: C,
  /// The level of compression to use.
  /// This value may have different meanings for different compression formats.
  pub level: u32
}

impl<C, F> Compressed<C, F> {
  /// Create a new [`Compressed`], given a compression level.
  #[inline]
  pub const fn with_level(format: F, compression: C, level: u32) -> Self {
    Compressed { format, compression, level }
  }
}

impl<C, F> Compressed<C, F> where C: CompressionFormatLevels {
  /// Creates a new [`Compressed`] with the default compression level.
  #[inline]
  pub const fn new(format: F, compression: C) -> Self {
    Compressed::with_level(format, compression, C::COMPRESSION_LEVEL_DEFAULT)
  }

  /// Creates a new [`Compressed`] with the 'fast' compression level.
  #[inline]
  pub const fn new_fast_compression(format: F, compression: C) -> Self {
    Compressed::with_level(format, compression, C::COMPRESSION_LEVEL_FAST)
  }

  /// Creates a new [`Compressed`] with the 'best' compression level.
  #[inline]
  pub const fn new_best_compression(format: F, compression: C) -> Self {
    Compressed::with_level(format, compression, C::COMPRESSION_LEVEL_BEST)
  }
}

impl<C, F> Default for Compressed<C, F>
where C: Default + CompressionFormatLevels, F: Default {
  #[inline]
  fn default() -> Self {
    Compressed::new(F::default(), C::default())
  }
}

impl<T, C, F> FileFormat<T> for Compressed<C, F>
where C: CompressionFormat, F: FileFormat<T> {
  type FormatError = F::FormatError;

  fn from_reader<R: Read>(&self, reader: R) -> Result<T, Self::FormatError> {
    self.format.from_reader(self.compression.decode_reader(reader))
  }

  fn to_writer<W: Write>(&self, writer: W, value: &T) -> Result<(), Self::FormatError> {
    self.format.to_writer(self.compression.encode_writer(writer, self.level), value)
  }
}

/// Defines a format for lossless compression of arbitrary data.
///
/// In order to use a [`CompressionFormat`], you may consider using the [`Compressed`] struct.
pub trait CompressionFormat {
  /// The encoder wrapper type that compresses data sent to the contained writer.
  type Encoder<W: Write>: Write;
  /// The decoder wrapper type that decompresses data sent from the contained reader.
  type Decoder<R: Read>: Read;

  /// Wraps a writer that takes uncompressed data, producing a new writer that outputs compressed data.
  fn encode_writer<W: Write>(&self, writer: W, level: u32) -> Self::Encoder<W>;
  /// Wraps a reader that takes compressed data, producing a new reader that outputs uncompressed data.
  fn decode_reader<R: Read>(&self, reader: R) -> Self::Decoder<R>;
}

/// Defines compression level presets for a [`CompressionFormat`].
pub trait CompressionFormatLevels: CompressionFormat {
  /// The level for no compression.
  const COMPRESSION_LEVEL_NONE: u32;
  /// The level for 'fast' compression.
  const COMPRESSION_LEVEL_FAST: u32;
  /// The level for 'best' compression.
  const COMPRESSION_LEVEL_BEST: u32;
  /// The level for default compression.
  const COMPRESSION_LEVEL_DEFAULT: u32;
}

/// Defines a [`FileFormat`] using the CBOR binary data format.
#[cfg_attr(docsrs, doc(cfg(feature = "cbor-serde")))]
#[cfg(feature = "cbor-serde")]
pub mod cbor_serde {
  pub extern crate ciborium;

  use serde::ser::Serialize;
  use serde::de::DeserializeOwned;
  use singlefile::FileFormat;
  use thiserror::Error;

  use std::io::{Read, Write};

  /// An error that can occur while using [`Cbor`].
  #[derive(Debug, Error)]
  pub enum CborError {
    /// An error occurred while serializing.
    #[error(transparent)]
    SerializeError(#[from] ciborium::ser::Error<std::io::Error>),
    /// An error occurred while deserializing.
    #[error(transparent)]
    DeserializeError(#[from] ciborium::de::Error<std::io::Error>)
  }

  /// A [`FileFormat`] corresponding to the CBOR binary data format.
  /// Implemented using the [`ciborium`] crate, only compatible with [`serde`] types.
  #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
  pub struct Cbor;

  impl<T> FileFormat<T> for Cbor
  where T: Serialize + DeserializeOwned {
    type FormatError = CborError;

    fn from_reader<R: Read>(&self, reader: R) -> Result<T, Self::FormatError> {
      ciborium::de::from_reader(reader).map_err(From::from)
    }

    fn to_writer<W: Write>(&self, writer: W, value: &T) -> Result<(), Self::FormatError> {
      ciborium::ser::into_writer(value, writer).map_err(From::from)
    }
  }

  /// A shortcut type to a [`Compressed`][crate::Compressed] [`Cbor`].
  /// Provides a single parameter for compression format.
  pub type CompressedCbor<C> = crate::Compressed<C, Cbor>;
}

/// Defines a [`FileFormat`] using the JSON data format.
#[cfg_attr(docsrs, doc(cfg(feature = "json-serde")))]
#[cfg(feature = "json-serde")]
pub mod json_serde {
  pub extern crate serde_json;

  use serde::ser::Serialize;
  use serde::de::DeserializeOwned;
  use singlefile::FileFormat;

  use std::io::{Read, Write};

  /// An error that can occur while using [`Json`].
  pub type JsonError = serde_json::Error;

  /// A [`FileFormat`] corresponding to the JSON data format.
  /// Implemented using the [`serde_json`] crate, only compatible with [`serde`] types.
  ///
  /// This type provides an optional constant generic parameter for configuring pretty-print.
  #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
  pub struct Json<const PRETTY: bool = true>;

  impl<T, const PRETTY: bool> FileFormat<T> for Json<PRETTY>
  where T: Serialize + DeserializeOwned {
    type FormatError = JsonError;

    fn from_reader<R: Read>(&self, reader: R) -> Result<T, Self::FormatError> {
      serde_json::from_reader(reader)
    }

    fn to_writer<W: Write>(&self, writer: W, value: &T) -> Result<(), Self::FormatError> {
      match PRETTY {
        true => serde_json::to_writer_pretty(writer, value),
        false => serde_json::to_writer(writer, value)
      }
    }

    fn to_buffer(&self, value: &T) -> Result<Vec<u8>, Self::FormatError> {
      match PRETTY {
        true => serde_json::to_vec_pretty(value),
        false => serde_json::to_vec(value)
      }
    }
  }

  /// A shortcut type to a [`Json`] with pretty-print enabled.
  pub type PrettyJson = Json<true>;
  /// A shortcut type to a [`Json`] with pretty-print disabled.
  pub type RegularJson = Json<false>;

  /// A shortcut type to a [`Compressed`][crate::Compressed] [`Json`].
  /// Provides parameters for compression format and pretty-print configuration (defaulting to off).
  pub type CompressedJson<C, const PRETTY: bool = false> = crate::Compressed<C, Json<PRETTY>>;
}

/// Defines a [`FileFormat`] using the TOML data format.
#[cfg_attr(docsrs, doc(cfg(feature = "toml-serde")))]
#[cfg(feature = "toml-serde")]
pub mod toml_serde {
  pub extern crate toml;

  use serde::ser::Serialize;
  use serde::de::DeserializeOwned;
  use singlefile::FileFormat;
  use thiserror::Error;

  use std::io::{Read, Write};

  /// An error that can occur while using [`Toml`].
  #[derive(Debug, Error)]
  pub enum TomlError {
    /// An error occured while reading data to the string buffer.
    #[error(transparent)]
    IoError(#[from] std::io::Error),
    /// An error occurred while serializing.
    #[error(transparent)]
    SerializeError(#[from] toml::ser::Error),
    /// An error occurred while deserializing.
    #[error(transparent)]
    DeserializeError(#[from] toml::de::Error)
  }

  /// A [`FileFormat`] corresponding to the TOML data format.
  /// Implemented using the [`toml`] crate, only compatible with [`serde`] types.
  ///
  /// This type provides an optional constant generic parameter for configuring pretty-print.
  #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
  pub struct Toml<const PRETTY: bool = true>;

  /// Since the [`toml`] crate exposes no writer-based operations, all operations within this implementation are buffered.
  impl<T, const PRETTY: bool> FileFormat<T> for Toml<PRETTY>
  where T: Serialize + DeserializeOwned {
    type FormatError = TomlError;

    fn from_reader<R: Read>(&self, mut reader: R) -> Result<T, Self::FormatError> {
      let mut buf = String::new();
      reader.read_to_string(&mut buf)?;
      toml::de::from_str(&buf).map_err(From::from)
    }

    #[inline]
    fn from_reader_buffered<R: Read>(&self, reader: R) -> Result<T, Self::FormatError> {
      // no need to pass `reader` in with a `BufReader` as that would cause things to be buffered twice
      self.from_reader(reader)
    }

    fn to_writer<W: Write>(&self, mut writer: W, value: &T) -> Result<(), Self::FormatError> {
      let buf = self.to_buffer(value)?;
      writer.write_all(&buf).map_err(From::from)
    }

    #[inline]
    fn to_writer_buffered<W: Write>(&self, writer: W, value: &T) -> Result<(), Self::FormatError> {
      // no need to pass `writer` in with a `BufWriter` as that would cause things to be buffered twice
      self.to_writer(writer, value)
    }

    fn to_buffer(&self, value: &T) -> Result<Vec<u8>, Self::FormatError> {
      Ok(match PRETTY {
        true => toml::ser::to_string_pretty(value),
        false => toml::ser::to_string(value)
      }?.into_bytes())
    }
  }

  /// A shortcut type to a [`Toml`] with pretty-print enabled.
  pub type PrettyToml = Toml<true>;
  /// A shortcut type to a [`Toml`] with pretty-print disabled.
  pub type RegularToml = Toml<false>;

  /// A shortcut type to a [`Compressed`][crate::Compressed] [`Toml`].
  /// Provides parameters for compression format and pretty-print configuration (defaulting to off).
  pub type CompressedToml<C, const PRETTY: bool = false> = crate::Compressed<C, Toml<PRETTY>>;
}

/// Defines a [`CompressionFormat`] for the bzip compression algorithm.
#[cfg_attr(docsrs, doc(cfg(feature = "bzip")))]
#[cfg(feature = "bzip")]
pub mod bzip {
  pub extern crate bzip2;

  use crate::{CompressionFormat, CompressionFormatLevels};

  use std::io::{Read, Write};

  /// A [`CompressionFormat`] corresponding to the bzip compression algorithm.
  /// Implemented using the [`bzip2`] crate.
  #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
  pub struct BZip2;

  impl CompressionFormat for BZip2 {
    type Encoder<W: Write> = bzip2::write::BzEncoder::<W>;
    type Decoder<R: Read> = bzip2::read::BzDecoder::<R>;

    fn encode_writer<W: Write>(&self, writer: W, level: u32) -> Self::Encoder<W> {
      Self::Encoder::new(writer, bzip2::Compression::new(level))
    }

    fn decode_reader<R: Read>(&self, reader: R) -> Self::Decoder<R> {
      Self::Decoder::new(reader)
    }
  }

  impl CompressionFormatLevels for BZip2 {
    const COMPRESSION_LEVEL_NONE: u32 = 0;
    const COMPRESSION_LEVEL_FAST: u32 = 1;
    const COMPRESSION_LEVEL_BEST: u32 = 9;
    const COMPRESSION_LEVEL_DEFAULT: u32 = 6;
  }
}

/// Defines [`CompressionFormat`]s for the DEFLATE, gzip and zlib compression algorithms.
#[cfg_attr(docsrs, doc(cfg(feature = "flate")))]
#[cfg(feature = "flate")]
pub mod flate {
  pub extern crate flate2;

  use crate::{CompressionFormat, CompressionFormatLevels};

  use std::io::{Read, Write};

  /// A [`CompressionFormat`] corresponding to the DEFLATE compression algorithm.
  /// Implemented using the [`flate2`] crate.
  #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
  pub struct Deflate;

  impl CompressionFormat for Deflate {
    type Encoder<W: Write> = flate2::write::DeflateEncoder::<W>;
    type Decoder<R: Read> = flate2::read::DeflateDecoder::<R>;

    fn encode_writer<W: Write>(&self, writer: W, compression: u32) -> Self::Encoder<W> {
      Self::Encoder::new(writer, flate2::Compression::new(compression))
    }

    fn decode_reader<R: Read>(&self, reader: R) -> Self::Decoder<R> {
      Self::Decoder::new(reader)
    }
  }

  impl CompressionFormatLevels for Deflate {
    const COMPRESSION_LEVEL_NONE: u32 = 0;
    const COMPRESSION_LEVEL_FAST: u32 = 1;
    const COMPRESSION_LEVEL_BEST: u32 = 9;
    const COMPRESSION_LEVEL_DEFAULT: u32 = 6;
  }

  /// A [`CompressionFormat`] corresponding to the gzip compression algorithm.
  /// Implemented using the [`flate2`] crate.
  #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
  pub struct Gz;

  impl CompressionFormat for Gz {
    type Encoder<W: Write> = flate2::write::GzEncoder::<W>;
    type Decoder<R: Read> = flate2::read::GzDecoder::<R>;

    fn encode_writer<W: Write>(&self, writer: W, compression: u32) -> Self::Encoder<W> {
      Self::Encoder::new(writer, flate2::Compression::new(compression))
    }

    fn decode_reader<R: Read>(&self, reader: R) -> Self::Decoder<R> {
      Self::Decoder::new(reader)
    }
  }

  impl CompressionFormatLevels for Gz {
    const COMPRESSION_LEVEL_NONE: u32 = 0;
    const COMPRESSION_LEVEL_FAST: u32 = 1;
    const COMPRESSION_LEVEL_BEST: u32 = 9;
    const COMPRESSION_LEVEL_DEFAULT: u32 = 6;
  }

  /// A [`CompressionFormat`] corresponding to the zlib compression algorithm.
  /// Implemented using the [`flate2`] crate.
  #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
  pub struct ZLib;

  impl CompressionFormat for ZLib {
    type Encoder<W: Write> = flate2::write::ZlibEncoder::<W>;
    type Decoder<R: Read> = flate2::read::ZlibDecoder::<R>;

    fn encode_writer<W: Write>(&self, writer: W, compression: u32) -> Self::Encoder<W> {
      Self::Encoder::new(writer, flate2::Compression::new(compression))
    }

    fn decode_reader<R: Read>(&self, reader: R) -> Self::Decoder<R> {
      Self::Decoder::new(reader)
    }
  }

  impl CompressionFormatLevels for ZLib {
    const COMPRESSION_LEVEL_NONE: u32 = 0;
    const COMPRESSION_LEVEL_FAST: u32 = 1;
    const COMPRESSION_LEVEL_BEST: u32 = 9;
    const COMPRESSION_LEVEL_DEFAULT: u32 = 6;
  }
}

/// Defines a [`CompressionFormat`] for the LZMA/XZ compression algorithm.
#[cfg_attr(docsrs, doc(cfg(feature = "xz")))]
#[cfg(feature = "xz")]
pub mod xz {
  pub extern crate xz2;

  use crate::{CompressionFormat, CompressionFormatLevels};

  use std::io::{Read, Write};

  /// A [`CompressionFormat`] corresponding to the LZMA/XZ compression algorithm.
  /// Implemented using the [`xz2`] crate.
  #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
  pub struct Xz;

  impl CompressionFormat for Xz {
    type Encoder<W: Write> = xz2::write::XzEncoder::<W>;
    type Decoder<R: Read> = xz2::read::XzDecoder::<R>;

    fn encode_writer<W: Write>(&self, writer: W, compression: u32) -> Self::Encoder<W> {
      Self::Encoder::new(writer, compression)
    }

    fn decode_reader<R: Read>(&self, reader: R) -> Self::Decoder<R> {
      Self::Decoder::new(reader)
    }
  }

  impl CompressionFormatLevels for Xz {
    const COMPRESSION_LEVEL_NONE: u32 = 0;
    const COMPRESSION_LEVEL_FAST: u32 = 1;
    const COMPRESSION_LEVEL_BEST: u32 = 9;
    const COMPRESSION_LEVEL_DEFAULT: u32 = 6;
  }
}