minerva 0.2.0

Causal ordering for distributed systems
use thiserror::Error;

use crate::kairos::DecodeError as KairosDecodeError;
use crate::metis::HaveSetDecodeError;

/// Decode failure for a [`Rhapsody`](crate::metis::Rhapsody) wire frame (S117).
///
/// This error is distinct from the two it wraps:
/// [`HaveSetDecodeError`](crate::metis::HaveSetDecodeError) for the visible
/// suffix, and the `Kairos` [`DecodeError`](crate::kairos::DecodeError) for a
/// rank. The rhapsody frame is a separate codec, with its own version space,
/// its own canonical-form rules, and its own structural rules (the skeleton
/// order, the anchor cross-reference). It therefore owns its own error rather
/// than sharing either wrapped one. `#[non_exhaustive]` so a future version's
/// validation failure is additive (ruling R-8).
#[non_exhaustive]
#[derive(Error, Debug, Clone, PartialEq, Eq)]
pub enum RhapsodyDecodeError {
    /// The leading version byte is not a version this build understands.
    #[error("unknown rhapsody wire version: {0:#04x}")]
    UnknownVersion(u8),
    /// The input was shorter than the declared locus count requires, or (for
    /// [`Rhapsody::from_bytes`](crate::metis::Rhapsody::from_bytes)) carried
    /// trailing bytes past the frame.
    #[error("unexpected rhapsody frame length: expected at least {expected}, found {found}")]
    UnexpectedLength {
        /// A lower bound on the frame length the declared count requires.
        expected: usize,
        /// The actual length of the supplied input.
        found: usize,
    },
    /// The declared locus count exceeds the identity plane's live-entry
    /// ceiling (`2^31 - 2`), so no decoded value could hold every record:
    /// decode refuses rather than construct a truncated skeleton (S199).
    #[error("rhapsody frame declares {count} loci, past the plane's {capacity} ceiling")]
    TooManyLoci {
        /// The declared locus count.
        count: u64,
        /// The plane's live-entry ceiling.
        capacity: u64,
    },
    /// A locus's woven dot carried `index == 0`. The non-dot `0` is refused by
    /// [`Rhapsody::weave`](crate::metis::Rhapsody::weave), so no encoder
    /// produces one.
    #[error("non-canonical rhapsody: zero index for station {station}")]
    ZeroDot {
        /// The station of the offending zero-index dot.
        station: u32,
    },
    /// Locus records were not in strictly ascending woven-dot order (unsorted, or a
    /// duplicate dot), so the frame is non-canonical: the skeleton is a map keyed by
    /// dot, and its encoding walks that map in order.
    #[error("non-canonical rhapsody: dot {found:?} is not strictly after {previous:?}")]
    NonAscendingLoci {
        /// The preceding locus's woven dot.
        previous: (u32, u64),
        /// The offending woven dot, not strictly greater than `previous`.
        found: (u32, u64),
    },
    /// A locus's anchor tag byte was none of `0x00` (origin), `0x01` (after), or
    /// `0x02` (before).
    #[error("non-canonical rhapsody: unknown anchor tag {tag:#04x}")]
    BadAnchorTag {
        /// The unrecognized tag byte.
        tag: u8,
    },
    /// The visible have-set named a dot with no locus in the skeleton. Visibility
    /// is a subset of the skeleton by construction, so a frame whose have-set
    /// over-reaches the skeleton is inconsistent.
    #[error("structurally invalid rhapsody: visible dot ({station}, {index}) has no locus")]
    VisibleWithoutLocus {
        /// The visible-but-unwoven dot's station.
        station: u32,
        /// The visible-but-unwoven dot's index.
        index: u64,
    },
    /// A locus's 17-byte rank frame failed to decode as a
    /// [`Kairos`](crate::kairos::Kairos) stamp.
    #[error("rhapsody rank frame: {0}")]
    Rank(#[source] KairosDecodeError),
    /// The trailing visible have-set frame failed to decode.
    #[error("rhapsody visible frame: {0}")]
    Visible(#[source] HaveSetDecodeError),
}