use super::id::CapabilityId;
use std::fmt;
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum CapabilityError {
Missing(CapabilityId),
Conflicting {
cap: CapabilityId,
first: String,
second: String,
},
}
impl fmt::Display for CapabilityError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Missing(id) => write!(f, "missing capability {id:?}"),
Self::Conflicting { cap, first, second } => {
write!(f, "conflicting providers for {cap:?}: {first} and {second}")
}
}
}
}
impl std::error::Error for CapabilityError {}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct ProviderError {
pub provider: &'static str,
pub message: String,
}
impl fmt::Display for ProviderError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "provider {} failed: {}", self.provider, self.message)
}
}
impl std::error::Error for ProviderError {}
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum CapabilityPlannerError {
DuplicateProviderId {
id: String,
},
ConflictingProvider {
cap: String,
first: String,
second: String,
},
MissingProvider {
provider: String,
cap: String,
},
CycleDetected {
nodes: Vec<String>,
},
}
impl CapabilityPlannerError {
#[inline]
pub fn code(&self) -> &'static str {
match self {
Self::DuplicateProviderId { .. } => "duplicate-provider-id",
Self::ConflictingProvider { .. } => "conflicting-provider",
Self::MissingProvider { .. } => "missing-provider",
Self::CycleDetected { .. } => "cycle-detected",
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CapabilityDiagnostic {
pub code: &'static str,
pub message: String,
pub suggestion: String,
}
impl CapabilityPlannerError {
pub fn to_diagnostic(&self) -> CapabilityDiagnostic {
match self {
Self::DuplicateProviderId { id } => CapabilityDiagnostic {
code: self.code(),
message: format!("Duplicate provider id `{id}`."),
suggestion: String::from("Ensure each provider has a unique id."),
},
Self::ConflictingProvider { cap, first, second } => CapabilityDiagnostic {
code: self.code(),
message: format!("Conflicting providers for capability `{cap}`: `{first}` and `{second}`."),
suggestion: format!(
"Use #[named(\"variant\")] on one provider or remove provide!({second}). See appendix-b-migration.md."
),
},
Self::MissingProvider { provider, cap } => CapabilityDiagnostic {
code: self.code(),
message: format!(
"Provider `{provider}` requires capability `{cap}` but none is registered."
),
suggestion: format!(
"Add `provide!(Your{cap}Live)` to your provider list. See appendix-b-migration.md."
),
},
Self::CycleDetected { nodes } => CapabilityDiagnostic {
code: self.code(),
message: format!("Provider dependency cycle: {}.", nodes.join(" -> ")),
suggestion: String::from("Break the cycle by extracting shared deps upstream."),
},
}
}
}
#[derive(Debug)]
pub enum RunError<E> {
Capability(CapabilityError),
Planner(CapabilityPlannerError),
Effect(E),
}
impl<E: fmt::Display> fmt::Display for RunError<E> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Self::Capability(e) => write!(f, "{e}"),
Self::Planner(e) => write!(f, "{e:?}"),
Self::Effect(e) => write!(f, "{e}"),
}
}
}
impl<E: std::error::Error + 'static> std::error::Error for RunError<E> {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
match self {
Self::Capability(e) => Some(e),
Self::Effect(e) => Some(e),
Self::Planner(_) => None,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn capability_error_display_variants() {
let missing = CapabilityError::Missing(CapabilityId::of::<u32>());
assert!(missing.to_string().contains("missing capability"));
let conflict = CapabilityError::Conflicting {
cap: CapabilityId::of::<u32>(),
first: "a".into(),
second: "b".into(),
};
assert!(conflict.to_string().contains("conflicting providers"));
}
#[test]
fn planner_error_codes_and_diagnostics() {
let dup = CapabilityPlannerError::DuplicateProviderId { id: "x".into() };
assert_eq!(dup.code(), "duplicate-provider-id");
assert_eq!(dup.to_diagnostic().code, "duplicate-provider-id");
let conflict = CapabilityPlannerError::ConflictingProvider {
cap: "Db".into(),
first: "a".into(),
second: "b".into(),
};
assert!(conflict.to_diagnostic().message.contains("Conflicting"));
let missing = CapabilityPlannerError::MissingProvider {
provider: "db".into(),
cap: "Config".into(),
};
assert!(missing.to_diagnostic().suggestion.contains("provide!"));
let cycle = CapabilityPlannerError::CycleDetected {
nodes: vec!["a".into(), "b".into()],
};
assert!(cycle.to_diagnostic().message.contains("cycle"));
}
#[test]
fn run_error_display_and_source() {
#[derive(Debug)]
struct Boom;
impl std::fmt::Display for Boom {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "boom")
}
}
impl std::error::Error for Boom {}
let cap = RunError::<Boom>::Capability(CapabilityError::Missing(CapabilityId::of::<u32>()));
assert!(cap.to_string().contains("missing"));
let eff: RunError<Boom> = RunError::Effect(Boom);
assert_eq!(eff.to_string(), "boom");
assert!(std::error::Error::source(&eff).is_some());
}
}