Skip to main content

netscli_core/
error.rs

1//! Public error type for `netscli-core`.
2//!
3//! Library consumers can pattern-match on [`Error`] variants instead
4//! of passing an opaque `anyhow::Error` around. The enum is marked
5//! `#[non_exhaustive]` so new categories can be added without being
6//! breaking changes — match arms should use a trailing `_ =>` fallback.
7//!
8//! All public functions in this crate return `Result<T, Error>`. A few
9//! private implementation helpers (e.g. ping's ICMP round-trip inside
10//! `PingScanner::ping`) still use `anyhow::Error` internally because
11//! they never leak their error type to consumers, and the
12//! [`From<anyhow::Error>`](Error) bridge would convert them to
13//! `Error::Other` anyway.
14
15use std::io;
16
17use thiserror::Error;
18
19/// Errors produced by `netscli-core` operations.
20///
21/// Variants group failures by category rather than by call site so
22/// consumers can write one match arm per *kind* of problem (`Dns`,
23/// `InvalidInput`, etc.) instead of per specific operation.
24#[derive(Debug, Error)]
25#[non_exhaustive]
26pub enum Error {
27    /// The caller supplied bad input (malformed IP, port 0, empty
28    /// host, subnet too large, etc). Recoverable by the caller
29    /// correcting the input.
30    #[error("invalid input: {0}")]
31    InvalidInput(String),
32
33    /// DNS resolution failed. Wraps the reason as a string because
34    /// `hickory-resolver` errors aren't `Clone` and their variants
35    /// aren't stable enough to re-export directly.
36    #[error("DNS resolution failed: {0}")]
37    Dns(String),
38
39    /// A socket-level network operation failed (connection refused,
40    /// unreachable, host down, etc).
41    #[error("network error: {0}")]
42    Network(String),
43
44    /// An operation exceeded its configured timeout. The value is
45    /// the timeout that was hit, in milliseconds.
46    #[error("timed out after {0}ms")]
47    Timeout(u64),
48
49    /// The requested operation isn't available in this build (e.g.
50    /// packet capture without the `pcap` feature) or on this platform.
51    #[error("unsupported: {0}")]
52    Unsupported(String),
53
54    /// Standard I/O error (file open, file write, socket bind, etc).
55    #[error("I/O error: {0}")]
56    Io(#[from] io::Error),
57
58    /// SQLite / sqlx error. Only present with the `db` feature.
59    #[cfg(feature = "db")]
60    #[error("database error: {0}")]
61    Database(#[from] sqlx::Error),
62
63    /// libpcap / Npcap error. Only present with the `pcap` feature.
64    #[cfg(feature = "pcap")]
65    #[error("pcap error: {0}")]
66    Pcap(#[from] ::pcap::Error),
67
68    /// Fallback for errors that haven't been categorised yet.
69    /// New variants will land here and move out as modules convert
70    /// from `anyhow::Error` to this type.
71    #[error("{0}")]
72    Other(String),
73}
74
75/// `netscli-core`'s public result type. Aliased so callers can write
76/// `netscli_core::Result<T>` without importing both.
77pub type Result<T> = std::result::Result<T, Error>;
78
79// Bridge from anyhow so modules that still use anyhow internally can
80// `?` into this type. The anyhow::Error gets rendered via Display so
81// we don't lose the message, just the opaque type.
82impl From<anyhow::Error> for Error {
83    fn from(err: anyhow::Error) -> Self {
84        Error::Other(err.to_string())
85    }
86}
87
88// Also allow our Error to be wrapped in anyhow where callers haven't
89// been converted yet. Makes cross-conversion lossless in both directions.
90impl Error {
91    /// Convenience constructor for `Error::InvalidInput` that accepts
92    /// anything `Display`able so call sites stay terse.
93    pub fn invalid_input<T: std::fmt::Display>(msg: T) -> Self {
94        Error::InvalidInput(msg.to_string())
95    }
96
97    /// Convenience constructor for `Error::Dns`.
98    pub fn dns<T: std::fmt::Display>(msg: T) -> Self {
99        Error::Dns(msg.to_string())
100    }
101
102    /// Convenience constructor for `Error::Network`.
103    pub fn network<T: std::fmt::Display>(msg: T) -> Self {
104        Error::Network(msg.to_string())
105    }
106
107    /// Convenience constructor for `Error::Unsupported`.
108    pub fn unsupported<T: std::fmt::Display>(msg: T) -> Self {
109        Error::Unsupported(msg.to_string())
110    }
111}