Skip to main content

nexcore_error/
lib.rs

1//! Zero-dependency error handling for `nexcore` ecosystem.
2//!
3//! Replaces `thiserror` and `anyhow` with zero external dependencies.
4
5#![cfg_attr(not(feature = "std"), no_std)]
6#![forbid(unsafe_code)]
7#![cfg_attr(not(test), deny(clippy::unwrap_used))]
8#![cfg_attr(not(test), deny(clippy::expect_used))]
9#![cfg_attr(not(test), deny(clippy::panic))]
10#![warn(missing_docs)]
11#[cfg(not(feature = "std"))]
12extern crate alloc;
13
14mod context;
15mod error;
16
17pub use context::Context;
18pub use error::NexError;
19
20/// Re-export the derive macro so users can `use nexcore_error::Error;`
21#[cfg(feature = "derive")]
22pub use nexcore_error_derive::Error;
23
24/// A convenient `Result` type alias using `NexError`.
25pub type Result<T, E = NexError> = core::result::Result<T, E>;
26
27/// Creates a new `NexError` from a format string.
28#[macro_export]
29macro_rules! nexerror {
30    ($fmt:literal) => { $crate::NexError::new(format!($fmt)) };
31    ($fmt:literal, $($arg:tt)*) => { $crate::NexError::new(format!($fmt, $($arg)*)) };
32}
33
34/// Return early with an error.
35#[macro_export]
36macro_rules! bail {
37    ($msg:literal) => {
38        return core::result::Result::Err($crate::NexError::new(format!($msg)).into())
39    };
40    ($err:expr) => {
41        return core::result::Result::Err($crate::NexError::from($err).into())
42    };
43    ($fmt:literal, $($arg:tt)*) => {
44        return core::result::Result::Err($crate::NexError::new(format!($fmt, $($arg)*)).into())
45    };
46}
47
48/// Return early with an error if a condition is not satisfied.
49#[macro_export]
50macro_rules! ensure {
51    ($cond:expr $(,)?) => {
52        if !($cond) {
53            return core::result::Result::Err($crate::NexError::new(concat!("Condition failed: `", stringify!($cond), "`")).into());
54        }
55    };
56    ($cond:expr, $msg:literal $(,)?) => {
57        if !($cond) {
58            return core::result::Result::Err($crate::NexError::new($msg).into());
59        }
60    };
61    ($cond:expr, $err:expr $(,)?) => {
62        if !($cond) {
63            return core::result::Result::Err($crate::NexError::from($err).into());
64        }
65    };
66    ($cond:expr, $fmt:literal, $($arg:tt)*) => {
67        if !($cond) {
68            return core::result::Result::Err($crate::NexError::new(format!($fmt, $($arg)*)).into());
69        }
70    };
71}
72
73/// Commonly used items for glob import.
74pub mod prelude {
75    pub use crate::{Context, NexError, Result, bail, ensure, nexerror};
76
77    #[cfg(feature = "derive")]
78    pub use crate::Error;
79}
80
81#[cfg(test)]
82mod tests {
83    use super::*;
84
85    #[test]
86    fn test_nexerror_msg() {
87        let err = nexerror!("test error");
88        assert_eq!(err.to_string(), "test error");
89    }
90
91    #[test]
92    fn test_nexerror_format() {
93        let err = nexerror!("error code: {}", 42);
94        assert_eq!(err.to_string(), "error code: 42");
95    }
96
97    // --- Derive macro tests ---
98
99    #[cfg(feature = "derive")]
100    mod derive_tests {
101        use super::*;
102
103        #[derive(Debug, Error)]
104        enum TestError {
105            #[error("not found")]
106            NotFound,
107
108            #[error("parse error: {0}")]
109            Parse(String),
110
111            #[error("io failed: {0}")]
112            Io(#[from] std::io::Error),
113
114            #[error("named: {msg}")]
115            Named { msg: String },
116
117            #[error("debug fmt: {0:?}")]
118            DebugFmt(Vec<String>),
119
120            #[error(transparent)]
121            Other(#[from] std::fmt::Error),
122        }
123
124        #[derive(Debug, Error)]
125        #[error("struct error: {msg}")]
126        struct StructError {
127            msg: String,
128        }
129
130        #[test]
131        fn test_unit_variant_display() {
132            let err = TestError::NotFound;
133            assert_eq!(err.to_string(), "not found");
134        }
135
136        #[test]
137        fn test_unnamed_field_display() {
138            let err = TestError::Parse("bad input".into());
139            assert_eq!(err.to_string(), "parse error: bad input");
140        }
141
142        #[test]
143        fn test_named_field_display() {
144            let err = TestError::Named { msg: "oops".into() };
145            assert_eq!(err.to_string(), "named: oops");
146        }
147
148        #[test]
149        fn test_debug_format() {
150            let err = TestError::DebugFmt(vec!["a".into(), "b".into()]);
151            assert_eq!(err.to_string(), "debug fmt: [\"a\", \"b\"]");
152        }
153
154        #[test]
155        fn test_from_io_error() {
156            let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "gone");
157            let err: TestError = io_err.into();
158            assert!(err.to_string().contains("io failed"));
159        }
160
161        #[test]
162        fn test_source_from_io() {
163            let io_err = std::io::Error::new(std::io::ErrorKind::NotFound, "gone");
164            let err: TestError = io_err.into();
165            assert!(std::error::Error::source(&err).is_some());
166        }
167
168        #[test]
169        fn test_transparent_display() {
170            let inner = std::fmt::Error;
171            let err: TestError = inner.into();
172            assert_eq!(err.to_string(), std::fmt::Error.to_string());
173        }
174
175        #[test]
176        fn test_transparent_source() {
177            let inner = std::fmt::Error;
178            let err: TestError = inner.into();
179            assert!(std::error::Error::source(&err).is_some());
180        }
181
182        #[test]
183        fn test_struct_error_display() {
184            let err = StructError {
185                msg: "broken".into(),
186            };
187            assert_eq!(err.to_string(), "struct error: broken");
188        }
189
190        #[test]
191        fn test_unit_no_source() {
192            let err = TestError::NotFound;
193            assert!(std::error::Error::source(&err).is_none());
194        }
195    }
196}