Skip to main content

active_rpc/
error.rs

1//! Stable, machine-readable errors for Active RPC.
2//!
3//! Codes are permanent (`rpc/…`) and are part of the wire contract: peers
4//! branch on codes, never on messages, and a message never echoes payload,
5//! argument or secret material.
6
7use std::fmt;
8
9/// Result alias for every fallible operation in this crate.
10pub type Result<T> = std::result::Result<T, Error>;
11
12/// A capability, promise, transport or protocol failure.
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum Error {
15    /// A capability id is unknown to this kernel (forged or expired).
16    UnknownCapability(u128),
17    /// A capability exists but the requested method is outside its grants.
18    MethodNotGranted {
19        /// Method that was attempted.
20        method: String,
21    },
22    /// An attenuation attempt would widen authority.
23    Escalation(&'static str),
24    /// A capability is read-only and the call mutates state.
25    ReadOnly,
26    /// A path restriction rejected the call.
27    PathNotGranted(String),
28    /// A tool policy rejected the call.
29    ToolPolicy(&'static str),
30    /// The capability has been revoked or its provider disappeared.
31    Revoked(u128),
32    /// A budget was exhausted.
33    BudgetExceeded(&'static str),
34    /// The lane does not exist (or was destroyed).
35    UnknownLane(u64),
36    /// The channel does not exist.
37    UnknownChannel(u64),
38    /// The promise does not exist.
39    UnknownPromise(u64),
40    /// A promise was cancelled and can no longer be awaited.
41    PromiseCancelled(u64),
42    /// A promise was resolved twice.
43    PromiseAlreadyResolved(u64),
44    /// The call references a promise that will never resolve.
45    UnresolvablePromise(u64),
46    /// The provider failed the call.
47    Provider {
48        /// Stable provider-side code.
49        code: String,
50    },
51    /// A translated Active evaluation failed.
52    Domain(&'static str),
53    /// A payload is not a valid Active Infinity module.
54    Payload(String),
55    /// The wire envelope is malformed.
56    Wire(&'static str),
57    /// The envelope's capability table is malformed or oversized.
58    CapabilityTable(&'static str),
59    /// A transport failed.
60    Transport(String),
61    /// A transport is unavailable in this build or on this host.
62    TransportUnavailable(&'static str),
63    /// A deadline elapsed before the call completed.
64    Timeout,
65    /// The kernel detected a cycle in pipelined calls.
66    PipelinedCycle,
67    /// Invalid argument supplied by the caller.
68    InvalidArgument(&'static str),
69}
70
71impl Error {
72    /// Permanent machine-readable code.
73    pub const fn code(&self) -> &'static str {
74        match self {
75            Error::UnknownCapability(_) => "rpc/unknown-capability",
76            Error::MethodNotGranted { .. } => "rpc/method-not-granted",
77            Error::Escalation(_) => "rpc/attenuation-escalation",
78            Error::ReadOnly => "rpc/read-only",
79            Error::PathNotGranted(_) => "rpc/path-not-granted",
80            Error::ToolPolicy(_) => "rpc/tool-policy",
81            Error::Revoked(_) => "rpc/revoked",
82            Error::BudgetExceeded(_) => "rpc/budget-exceeded",
83            Error::UnknownLane(_) => "rpc/unknown-lane",
84            Error::UnknownChannel(_) => "rpc/unknown-channel",
85            Error::UnknownPromise(_) => "rpc/unknown-promise",
86            Error::PromiseCancelled(_) => "rpc/promise-cancelled",
87            Error::PromiseAlreadyResolved(_) => "rpc/promise-resolved",
88            Error::UnresolvablePromise(_) => "rpc/unresolvable-promise",
89            Error::Provider { .. } => "rpc/provider",
90            Error::Domain(_) => "rpc/domain",
91            Error::Payload(_) => "rpc/payload",
92            Error::Wire(_) => "rpc/wire",
93            Error::CapabilityTable(_) => "rpc/capability-table",
94            Error::Transport(_) => "rpc/transport",
95            Error::TransportUnavailable(_) => "rpc/transport-unavailable",
96            Error::Timeout => "rpc/timeout",
97            Error::PipelinedCycle => "rpc/pipelined-cycle",
98            Error::InvalidArgument(_) => "rpc/invalid-argument",
99        }
100    }
101
102    /// True when the failure means the peer sent something hostile.
103    pub const fn is_fatal(&self) -> bool {
104        matches!(
105            self,
106            Error::Wire(_)
107                | Error::CapabilityTable(_)
108                | Error::Payload(_)
109                | Error::UnknownCapability(_)
110        )
111    }
112}
113
114impl fmt::Display for Error {
115    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
116        match self {
117            Error::UnknownCapability(id) => write!(f, "unknown capability {id:032x}"),
118            Error::MethodNotGranted { method } => write!(f, "method {method} is not granted"),
119            Error::Escalation(what) => write!(f, "attenuation would widen authority: {what}"),
120            Error::ReadOnly => write!(f, "capability is read-only"),
121            Error::PathNotGranted(path) => write!(f, "path {path} is outside the capability"),
122            Error::ToolPolicy(what) => write!(f, "tool policy rejects the call: {what}"),
123            Error::Revoked(id) => write!(f, "capability {id:032x} is revoked"),
124            Error::BudgetExceeded(kind) => write!(f, "{kind} budget exhausted"),
125            Error::UnknownLane(id) => write!(f, "unknown lane {id}"),
126            Error::UnknownChannel(id) => write!(f, "unknown channel {id}"),
127            Error::UnknownPromise(id) => write!(f, "unknown promise {id}"),
128            Error::PromiseCancelled(id) => write!(f, "promise {id} was cancelled"),
129            Error::PromiseAlreadyResolved(id) => write!(f, "promise {id} was already resolved"),
130            Error::UnresolvablePromise(id) => write!(f, "promise {id} can never resolve"),
131            Error::Provider { code } => write!(f, "provider failed: {code}"),
132            Error::Domain(code) => write!(f, "domain operation failed: {code}"),
133            Error::Payload(reason) => write!(f, "invalid payload: {reason}"),
134            Error::Wire(reason) => write!(f, "malformed envelope: {reason}"),
135            Error::CapabilityTable(reason) => write!(f, "malformed capability table: {reason}"),
136            Error::Transport(reason) => write!(f, "transport failure: {reason}"),
137            Error::TransportUnavailable(name) => write!(f, "transport {name} is unavailable"),
138            Error::Timeout => write!(f, "deadline elapsed"),
139            Error::PipelinedCycle => write!(f, "pipelined calls form a cycle"),
140            Error::InvalidArgument(what) => write!(f, "invalid argument: {what}"),
141        }
142    }
143}
144
145impl std::error::Error for Error {}
146
147impl From<active_infinity::Error> for Error {
148    fn from(error: active_infinity::Error) -> Self {
149        Error::Payload(error.code().to_string())
150    }
151}