cyclone-runtime-rust 1.0.1

Rust reference runtime for the Cyclone binary wire format: Writer, Reader, DecodeError.
Documentation
//! The traits a codec implements, and the two helpers that use them.
//!
//! The runtime knows nothing about how an implementation of these traits comes
//! to exist. `#[derive(Network)]` writes one, the Cyclone CLI writes one into a
//! `*.codec.rs` file, and writing one by hand is equally valid. All three are
//! the same to this module.

use crate::error::DecodeError;
use crate::reader::Reader;
use crate::writer::Writer;

/// A type that can be written as Cyclone bytes.
///
/// The implementation is the schema: it decides which fields are written, in
/// which order, and with which Cyclone type. Nothing here inspects the Rust
/// type to work that out.
///
/// ```
/// use cyclone_runtime::{Encode, Writer};
///
/// struct Item { id: u32, name: String }
///
/// impl Encode for Item {
///     fn encode(&self, writer: &mut Writer) {
///         writer.write_u32(self.id);
///         writer.write_string(&self.name);
///     }
/// }
///
/// let mut writer = Writer::new();
/// Item { id: 42, name: "Sword".to_owned() }.encode(&mut writer);
/// assert_eq!(writer.len(), 13);
/// ```
pub trait Encode {
    /// Appends the Cyclone encoding of `self` to `writer`.
    ///
    /// Fields go in declaration order with nothing between them: no padding,
    /// no alignment, no field id, no header (RFC-0002 §5).
    fn encode(&self, writer: &mut Writer);
}

/// A type that can be read back from Cyclone bytes.
///
/// The counterpart of [`Encode`], and the two must agree field for field: the
/// byte stream carries no metadata to fall back on.
pub trait Decode: Sized {
    /// Reads one value from `reader`, advancing its cursor past it.
    ///
    /// # Errors
    ///
    /// Any [`DecodeError`] the underlying reads produce. On failure the cursor
    /// is left where the failing read began.
    fn decode(reader: &mut Reader<'_>) -> Result<Self, DecodeError>;
}

/// Encodes `value` into a fresh `Vec<u8>`.
///
/// ```
/// # use cyclone_runtime::{Encode, Writer};
/// # struct Item { id: u32 }
/// # impl Encode for Item { fn encode(&self, w: &mut Writer) { w.write_u32(self.id); } }
/// let bytes = cyclone_runtime::to_bytes(&Item { id: 42 });
/// assert_eq!(bytes, [0x2A, 0x00, 0x00, 0x00]);
/// ```
///
/// Use [`Writer`] directly when several values share one buffer, or when a
/// pre-sized buffer avoids reallocation.
pub fn to_bytes<T: Encode>(value: &T) -> Vec<u8> {
    let mut writer = Writer::new();
    value.encode(&mut writer);
    writer.into_bytes()
}

/// Decodes a `T` from `bytes`.
///
/// ```
/// # use cyclone_runtime::{DecodeError, Decode, Reader};
/// # struct Item { id: u32 }
/// # impl Decode for Item {
/// #     fn decode(r: &mut Reader<'_>) -> Result<Self, DecodeError> { Ok(Item { id: r.read_u32()? }) }
/// # }
/// let item = cyclone_runtime::from_bytes::<Item>(&[0x2A, 0x00, 0x00, 0x00])?;
/// assert_eq!(item.id, 42);
/// # Ok::<(), DecodeError>(())
/// ```
///
/// # Errors
///
/// Any [`DecodeError`] the decode produces.
///
/// # Trailing bytes
///
/// Bytes left over after the value are **not** an error here - this helper
/// decodes one value and returns it. A stream that should end exactly at the
/// value (RFC-0002 §9) needs the check made explicitly:
///
/// ```
/// # use cyclone_runtime::{DecodeError, Decode, Reader};
/// # struct Item { id: u32 }
/// # impl Decode for Item {
/// #     fn decode(r: &mut Reader<'_>) -> Result<Self, DecodeError> { Ok(Item { id: r.read_u32()? }) }
/// # }
/// # let bytes = [0x2A, 0x00, 0x00, 0x00];
/// let mut reader = Reader::new(&bytes);
/// let item = Item::decode(&mut reader)?;
/// assert!(reader.is_empty(), "trailing bytes: the two ends disagree about the schema");
/// # Ok::<(), DecodeError>(())
/// ```
///
/// The same route applies when decoding untrusted input, which wants
/// [`Reader::with_limits`] rather than the permissive default this helper uses.
pub fn from_bytes<T: Decode>(bytes: &[u8]) -> Result<T, DecodeError> {
    let mut reader = Reader::new(bytes);
    T::decode(&mut reader)
}