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}