structio 0.2.0

High performance JSON and BEVE for Rust structs. No dependencies, no proc-macros, no intermediate representation.
Documentation
//! The traits and constants that do not belong to any one format.
//!
//! A struct's *schema* is format independent: the same field list, in the same
//! order, under the same keys, whether it is going out as JSON text or BEVE
//! binary. That schema is [`Keys`], and the [`object!`](crate::object) macro
//! generates it once per type. A struct declared positionally has no keys to
//! share, only a length: that is [`Elements`], from [`array!`](crate::array).
//! An enum shares its variant names, which is [`Variants`], from
//! [`unit_enum!`](crate::unit_enum) and [`tagged_enum!`](crate::tagged_enum).
//!
//! Everything downstream of the schema is format specific and lives in
//! [`json`](crate::json) and [`beve`](crate::beve): each has its own `Read`,
//! `Write`, `ReadObject`, and `WriteObject`.

use core::marker::PhantomData;

use crate::keymap::KeyMap;
use crate::options::Options;

/// The key schema of a struct, and its compile-time perfect hash.
///
/// Shared by every format, because the keys are a property of the type rather
/// than of the encoding. JSON looks a key up out of a quoted run of document
/// bytes and BEVE out of a length-prefixed one, but both land in the same
/// table and yield the same field index.
pub trait Keys {
    /// Field keys, in declaration order. Index `i` here is field `i` in each
    /// format's `read_field` and `write_fields`.
    const KEYS: &'static [&'static str];

    /// The perfect hash over [`Keys::KEYS`], built during const evaluation.
    ///
    /// A reference rather than a value, so the table lives in read-only memory
    /// and is never copied onto the stack at a call site.
    const MAP: &'static KeyMap;

    /// Bit `i` set for each field that a document must supply, in
    /// [`KEYS`](Keys::KEYS) order.
    ///
    /// Where [`Options::ERROR_ON_MISSING_KEYS`] is the reader's answer to "may
    /// a member be left out", this is the type's, and the two are a union: a
    /// field marked here is required under every policy, and
    /// [`RequireKeys`](crate::RequireKeys) requires every field whether or not
    /// any is marked. Absence is otherwise no error, so the default is zero and
    /// a schema that says nothing about it reads exactly as it did before.
    ///
    /// Written by [`object!`](crate::object) from the `#[required]` markers in
    /// a declaration. A hand-written impl may set it directly, and should set
    /// no bit past the end of [`KEYS`](Keys::KEYS): such a bit asks for a field
    /// that cannot be filled, so no reading under the default policy would ever
    /// succeed, while [`RequireKeys`](crate::RequireKeys) would discard it
    /// along with the rest of the mask and accept the same document.
    ///
    /// **A marked field must be one of the first 64 declared.** The mask is a
    /// `u64`, and a field past that has no bit to set. Marking one is a build
    /// error naming the limit, reported when the crate is built rather than by
    /// `cargo check`, the mask being a constant of a generic type.
    ///
    /// The struct itself may be wider: only the fields that are marked need
    /// room here, unlike
    /// [`ERROR_ON_MISSING_KEYS`](Options::ERROR_ON_MISSING_KEYS), which needs a
    /// bit for every one and so caps the whole struct.
    ///
    /// [`Options::ERROR_ON_MISSING_KEYS`]: crate::Options::ERROR_ON_MISSING_KEYS
    const REQUIRED: u64 = 0;
}

/// The variant names of an enum, and their compile-time perfect hash.
///
/// The enum counterpart of [`Keys`], and shared by every format for the same
/// reason: which variants there are and what they are called is a property of
/// the type, not of the encoding. A name goes out as a JSON string or as a
/// BEVE one, but both land in the same table and yield the same variant index.
///
/// Generated by [`unit_enum!`](crate::unit_enum) and
/// [`tagged_enum!`](crate::tagged_enum).
pub trait Variants {
    /// Variant names, in declaration order. Index `i` here is variant `i` in
    /// each format's `read_name` and `read_payload`.
    const VARIANTS: &'static [&'static str];

    /// The perfect hash over [`Variants::VARIANTS`], built during const
    /// evaluation.
    ///
    /// A reference rather than a value, for [`Keys::MAP`]'s reason: the table
    /// lives in read-only memory and is never copied onto the stack.
    const MAP: &'static KeyMap;
}

