feat(llvm): Phase 97 Box/Policy refactoring complete
Box化完了: - CallRoutePolicyBox: Call routing SSoT - PrintArgMarshallerBox: Print marshalling SSoT - TypeFactsBox: Type propagation SSoT - PhiSnapshotPolicyBox: PHI contract SSoT - PluginErrorContext: Structured error reporting 📋 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
181
src/runtime/plugin_loader_v2/enabled/loader/error_reporter.rs
Normal file
181
src/runtime/plugin_loader_v2/enabled/loader/error_reporter.rs
Normal file
@ -0,0 +1,181 @@
|
||||
/// Phase 97 Refactoring: Structured Error Reporter Box for Plugin Loader
|
||||
///
|
||||
/// This module provides structured error reporting with clear context,
|
||||
/// attempted paths, and actionable hints for plugin loading failures.
|
||||
|
||||
use crate::bid::BidError;
|
||||
use std::path::PathBuf;
|
||||
|
||||
/// Structured plugin error information
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct PluginErrorContext {
|
||||
pub kind: PluginErrorKind,
|
||||
pub plugin_name: String,
|
||||
pub message: String,
|
||||
pub attempted_paths: Vec<String>,
|
||||
pub hint: Option<String>,
|
||||
}
|
||||
|
||||
/// Plugin error kind classification
|
||||
#[derive(Debug, Clone, PartialEq)]
|
||||
pub enum PluginErrorKind {
|
||||
/// Plugin library file not found
|
||||
MissingLibrary,
|
||||
/// dlopen() failed
|
||||
LoadFailed,
|
||||
/// Plugin initialization failed
|
||||
InitFailed,
|
||||
/// Version mismatch
|
||||
VersionMismatch,
|
||||
}
|
||||
|
||||
impl PluginErrorContext {
|
||||
/// Create error context for missing plugin
|
||||
pub fn missing_library(
|
||||
plugin_name: &str,
|
||||
configured_path: &str,
|
||||
attempted_paths: Vec<PathBuf>,
|
||||
) -> Self {
|
||||
let paths_str: Vec<String> = attempted_paths
|
||||
.iter()
|
||||
.map(|p| p.display().to_string())
|
||||
.collect();
|
||||
|
||||
Self {
|
||||
kind: PluginErrorKind::MissingLibrary,
|
||||
plugin_name: plugin_name.to_string(),
|
||||
message: format!(
|
||||
"Plugin '{}' not found at configured path: {}",
|
||||
plugin_name, configured_path
|
||||
),
|
||||
attempted_paths: paths_str,
|
||||
hint: Some(
|
||||
"Check LD_LIBRARY_PATH or configure nyash.toml [libraries] section"
|
||||
.to_string(),
|
||||
),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create error context for load failure
|
||||
pub fn load_failed(
|
||||
plugin_name: &str,
|
||||
path: &str,
|
||||
error_msg: &str,
|
||||
) -> Self {
|
||||
Self {
|
||||
kind: PluginErrorKind::LoadFailed,
|
||||
plugin_name: plugin_name.to_string(),
|
||||
message: format!(
|
||||
"Failed to load plugin '{}' from {}: {}",
|
||||
plugin_name, path, error_msg
|
||||
),
|
||||
attempted_paths: vec![path.to_string()],
|
||||
hint: Some("Check plugin architecture (32/64-bit) and dependencies".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create error context for init failure
|
||||
pub fn init_failed(plugin_name: &str, error_msg: &str) -> Self {
|
||||
Self {
|
||||
kind: PluginErrorKind::InitFailed,
|
||||
plugin_name: plugin_name.to_string(),
|
||||
message: format!(
|
||||
"Plugin '{}' initialization failed: {}",
|
||||
plugin_name, error_msg
|
||||
),
|
||||
attempted_paths: vec![],
|
||||
hint: Some("Check plugin logs for initialization errors".to_string()),
|
||||
}
|
||||
}
|
||||
|
||||
/// Log structured error using global ring0 logger
|
||||
pub fn log_structured(&self) {
|
||||
use crate::runtime::get_global_ring0;
|
||||
|
||||
let ring0 = get_global_ring0();
|
||||
|
||||
match self.kind {
|
||||
PluginErrorKind::MissingLibrary => {
|
||||
ring0.log.error(&format!("[plugin/missing] {}", self.message));
|
||||
if !self.attempted_paths.is_empty() {
|
||||
ring0.log.error(&format!(
|
||||
"[plugin/missing] Attempted paths: {}",
|
||||
self.attempted_paths.join(", ")
|
||||
));
|
||||
}
|
||||
if let Some(ref hint) = self.hint {
|
||||
ring0.log.warn(&format!("[plugin/hint] {}", hint));
|
||||
}
|
||||
}
|
||||
PluginErrorKind::LoadFailed => {
|
||||
ring0.log.error(&format!("[plugin/init] {}", self.message));
|
||||
if let Some(ref hint) = self.hint {
|
||||
ring0.log.warn(&format!("[plugin/hint] {}", hint));
|
||||
}
|
||||
}
|
||||
PluginErrorKind::InitFailed => {
|
||||
ring0.log.error(&format!("[plugin/init] {}", self.message));
|
||||
if let Some(ref hint) = self.hint {
|
||||
ring0.log.warn(&format!("[plugin/hint] {}", hint));
|
||||
}
|
||||
}
|
||||
PluginErrorKind::VersionMismatch => {
|
||||
ring0.log.error(&format!("[plugin/version] {}", self.message));
|
||||
if let Some(ref hint) = self.hint {
|
||||
ring0.log.warn(&format!("[plugin/hint] {}", hint));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Convert to BidError
|
||||
pub fn to_bid_error(&self) -> BidError {
|
||||
match self.kind {
|
||||
PluginErrorKind::MissingLibrary => BidError::PluginError,
|
||||
PluginErrorKind::LoadFailed => BidError::PluginError,
|
||||
PluginErrorKind::InitFailed => BidError::PluginError,
|
||||
PluginErrorKind::VersionMismatch => BidError::VersionMismatch,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Helper function for logging and returning error
|
||||
pub fn report_and_fail(ctx: PluginErrorContext) -> BidError {
|
||||
ctx.log_structured();
|
||||
ctx.to_bid_error()
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_missing_library_context() {
|
||||
let ctx = PluginErrorContext::missing_library(
|
||||
"test_plugin",
|
||||
"/usr/lib/test.so",
|
||||
vec![
|
||||
PathBuf::from("/usr/lib/test.so"),
|
||||
PathBuf::from("/usr/lib/libtest.so"),
|
||||
],
|
||||
);
|
||||
|
||||
assert_eq!(ctx.kind, PluginErrorKind::MissingLibrary);
|
||||
assert_eq!(ctx.plugin_name, "test_plugin");
|
||||
assert_eq!(ctx.attempted_paths.len(), 2);
|
||||
assert!(ctx.hint.is_some());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_load_failed_context() {
|
||||
let ctx = PluginErrorContext::load_failed(
|
||||
"test_plugin",
|
||||
"/usr/lib/test.so",
|
||||
"undefined symbol: foo",
|
||||
);
|
||||
|
||||
assert_eq!(ctx.kind, PluginErrorKind::LoadFailed);
|
||||
assert!(ctx.message.contains("undefined symbol"));
|
||||
assert_eq!(ctx.attempted_paths.len(), 1);
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,4 @@
|
||||
use super::error_reporter::{report_and_fail, PluginErrorContext};
|
||||
use super::specs;
|
||||
use super::util::dbg_on;
|
||||
use super::PluginLoaderV2;
|
||||
@ -96,19 +97,13 @@ pub(super) fn load_plugin(
|
||||
let lib_path = match lib_path {
|
||||
Some(path) => path,
|
||||
None => {
|
||||
let mut attempted = candidates
|
||||
.iter()
|
||||
.map(|p| p.display().to_string())
|
||||
.collect::<Vec<_>>();
|
||||
attempted.sort();
|
||||
attempted.dedup();
|
||||
get_global_ring0().log.error(&format!(
|
||||
"[plugin/missing] {}: no existing file for configured path='{}' (attempted={})",
|
||||
// Phase 97: Use structured error reporter
|
||||
let ctx = PluginErrorContext::missing_library(
|
||||
lib_name,
|
||||
base.display(),
|
||||
attempted.join(", ")
|
||||
));
|
||||
return Err(BidError::PluginError);
|
||||
&base.display().to_string(),
|
||||
candidates,
|
||||
);
|
||||
return Err(report_and_fail(ctx));
|
||||
}
|
||||
};
|
||||
if dbg_on() {
|
||||
@ -119,13 +114,13 @@ pub(super) fn load_plugin(
|
||||
));
|
||||
}
|
||||
let lib = unsafe { Library::new(&lib_path) }.map_err(|e| {
|
||||
get_global_ring0().log.error(&format!(
|
||||
"[plugin/init] dlopen failed for {} ({}): {}",
|
||||
// Phase 97: Use structured error reporter
|
||||
let ctx = PluginErrorContext::load_failed(
|
||||
lib_name,
|
||||
lib_path.display(),
|
||||
e
|
||||
));
|
||||
BidError::PluginError
|
||||
&lib_path.display().to_string(),
|
||||
&e.to_string(),
|
||||
);
|
||||
report_and_fail(ctx)
|
||||
})?;
|
||||
let lib_arc = Arc::new(lib);
|
||||
|
||||
@ -197,3 +192,80 @@ fn candidate_paths(base: &Path) -> Vec<PathBuf> {
|
||||
}
|
||||
candidates
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::config::nyash_toml_v2::{NyashConfigV2, PluginPaths};
|
||||
use std::env;
|
||||
|
||||
struct EnvGuard {
|
||||
key: &'static str,
|
||||
original: Option<String>,
|
||||
}
|
||||
|
||||
impl EnvGuard {
|
||||
fn set(key: &'static str, value: &str) -> Self {
|
||||
let original = env::var(key).ok();
|
||||
env::set_var(key, value);
|
||||
Self { key, original }
|
||||
}
|
||||
|
||||
fn unset(key: &'static str) -> Self {
|
||||
let original = env::var(key).ok();
|
||||
env::remove_var(key);
|
||||
Self { key, original }
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for EnvGuard {
|
||||
fn drop(&mut self) {
|
||||
if let Some(val) = &self.original {
|
||||
env::set_var(self.key, val);
|
||||
} else {
|
||||
env::remove_var(self.key);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn loader_with_missing_library(path: &str) -> PluginLoaderV2 {
|
||||
let mut libraries = HashMap::new();
|
||||
libraries.insert(
|
||||
"missing_lib".to_string(),
|
||||
LibraryDefinition {
|
||||
boxes: vec!["FileBox".to_string()],
|
||||
path: path.to_string(),
|
||||
},
|
||||
);
|
||||
PluginLoaderV2 {
|
||||
config: Some(NyashConfigV2 {
|
||||
libraries,
|
||||
plugin_paths: PluginPaths::default(),
|
||||
plugins: HashMap::new(),
|
||||
box_types: HashMap::new(),
|
||||
}),
|
||||
..PluginLoaderV2::new()
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_all_plugins_strict_fails_on_missing_library() {
|
||||
let _guard = EnvGuard::set("HAKO_JOINIR_STRICT", "1");
|
||||
let loader = loader_with_missing_library("/nonexistent/libnyash_filebox_plugin");
|
||||
|
||||
let result = load_all_plugins(&loader);
|
||||
assert!(result.is_err(), "strict mode must fail when library is missing");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn load_all_plugins_best_effort_continues_on_missing_library() {
|
||||
let _guard = EnvGuard::unset("HAKO_JOINIR_STRICT");
|
||||
let loader = loader_with_missing_library("/nonexistent/libnyash_filebox_plugin");
|
||||
|
||||
let result = load_all_plugins(&loader);
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"non-strict mode should continue even when a library is missing"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
mod config;
|
||||
mod error_reporter;
|
||||
mod library;
|
||||
mod metadata;
|
||||
mod singletons;
|
||||
|
||||
Reference in New Issue
Block a user