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 {}