/// The bookkeeping behind [`Options::ERROR_ON_MISSING_KEYS`] and
/// [`Keys::REQUIRED`], and the one place a struct too wide for the first is
/// refused.
///
/// An object reader sets bit `i` when it fills field `i`; an object that ends
/// holding anything less than [`MASK`](Fields::MASK) left a member out that
/// either the policy or the type insisted on.
pub(crate) struct Fields<O, T>(PhantomData<fn(O, T)>);

impl<O: Options, T: Keys> Fields<O, T> {
    /// The bits an object has to end up holding: every field under a policy
    /// that requires them all, and otherwise the ones the type marked.
    ///
    /// Zero when neither asks for anything, which makes the comparison against
    /// it `0 != 0` and takes the whole check out of the reader.
    ///
    /// The assertion is the 64-field cap, and it sits inside the branch that
    /// needs it rather than in front of both. Const evaluation follows the
    /// control flow, so a struct too wide for a bit per field stays perfectly
    /// legal for every reading that does not ask for one -- which is every
    /// reading under the default policy, whatever the type marks.
    ///
    /// Being a constant of a generic type, it is refused when the crate is
    /// built rather than by `cargo check`.
    pub(crate) const MASK: u64 = if O::ERROR_ON_MISSING_KEYS {
        let n = T::MAP.n as usize;
        assert!(
            n <= 64,
            "ERROR_ON_MISSING_KEYS tracks one bit per field in a u64, \
             so it cannot read a struct of more than 64 fields"
        );
        if n == 64 { u64::MAX } else { (1u64 << n) - 1 }
    } else {
        T::REQUIRED
    };

    /// Whether a filled field is worth recording. False leaves `seen` a
    /// constant zero and the `|=` in the read loop unreachable.
    pub(crate) const TRACK: bool = Self::MASK != 0;

    /// Whether every field has a bit, which is every struct the 64-field cap
    /// admits.
    const NARROW: bool = T::MAP.n as usize <= 64;

    /// The first key the mask asked for that `seen` does not hold.
    ///
    /// Which of several absent members it names is the declaration order,
    /// which is stable and is where a person reading the schema would look.
    ///
    /// `get` rather than an index, and so `None` rather than a panic, because
    /// [`Keys`] is a public trait and a hand-written impl may set a
    /// [`REQUIRED`](Keys::REQUIRED) bit past the end of its own
    /// [`KEYS`](Keys::KEYS): an unsatisfiable schema, which its own
    /// documentation allows, but not a reason for a diagnostic to panic.
    pub(crate) fn missing(seen: u64) -> Option<&'static str> {
        let i = (Self::MASK & !seen).trailing_zeros() as usize;
        T::KEYS.get(i).copied()
    }

    /// Bit `index` of the seen mask, or nothing for a field the mask has no
    /// room for.
    ///
    /// The comparison is live only for a struct of more than 64 fields that
    /// marks one of its first 64 required, [`MASK`](Fields::MASK) having
    /// refused every other wide reading. Anywhere else `NARROW` is a constant
    /// `true` and this is the shift alone.
    #[inline(always)]
    pub(crate) const fn seen(index: usize) -> u64 {
        if Self::NARROW || index < 64 {
            1u64 << index
        } else {
            0
        }
    }
}

