refactor(crates): implement std::error::Error for all module error enums

thiserror derive across oakundo/oakcommon/oaknode/oaktimeline/oaktask/
oakotio/oakcodec/oakaudio/oakrender/oakplugin/oakengine/oakstorage;
Display carries the module prefix and the Failed context, source()
stays default except oakotio's #[from] forwarding. code() mappings and
variants unchanged; each error.rs gains Display/object-safety/code
regression tests.
This commit is contained in:
2026-08-14 05:45:05 +08:00
parent d460d57805
commit 4240df1d71
29 changed files with 773 additions and 51 deletions
+60 -1
View File
@@ -17,6 +17,8 @@
//! Error codes, mirroring `include/node/error.h` verbatim; project-wide
//! -MMCCCC scheme (module registry in include/common/error.h), pass-through untranslated.
use thiserror::Error;
/// Success.
pub const OAKNODE_OK: i32 = 0;
/// Null handle or invalid argument.
@@ -34,17 +36,22 @@ pub const OAKNODE_E_NOMEM: i32 = -30005;
pub type Result<T> = std::result::Result<T, Error>;
/// Crate-internal error.
#[derive(Debug, PartialEq)]
#[derive(Debug, PartialEq, Error)]
pub enum Error {
/// Null handle or invalid argument.
#[error("node: empty handle or invalid argument")]
Invalid,
/// Wrong state.
#[error("node: call not valid in current state")]
State,
/// Operation failed (context string is log-only).
#[error("node: operation failed: {0}")]
Failed(String),
/// Not found.
#[error("node: entry not found")]
NotFound,
/// Out of memory.
#[error("node: allocation failed")]
NoMem,
}
@@ -60,3 +67,55 @@ impl Error {
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn public_codes_match_header_values() {
// Load-bearing values from include/node/error.h (module 03).
assert_eq!(OAKNODE_OK, 0);
assert_eq!(OAKNODE_E_INVALID, -30001);
assert_eq!(OAKNODE_E_STATE, -30002);
assert_eq!(OAKNODE_E_FAILED, -30003);
assert_eq!(OAKNODE_E_NOT_FOUND, -30004);
assert_eq!(OAKNODE_E_NOMEM, -30005);
}
#[test]
fn code_maps_each_variant() {
assert_eq!(Error::Invalid.code(), OAKNODE_E_INVALID);
assert_eq!(Error::State.code(), OAKNODE_E_STATE);
assert_eq!(Error::Failed("boom".to_string()).code(), OAKNODE_E_FAILED);
assert_eq!(Error::NotFound.code(), OAKNODE_E_NOT_FOUND);
assert_eq!(Error::NoMem.code(), OAKNODE_E_NOMEM);
}
#[test]
fn display_is_non_empty_for_each_variant() {
let variants = [
Error::Invalid,
Error::State,
Error::Failed("context".to_string()),
Error::NotFound,
Error::NoMem,
];
for e in &variants {
assert!(!e.to_string().is_empty());
}
}
#[test]
fn failed_display_includes_context() {
let e = Error::Failed("context info".to_string());
assert!(e.to_string().contains("context info"));
}
#[test]
fn error_is_object_safe() {
let e: Box<dyn std::error::Error> = Box::new(Error::NoMem);
assert!(!e.to_string().is_empty());
}
}