melin-app 0.18.0

Trait abstractions for pluggable applications on the Melin sequencer
Documentation
//! Wire-side request decoder seam.
//!
//! The server runtime (accept loop, frame reader, DPDK transport)
//! consumes incoming frames from the network and needs to turn them
//! into application events to publish to the pipeline. The decoding
//! itself — telling the application's messages apart, mapping
//! per-variant fields, enforcing per-connection permission policy — is
//! application-shaped: a trading server decodes order submissions, a
//! payments server decodes transfers, a logistics server decodes
//! shipment events. This trait is the seam that lets the runtime
//! delegate that decoding to the application without ever naming the
//! concrete wire enum.
//!
//! The frame around the request is not the application's. Every client
//! frame is `[length: u32 LE][tag: u8][body]`, and the tag is the
//! protocol's: the runtime reads it, handles or drops the protocol's own
//! frames, and hands the decoder the body of an application frame alone.
//! The body's layout is the application's from its first byte, with no
//! value reserved.
//!
//! The runtime calls
//! [`RequestDecoder::decode`](crate::decoder::RequestDecoder::decode) once
//! per such frame; the [`Decoded`](crate::decoder::Decoded) return value
//! encodes exactly the four outcomes the runtime acts on (drop, publish,
//! reject with reason, log decode error).

use crate::AppEvent;
use crate::auth::Permission;

/// Decode an authenticated client request into an application event the
/// runtime can publish to the pipeline.
///
/// Stateless on the connection (the runtime carries connection-level
/// state — `Permission`, `key_hash` — and feeds the relevant piece in
/// per call). Implementors are typically zero-sized types.
pub trait RequestDecoder: Send + Sync {
    /// Application event type produced on a successful decode. The
    /// runtime wraps this in a transport-level envelope (e.g.
    /// `JournalEvent::App`) before publishing.
    type Event: AppEvent;

    /// Decode one request. `body` is the application frame's body, up to
    /// the end of the frame, and may be empty. `permission` is the role
    /// established during the auth handshake and stored on the connection.
    fn decode(&self, body: &[u8], permission: Permission) -> Decoded<Self::Event>;
}

/// Outcome of a single [`RequestDecoder::decode`] call. The runtime
/// branches on this and never needs to know the underlying wire enum.
pub enum Decoded<E: AppEvent> {
    /// Drop the request silently. For application messages that are not
    /// events — a subscription request arriving on a connection that
    /// only submits events, say. The protocol's own frames never reach the
    /// decoder.
    Filter,
    /// Request OK and authorized. The runtime publishes the event. Whether
    /// it needs a timestamp is derived by the runtime from
    /// [`AppEvent::is_query`] — query events bypass the journal and skip
    /// the wall-clock stamp.
    Permitted(E),
    /// Authenticated connection lacks the permission level for this
    /// operation. The static string is logged at debug level on the
    /// reader thread; the runtime drops the request.
    PermissionDenied(&'static str),
    /// Decode failure (unknown message, malformed body, invalid field). The
    /// runtime logs at debug level and drops the request; the connection
    /// is not closed (a misbehaving client drops itself on the next read
    /// timeout).
    DecodeError(&'static str),
}