oxidef_compact1 0.1.0-alpha.1

Oxidef is an experimental interface definition language and serialisation scheme for efficient and strongly-typed payloads.
Documentation
use bytes::{Buf, TryGetError};
use thiserror::Error;

#[derive(Clone, Debug, Error, PartialEq, Eq, Hash)]
pub enum DecError {
    /// Unexpectedly encountered the end of the payload
    #[error("unexpectedly encountered end of payload")]
    UnexpectedEnd,

    #[error("a varuint was not encoded in canonical form")]
    VarUintNotCanonical,

    #[error("a container was longer than allowed")]
    ContainerTooLong,

    #[error("trailing rubbish following the payload")]
    TrailingRubbish,

    #[cfg(all(feature = "alloc", feature = "validation"))]
    #[error("validation error at {path}: [{rule_code}] {rule_message}")]
    Validation {
        path: String,
        rule_code: &'static str,
        rule_message: &'static str,
    },

    #[error("overran the extension of an extensible type")]
    ExtensionOverran,

    #[error("invalid union variant")]
    InvalidUnionVariant,

    #[error("utf-8 decoding failed")]
    Utf8Decode,

    #[error("there were duplicate keys in a map")]
    DuplicateMapKeys,

    #[error("float not finite")]
    FloatNotFinite,

    #[error("attempt to decode 'Infallible' payload")]
    Never,

    #[error("interoperability integer type is invalid")]
    InteropIntInvalid,
}

impl From<TryGetError> for DecError {
    fn from(_value: TryGetError) -> Self {
        DecError::UnexpectedEnd
    }
}

pub struct Decoder<B: MeasureBuf> {
    bit_stash_remaining: u8,
    bit_stash: u8,
    pub buf: B,
}

impl<B: MeasureBuf> Decoder<B> {
    pub fn new(buf: B) -> Self {
        Self {
            bit_stash_remaining: 0,
            bit_stash: 0,
            buf,
        }
    }

    /// Reads a bit from the dense 'bit stash'
    /// If the bit stash is empty, reads another byte from the buffer to populate it.
    pub fn read_stash_bit(&mut self) -> Result<bool, DecError> {
        if self.bit_stash_remaining == 0 {
            self.bit_stash = self.buf.try_get_u8()?;
            self.bit_stash_remaining = 8;
        }
        self.bit_stash_remaining -= 1;
        let result = self.bit_stash & (1 << self.bit_stash_remaining) != 0;
        Ok(result)
    }

    pub fn discard_bit_stash(&mut self) {
        self.bit_stash_remaining = 0;
    }
}

pub trait MeasureBuf: Buf {
    /// Measure the current position of the decoder.
    /// This is only valid for relative measurements (end - start),
    /// not as an absolute index into the buffer.
    fn position_measure(&self) -> u32;
}

impl MeasureBuf for &[u8] {
    fn position_measure(&self) -> u32 {
        u32::MAX - u32::try_from(self.len()).expect("buf too large")
    }
}

#[cfg(feature = "alloc")]
pub fn decode_unvalidated<T: crate::codec::Compact1Codec>(bytes: &[u8]) -> Result<T, DecError> {
    let mut decoder = Decoder::new(bytes);
    let out = T::decode(&mut decoder)?;

    // assert empty remainder
    if !decoder.buf.is_empty() {
        return Err(DecError::TrailingRubbish);
    }

    Ok(out)
}

#[cfg(all(feature = "alloc", feature = "validation"))]
pub fn decode<T: crate::codec::Compact1Codec + oxidef_validation::Validate>(
    bytes: &[u8],
) -> Result<T, DecError> {
    let out: T = decode_unvalidated(bytes)?;

    out.validate().map_err(|err| DecError::Validation {
        path: format!("{:?}", err.path),
        rule_code: err.rule_code,
        rule_message: err.rule_message,
    })?;

    Ok(out)
}