Skip to main content

everruns_capability/
error.rs

1//! Structured validation and registry errors for the capability contract.
2
3use std::fmt;
4
5/// A structured capability contract violation.
6///
7/// Produced by identity/configuration validation ([`crate::validate_capability_id`],
8/// [`crate::validate_capability_config`], [`crate::CapabilityRef::validate`],
9/// [`crate::Definition::validate`](crate::definition::Definition::validate))
10/// and by duplicate/collision rejection ([`crate::CapabilityIdIndex`],
11/// [`crate::ActivationSet`]). Hosts map these onto their own error surfaces
12/// (e.g. the Framework's `BuildError`, the server's HTTP 400s) without
13/// re-implementing the rules.
14#[derive(Debug, Clone, PartialEq, Eq)]
15#[non_exhaustive]
16pub enum CapabilityError {
17    /// A capability identifier violates the open-ID grammar or reserved
18    /// namespace rules.
19    InvalidId {
20        /// The rejected capability id.
21        id: String,
22        /// Why the identifier was rejected.
23        reason: String,
24    },
25    /// A capability configuration violates the JSON object boundary.
26    InvalidConfig {
27        /// The capability id the configuration belongs to.
28        id: String,
29        /// Why the configuration was rejected.
30        reason: String,
31    },
32    /// A code-defined capability definition is structurally invalid.
33    InvalidDefinition {
34        /// The rejected capability id.
35        id: String,
36        /// Why the definition was rejected.
37        reason: String,
38    },
39    /// Two capability inputs resolve to the same stable identity.
40    Duplicate {
41        /// The colliding capability id.
42        id: String,
43    },
44}
45
46impl CapabilityError {
47    /// The capability id the error refers to.
48    pub fn id(&self) -> &str {
49        match self {
50            Self::InvalidId { id, .. }
51            | Self::InvalidConfig { id, .. }
52            | Self::InvalidDefinition { id, .. }
53            | Self::Duplicate { id } => id,
54        }
55    }
56
57    /// The human-readable rejection reason.
58    pub fn reason(&self) -> String {
59        match self {
60            Self::InvalidId { reason, .. }
61            | Self::InvalidConfig { reason, .. }
62            | Self::InvalidDefinition { reason, .. } => reason.clone(),
63            Self::Duplicate { id } => format!("duplicate capability id {id:?}"),
64        }
65    }
66
67    /// Whether the error is a duplicate/collision rejection.
68    pub fn is_duplicate(&self) -> bool {
69        matches!(self, Self::Duplicate { .. })
70    }
71}
72
73impl fmt::Display for CapabilityError {
74    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
75        match self {
76            Self::InvalidId { id, reason } => {
77                write!(f, "invalid capability id {id:?}: {reason}")
78            }
79            Self::InvalidConfig { id, reason } => {
80                write!(f, "invalid capability config for {id:?}: {reason}")
81            }
82            Self::InvalidDefinition { id, reason } => {
83                write!(f, "invalid capability definition {id:?}: {reason}")
84            }
85            Self::Duplicate { id } => write!(f, "duplicate capability id {id:?}"),
86        }
87    }
88}
89
90impl std::error::Error for CapabilityError {}