use std::collections::HashMap;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum PluginError {
#[error("plugin '{plugin_name}' failed: {message}")]
Execution {
plugin_name: String,
message: String,
#[source]
source: Option<Box<dyn std::error::Error + Send + Sync>>,
code: Option<String>,
details: HashMap<String, serde_json::Value>,
proto_error_code: Option<i64>,
},
#[error("plugin '{plugin_name}' timed out after {timeout_ms}ms")]
Timeout {
plugin_name: String,
timeout_ms: u64,
proto_error_code: Option<i64>,
},
#[error("plugin '{plugin_name}' denied: {}", violation.reason)]
Violation {
plugin_name: String,
violation: PluginViolation,
},
#[error("configuration error: {message}")]
Config { message: String },
#[error("unknown hook type: {hook_type}")]
UnknownHook { hook_type: String },
}
impl PluginError {
pub fn boxed(self) -> Box<Self> {
Box::new(self)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginErrorRecord {
pub plugin_name: String,
pub message: String,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub code: Option<String>,
#[serde(default, skip_serializing_if = "HashMap::is_empty")]
pub details: HashMap<String, serde_json::Value>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub proto_error_code: Option<i64>,
}
impl From<&Box<PluginError>> for PluginErrorRecord {
fn from(e: &Box<PluginError>) -> Self {
PluginErrorRecord::from(e.as_ref())
}
}
impl From<&PluginError> for PluginErrorRecord {
fn from(e: &PluginError) -> Self {
match e {
PluginError::Execution {
plugin_name,
message,
code,
details,
proto_error_code,
..
} => Self {
plugin_name: plugin_name.clone(),
message: message.clone(),
code: code.clone(),
details: details.clone(),
proto_error_code: *proto_error_code,
},
PluginError::Timeout {
plugin_name,
timeout_ms,
proto_error_code,
} => Self {
plugin_name: plugin_name.clone(),
message: format!("plugin timed out after {}ms", timeout_ms),
code: Some("timeout".into()),
details: HashMap::new(),
proto_error_code: *proto_error_code,
},
PluginError::Violation {
plugin_name,
violation,
} => Self {
plugin_name: plugin_name.clone(),
message: format!("plugin denied: {}", violation.reason),
code: Some(violation.code.clone()),
details: violation.details.clone(),
proto_error_code: violation.proto_error_code,
},
PluginError::Config { message } => Self {
plugin_name: String::new(),
message: message.clone(),
code: Some("config".into()),
details: HashMap::new(),
proto_error_code: None,
},
PluginError::UnknownHook { hook_type } => Self {
plugin_name: String::new(),
message: format!("unknown hook type: {}", hook_type),
code: Some("unknown_hook".into()),
details: HashMap::new(),
proto_error_code: None,
},
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PluginViolation {
pub code: String,
pub reason: String,
pub description: Option<String>,
pub details: HashMap<String, serde_json::Value>,
pub plugin_name: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub proto_error_code: Option<i64>,
}
impl PluginViolation {
pub fn new(code: impl Into<String>, reason: impl Into<String>) -> Self {
Self {
code: code.into(),
reason: reason.into(),
description: None,
details: HashMap::new(),
plugin_name: None,
proto_error_code: None,
}
}
pub fn with_description(mut self, description: impl Into<String>) -> Self {
self.description = Some(description.into());
self
}
pub fn with_details(mut self, details: HashMap<String, serde_json::Value>) -> Self {
self.details = details;
self
}
pub fn with_proto_error_code(mut self, code: i64) -> Self {
self.proto_error_code = Some(code);
self
}
}
impl std::fmt::Display for PluginViolation {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "[{}] {}", self.code, self.reason)
}
}