/// Convenience bound for generic containers: readable and writable in every
/// format this crate supports, from any input.
///
/// [`object!`](crate::object) generates impls for all formats at once, so a
/// generic struct's type parameter needs all of them. This is the bound to
/// write:
///
/// ```ignore
/// structio::object!([T: structio::ReadWrite] Page<T> { items, cursor });
/// ```
///
/// Types that borrow from the input do not satisfy this, exactly as they do
/// not satisfy an "owned" bound elsewhere in the ecosystem. For a struct that
/// is only ever used with one format, the narrower [`json::ReadWrite`] or
/// [`beve::ReadWrite`] will do.
///
/// [`json::ReadWrite`]: crate::json::ReadWrite
/// [`beve::ReadWrite`]: crate::beve::ReadWrite
pub trait ReadWrite: crate::json::ReadWrite + crate::beve::ReadWrite {}
impl<T> ReadWrite for T where T: crate::json::ReadWrite + crate::beve::ReadWrite {}

/// The length of a struct encoded as a positional array.
///
/// The array counterpart of [`Keys`], and shared by every format for the same
/// reason: which fields there are and what order they come in is a property of
/// the type, not of the encoding. JSON writes them between brackets and BEVE
/// behind a generic-array header, but both write the same values in the same
/// order, and both refuse a document that holds a different number of them.
///
/// Generated by [`array!`](crate::array), which unlike [`object!`](crate::object)
/// emits no key list and no hash table: an element is found by counting, so
/// there is nothing to look up.
pub trait Elements {
    /// How many elements the encoded array has. Always the field count.
    const LEN: usize;
}

macro_rules! impl_tuple_elements {
    ($n:expr; $($name:ident),+) => {
        impl<$($name),+> Elements for ($($name,)+) {
            const LEN: usize = $n;
        }
    };
}

// A tuple is an array-encoded struct whose fields happen to have no names, so
// it reaches the same drivers through the same trait.
impl_tuple_elements!(1; A);
impl_tuple_elements!(2; A, B);
impl_tuple_elements!(3; A, B, C);
impl_tuple_elements!(4; A, B, C, D);
impl_tuple_elements!(5; A, B, C, D, E);
impl_tuple_elements!(6; A, B, C, D, E, F);
impl_tuple_elements!(7; A, B, C, D, E, F, G);
impl_tuple_elements!(8; A, B, C, D, E, F, G, H);
impl_tuple_elements!(9; A, B, C, D, E, F, G, H, I);
impl_tuple_elements!(10; A, B, C, D, E, F, G, H, I, J);
impl_tuple_elements!(11; A, B, C, D, E, F, G, H, I, J, K);
impl_tuple_elements!(12; A, B, C, D, E, F, G, H, I, J, K, L);

/// The identity adapter: read and write this position the way the type itself
/// would.
///
/// Adapters compose as types do, so a container adapter needs something to
/// name at a position that wants no adapting. `Vec<Same>` reads a `Vec<T>`
/// element for element as `Vec<T>`'s own impl does, and
/// `HashMap<Same, Millis>` adapts only a map's values, leaving its keys to
/// [`FromJsonKey`](crate::json::FromJsonKey) and
/// [`FromBeveKey`](crate::beve::FromBeveKey).
///
/// One type rather than one per format, because a declaration names a single
/// adapter and the macro emits it against [`json::ReadAs`](crate::json::ReadAs),
/// [`json::WriteAs`](crate::json::WriteAs),
/// [`beve::ReadAs`](crate::beve::ReadAs) and
/// [`beve::WriteAs`](crate::beve::WriteAs) alike. A per-format `Same` could
/// not be written at a field at all.
///
/// It is an identity on the bytes too, not only on the values, and in both
/// directions: `Same` forwards BEVE's
/// [`Write::ARRAY`](crate::beve::Write::ARRAY), so a `Vec<Same>` over a
/// `Vec<f64>` is still one typed array rather than a value per element, and it
/// forwards [`Read::read_bulk`](crate::beve::Read::read_bulk), so reading that
/// array back is still the single `memcpy` the unadapted field would have got.
///
/// Neither is true of an adapter in general, and neither should be. An adapter
/// with a conversion to do has no block to copy, so it leaves both alone and
/// gets a generic array and an element-by-element read. What `Same` shows is
/// that the ceiling is the adapter's, not the mechanism's: an adapter over a
/// type whose memory is already a payload can reach the same two paths, which
/// is what [`NumericBytes`](crate::beve::NumericBytes) is implementable for.
pub struct Same;