webrune 0.1.2

A composable web server.
Documentation
use std::convert::Infallible;
use std::ops::ControlFlow;
use std::ops::FromResidual;

/// Re-export of [`Flow::Continue`] for ergonomic pattern matching.
pub use Flow::Continue;

/// Re-export of [`Flow::Exit`] for ergonomic pattern matching.
pub use Flow::Exit;

/// A control-flow enum representing either continuation or early exit.
///
/// `Flow` is similar in spirit to [`Result`] or [`ControlFlow`], but is intended
/// to model *structured early exits* that are not necessarily errors.
///
/// It is commonly used to:
/// - continue normal execution with a value ([`Continue`]), or
/// - exit early with an alternate value ([`Exit`]).
///
/// Unlike [`Result`], both branches are considered valid outcomes.
///
/// # Examples
///
/// ```
/// use crate::Flow::{Continue, Exit};
///
/// fn step(x: i32) -> Flow<i32, i32> {
///     if x > 10 {
///         Exit(x)
///     } else {
///         Continue(x + 1)
///     }
/// }
/// ```
///
/// # `?` operator
///
/// `Flow` implements [`std::ops::Try`], allowing the `?` operator to be used to
/// propagate [`Exit`] values while unwrapping [`Continue`] values.
///
/// ```
/// use crate::Flow::{Continue, Exit};
///
/// fn process(x: i32) -> Flow<i32, &'static str> {
///     let value = Continue(x + 1)?;
///
///     if value > 5 {
///         Exit("too large")
///     } else {
///         Continue(value)
///     }
/// }
/// ```
pub enum Flow<C, E> {
    /// Continue execution with the given value.
    Continue(C),

    /// Exit early with the given value.
    Exit(E),
}

impl<C, E> Flow<C, E> {
    /// Maps a function over the [`Continue`] value, leaving [`Exit`] untouched.
    ///
    /// This is analogous to [`Result::map`].
    ///
    /// # Examples
    ///
    /// ```
    /// use crate::Flow::{Continue, Exit};
    ///
    /// let flow = Continue(2).map(|x| x * 2);
    /// assert!(matches!(flow, Continue(4)));
    /// ```
    pub fn map<F, C2>(self, f: F) -> Flow<C2, E>
    where
        F: FnOnce(C) -> C2,
    {
        match self {
            Flow::Continue(v) => Flow::Continue(f(v)),
            Flow::Exit(e) => Flow::Exit(e),
        }
    }

    /// Maps a function over the [`Exit`] value, leaving [`Continue`] untouched.
    ///
    /// This is analogous to [`Result::map_err`].
    ///
    /// # Examples
    ///
    /// ```
    /// use crate::Flow::{Continue, Exit};
    ///
    /// let flow = Exit("oops").map_exception(|e| e.len());
    /// assert!(matches!(flow, Exit(4)));
    /// ```
    pub fn map_exception<F, E2>(self, f: F) -> Flow<C, E2>
    where
        F: FnOnce(E) -> E2,
    {
        match self {
            Flow::Continue(v) => Flow::Continue(v),
            Flow::Exit(e) => Flow::Exit(f(e)),
        }
    }
}

impl<C, E> std::ops::Try for Flow<C, E> {
    /// The value produced when continuing.
    type Output = C;

    /// The residual type used to propagate early exits.
    ///
    /// `Infallible` is used to indicate that a residual can only
    /// represent an [`Exit`].
    type Residual = Flow<Infallible, E>;

    fn from_output(output: Self::Output) -> Self {
        Continue(output)
    }

    fn branch(self) -> std::ops::ControlFlow<Self::Residual, Self::Output> {
        match self {
            Flow::Continue(value) => ControlFlow::Continue(value),
            Flow::Exit(e) => ControlFlow::Break(Flow::Exit(e)),
        }
    }
}

impl<C, E> FromResidual for Flow<C, E> {
    /// Reconstructs a [`Flow`] from a propagated residual.
    fn from_residual(residual: <Self as std::ops::Try>::Residual) -> Self {
        match residual {
            Flow::Exit(e) => Flow::Exit(e),
            Flow::Continue(_) => unreachable!("This should never be reached."),
        }
    }
}