netsky-core 0.1.2

netsky core: agent model, prompt loader, spawner, config
Documentation
//! The netsky error taxonomy.
//!
//! We speak in typed variants, not untyped messages. Each variant
//! names a concrete boundary where recovery is meaningful: I/O,
//! subprocess timeout vs nonzero exit, missing dep, tmux, prompt
//! render, invalid input. A residual `Message` catch-all exists for
//! one-shot error sites that don't yet warrant a dedicated variant —
//! prefer promoting to a typed variant over using `Message` long-term.
//!
//! Display + std::error::Error + From impls are hand-rolled — no
//! thiserror. Keeps the error surface transparent at the expense of
//! a few lines per variant; worth it for zero macro deps.

use std::io;

/// Convenience alias used across the workspace.
pub type Result<T> = std::result::Result<T, Error>;

#[derive(Debug)]
pub enum Error {
    Io(io::Error),
    Json(serde_json::Error),
    Prompt(crate::prompt::PromptError),
    Tmux(String),
    MissingDep(&'static str),
    SubprocessTimeout {
        what: String,
        ceiling_s: u64,
    },
    SubprocessFailed {
        what: String,
        code: i32,
    },
    Invalid(String),
    /// Residual catch-all for sites not yet promoted to a typed
    /// variant. Prefer to add a new variant when introducing a new
    /// error class — `Message` is for stragglers, not the happy path.
    Message(String),
}

impl Error {
    pub fn msg(s: impl Into<String>) -> Self {
        Self::Message(s.into())
    }
}

impl std::fmt::Display for Error {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Self::Io(e) => write!(f, "I/O: {e}"),
            Self::Json(e) => write!(f, "JSON: {e}"),
            Self::Prompt(e) => write!(f, "prompt: {e}"),
            Self::Tmux(s) => write!(f, "tmux: {s}"),
            Self::MissingDep(s) => write!(f, "missing dep on PATH: {s}"),
            Self::SubprocessTimeout { what, ceiling_s } => {
                write!(f, "subprocess `{what}` timed out after {ceiling_s}s")
            }
            Self::SubprocessFailed { what, code } => {
                write!(f, "subprocess `{what}` failed (exit {code})")
            }
            Self::Invalid(s) => write!(f, "invalid input: {s}"),
            Self::Message(s) => f.write_str(s),
        }
    }
}

impl std::error::Error for Error {
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match self {
            Self::Io(e) => Some(e),
            Self::Json(e) => Some(e),
            Self::Prompt(e) => Some(e),
            _ => None,
        }
    }
}

impl From<io::Error> for Error {
    fn from(e: io::Error) -> Self {
        Self::Io(e)
    }
}

impl From<serde_json::Error> for Error {
    fn from(e: serde_json::Error) -> Self {
        Self::Json(e)
    }
}

impl From<crate::prompt::PromptError> for Error {
    fn from(e: crate::prompt::PromptError) -> Self {
        Self::Prompt(e)
    }
}

impl From<netsky_sh::Error> for Error {
    fn from(e: netsky_sh::Error) -> Self {
        // netsky_sh covers tmux + shell + which; render as Tmux since
        // that's where every current use site lives. Promote when we
        // grow distinct branches worth distinguishing.
        Self::Tmux(format!("{e}"))
    }
}

impl From<std::time::SystemTimeError> for Error {
    fn from(e: std::time::SystemTimeError) -> Self {
        Self::Message(format!("system time: {e}"))
    }
}

impl From<std::num::ParseIntError> for Error {
    fn from(e: std::num::ParseIntError) -> Self {
        Self::Invalid(format!("parse int: {e}"))
    }
}

/// Early-return with an `Error::Message`. Drop-in for `anyhow::bail!`.
/// Single-literal form supports captured identifiers via `format!`'s
/// Rust 2021 implicit named args.
#[macro_export]
macro_rules! bail {
    ($fmt:literal $(,)?) => {
        return Err($crate::error::Error::Message(format!($fmt)))
    };
    ($fmt:expr, $($arg:tt)*) => {
        return Err($crate::error::Error::Message(format!($fmt, $($arg)*)))
    };
}

/// Construct an `Error::Message` from a format string. Drop-in for
/// `anyhow::anyhow!`. Useful inside `.map_err(|e| netsky_core::anyhow!("...: {e}"))`.
#[macro_export]
macro_rules! anyhow {
    ($fmt:literal $(,)?) => {
        $crate::error::Error::Message(format!($fmt))
    };
    ($fmt:expr, $($arg:tt)*) => {
        $crate::error::Error::Message(format!($fmt, $($arg)*))
    };
}