use std::fmt;
pub(crate) mod header {
use super::Version;
use crate::error::{Error, Result};
const MAGIC: u8 = 0xba;
const FIXED_MASK: u8 = 0b1110_0000;
const FIXED: u8 = 0b1010_0000;
const IDENTS: u8 = 0b0001_0000;
const VERSION_MASK: u8 = 0b0000_1111;
pub(crate) const fn bytes(version: Version, with_idents: bool) -> [u8; 2] {
let idents = if with_idents { IDENTS } else { 0 };
[MAGIC, FIXED | idents | version.as_u8()]
}
pub(crate) fn parse(bytes: [u8; 2], with_idents: bool) -> Result<Version> {
if bytes[0] != MAGIC || bytes[1] & FIXED_MASK != FIXED {
return Err(Error::BadHeader);
}
let data_has_idents = bytes[1] & IDENTS != 0;
if data_has_idents != with_idents {
return Err(Error::WithIdentsMismatch(data_has_idents));
}
let version = bytes[1] & VERSION_MASK;
match Version::try_from(version) {
Ok(version) if !version.is_0_4() => Ok(version),
_ => Err(Error::UnsupportedVersion(version)),
}
}
}
pub const DEFAULT_DEPTH_LIMIT: usize = 128;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug, Default)]
#[non_exhaustive]
pub enum Version {
Postbag0_4,
#[default]
Postbag1,
}
impl Version {
pub(crate) const fn is_0_4(self) -> bool {
matches!(self, Self::Postbag0_4)
}
const fn as_u8(self) -> u8 {
match self {
Version::Postbag0_4 => 0,
Version::Postbag1 => 1,
}
}
}
impl From<Version> for u8 {
fn from(version: Version) -> Self {
version.as_u8()
}
}
impl TryFrom<u8> for Version {
type Error = UnknownVersion;
fn try_from(value: u8) -> Result<Self, Self::Error> {
match value {
0 => Ok(Self::Postbag0_4),
1 => Ok(Self::Postbag1),
unknown => Err(UnknownVersion(unknown)),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Debug)]
pub struct UnknownVersion(pub u8);
impl fmt::Display for UnknownVersion {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "unknown Postbag data format version {}", self.0)
}
}
impl std::error::Error for UnknownVersion {}
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Cfg<const WITH_IDENTS: bool> {
depth_limit: usize,
version: Version,
header: bool,
allow_skip: bool,
}
impl<const WITH_IDENTS: bool> Cfg<WITH_IDENTS> {
pub const DEFAULT_DEPTH_LIMIT: usize = DEFAULT_DEPTH_LIMIT;
pub const fn new() -> Self {
Self { depth_limit: DEFAULT_DEPTH_LIMIT, version: Version::Postbag1, header: true, allow_skip: true }
}
pub const fn with_idents(&self) -> bool {
WITH_IDENTS
}
pub const fn with_depth_limit(self, depth_limit: usize) -> Self {
Self { depth_limit, ..self }
}
pub const fn with_version(self, version: Version) -> Self {
Self { version, ..self }
}
pub const fn with_header(self, header: bool) -> Self {
Self { header, ..self }
}
pub const fn with_allow_skip(self, allow_skip: bool) -> Self {
Self { allow_skip, ..self }
}
pub const fn depth_limit(&self) -> usize {
self.depth_limit
}
pub const fn header(&self) -> bool {
self.header && !self.version().is_0_4()
}
pub(crate) const fn header_bytes(&self) -> [u8; 2] {
header::bytes(self.version, WITH_IDENTS)
}
pub const fn allow_skip(&self) -> bool {
self.allow_skip && WITH_IDENTS && !cfg!(postbag_fast_compile)
}
pub const fn version(&self) -> Version {
self.version
}
}
impl<const WITH_IDENTS: bool> Default for Cfg<WITH_IDENTS> {
fn default() -> Self {
Self::new()
}
}
impl<const WITH_IDENTS: bool> fmt::Debug for Cfg<WITH_IDENTS> {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("Cfg")
.field("with_idents", &WITH_IDENTS)
.field("depth_limit", &self.depth_limit)
.field("version", &self.version)
.field("header", &self.header())
.field("allow_skip", &self.allow_skip())
.finish()
}
}
pub type Full = Cfg<true>;
pub type Slim = Cfg<false>;