use std::fmt;
use thiserror::Error;
use crate::capability::Capability;
use crate::qname::QName;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum PluginError {
#[error("plugin manifest parse failure: {0}")]
ManifestParse(String),
#[error(
"plugin {plugin} requires uni-plugin ABI {required}; \
host supports majors {supported:?}"
)]
AbiUnsupported {
plugin: String,
required: String,
supported: Vec<u64>,
},
#[error("plugin attempted registration requiring capability {0:?}; not granted")]
CapabilityRequired(Capability),
#[error("plugin requested capability {0:?}; denied by host")]
CapabilityDenied(Capability),
#[error("duplicate registration for qualified name {0}")]
DuplicateRegistration(QName),
#[error("plugin {dependent} depends on {dep_id} (req {req}); not satisfied")]
DependencyMissing {
dependent: String,
dep_id: String,
req: String,
},
#[error("dependency cycle in plugin graph: {0:?}")]
DependencyCycle(Vec<String>),
#[error("plugin manifest signature invalid: {0}")]
SignatureInvalid(String),
#[error("plugin hash mismatch: expected {expected}, actual {actual}")]
HashMismatch {
expected: String,
actual: String,
},
#[error("WASM instantiate failure: {0}")]
WasmInstantiate(String),
#[error("Lua plugin parse failure: {0}")]
LuaParse(String),
#[error("Rhai plugin parse failure: {0}")]
RhaiParse(String),
#[error("invalid qualified name: `{0}`")]
InvalidQName(String),
#[error("logical-type conflict: extension name `{0}` already registered")]
LogicalTypeConflict(String),
#[error("storage scheme `{0}` already registered")]
StorageSchemeConflict(String),
#[error("internal plugin-framework error: {0}")]
Internal(String),
}
impl PluginError {
#[must_use]
pub fn internal(message: impl Into<String>) -> Self {
Self::Internal(message.into())
}
}
#[derive(Clone, Debug)]
pub struct FnError {
pub code: u32,
pub message: String,
pub retryable: bool,
}
impl FnError {
#[must_use]
pub fn new(code: u32, message: impl Into<String>) -> Self {
Self {
code,
message: message.into(),
retryable: false,
}
}
#[must_use]
pub fn retryable(code: u32, message: impl Into<String>) -> Self {
Self {
code,
message: message.into(),
retryable: true,
}
}
pub const CODE_UNKNOWN_FUNCTION: u32 = 0x01;
pub const CODE_TYPE_COERCION: u32 = 0x02;
pub const CODE_UNEXPECTED_NULL: u32 = 0x03;
pub const CODE_RESOURCE_LIMIT: u32 = 0x04;
pub const CODE_FORBIDDEN: u32 = 0x05;
#[must_use]
pub fn unknown_function(name: impl AsRef<str>) -> Self {
Self::new(
Self::CODE_UNKNOWN_FUNCTION,
format!("unknown function: {}", name.as_ref()),
)
}
}
impl fmt::Display for FnError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"plugin fn error (code={}, retryable={}): {}",
self.code, self.retryable, self.message
)
}
}
impl std::error::Error for FnError {}
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum ReloadError {
#[error("drain failure during reload: {0}")]
Drain(String),
#[error("schema-incompat for {kind}: {reason}")]
SchemaIncompat {
kind: String,
reason: String,
},
#[error("persist/restore failure during reload: {0}")]
Persist(FnError),
#[error(transparent)]
Plugin(#[from] PluginError),
#[error("plugin {0} not found in host registry")]
PluginNotFound(String),
}
impl ReloadError {
#[must_use]
pub fn schema_incompat(kind: impl Into<String>, reason: impl Into<String>) -> Self {
Self::SchemaIncompat {
kind: kind.into(),
reason: reason.into(),
}
}
}
#[derive(Debug)]
#[non_exhaustive]
pub enum HookOutcome {
Continue,
Reject {
reason: String,
},
}
impl HookOutcome {
#[must_use]
pub fn reject(reason: impl Into<String>) -> Self {
Self::Reject {
reason: reason.into(),
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn fn_error_constructors() {
let e = FnError::new(0x100, "boom");
assert_eq!(e.code, 0x100);
assert!(!e.retryable);
assert_eq!(e.message, "boom");
let e = FnError::retryable(0x101, "transient");
assert!(e.retryable);
let e = FnError::unknown_function("nope");
assert_eq!(e.code, FnError::CODE_UNKNOWN_FUNCTION);
assert!(e.message.contains("nope"));
}
#[test]
fn plugin_error_internal_constructor() {
let e = PluginError::internal("oops");
match e {
PluginError::Internal(message) => assert_eq!(message, "oops"),
other => panic!("expected Internal, got {other:?}"),
}
}
#[test]
fn plugin_error_display_contains_context() {
let e = PluginError::HashMismatch {
expected: "abc".to_owned(),
actual: "def".to_owned(),
};
let s = e.to_string();
assert!(s.contains("abc"));
assert!(s.contains("def"));
}
}