use core::{fmt, panic::Location};
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct ErrorLocation {
location: &'static Location<'static>,
}
impl ErrorLocation {
#[inline]
#[track_caller]
pub(crate) fn caller() -> Self {
Self {
location: Location::caller(),
}
}
#[inline]
const fn file(self) -> &'static str {
self.location.file()
}
#[inline]
const fn line(self) -> u32 {
self.location.line()
}
#[inline]
const fn column(self) -> u32 {
self.location.column()
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) enum AttachOp {
Internal,
AddRendezvous,
Enter,
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct AttachError {
op: AttachOp,
location: ErrorLocation,
kind: AttachErrorKind,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum AttachErrorKind {
Control(CpError),
Rendezvous(crate::rendezvous::error::RendezvousError),
}
impl fmt::Debug for AttachError {
fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
formatter
.debug_struct("AttachError")
.field("operation", &self.operation())
.field("file", &self.file())
.field("line", &self.line())
.field("column", &self.column())
.field("kind", &self.kind)
.finish()
}
}
impl AttachError {
#[inline]
#[track_caller]
pub(crate) fn control(error: CpError) -> Self {
Self {
op: AttachOp::Internal,
location: ErrorLocation::caller(),
kind: AttachErrorKind::Control(error),
}
}
#[inline]
#[track_caller]
pub(crate) fn rendezvous(error: crate::rendezvous::error::RendezvousError) -> Self {
Self {
op: AttachOp::Internal,
location: ErrorLocation::caller(),
kind: AttachErrorKind::Rendezvous(error),
}
}
#[inline]
pub(crate) const fn with_operation(mut self, op: AttachOp, location: ErrorLocation) -> Self {
self.op = op;
self.location = location;
self
}
#[inline]
pub(crate) const fn control_cause(&self) -> Option<CpError> {
match self.kind {
AttachErrorKind::Control(error) => Some(error),
AttachErrorKind::Rendezvous(_) => None,
}
}
#[inline]
pub const fn operation(&self) -> &'static str {
match self.op {
AttachOp::Internal => "attach",
AttachOp::AddRendezvous => "add_rendezvous",
AttachOp::Enter => "enter",
}
}
#[inline]
pub const fn file(&self) -> &'static str {
self.location.file()
}
#[inline]
pub const fn line(&self) -> u32 {
self.location.line()
}
#[inline]
pub const fn column(&self) -> u32 {
self.location.column()
}
}
impl From<CpError> for AttachError {
#[inline]
#[track_caller]
fn from(err: CpError) -> Self {
Self::control(err)
}
}
impl From<crate::rendezvous::error::RendezvousError> for AttachError {
#[inline]
#[track_caller]
fn from(err: crate::rendezvous::error::RendezvousError) -> Self {
Self::rendezvous(err)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CpError {
Topology(TopologyError),
Abort(AbortError),
StateSnapshot(StateSnapshotError),
StateRestore(StateRestoreError),
TxCommit(TxCommitError),
TxAbort(TxAbortError),
Delegation(DelegationError),
RendezvousMismatch { expected: u16, actual: u16 },
RendezvousMissing { id: u16 },
RendezvousBusy { id: u16 },
ReplayDetected { operation: u8, nonce: u32 },
GenerationViolation { expected: u16, actual: u16 },
ResourceExhausted { resource: ResourceScope },
Authorisation { operation: u8 },
UnsupportedEffect(u8),
LabelOutOfUniverse { max: u8, actual: u8 },
PolicyAbort { reason: u16 },
ResourceMismatch { expected: u8, actual: u8 },
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ResourceScope {
Generic,
SessionKit,
RendezvousSlot,
ResolverTable,
PolicyTable,
ProgramImage,
RoleImage,
EndpointResidentBudget,
RouteTable,
LoopTable,
CapTable,
EndpointLease,
EndpointBounds,
EndpointMark,
EndpointPin,
EndpointHeader,
ControlLaneStorage,
}
impl ResourceScope {
pub const fn as_str(self) -> &'static str {
match self {
Self::Generic => "generic",
Self::SessionKit => "session-kit",
Self::RendezvousSlot => "rendezvous-slot",
Self::ResolverTable => "resolver-table",
Self::PolicyTable => "policy-table",
Self::ProgramImage => "program-image",
Self::RoleImage => "role-image",
Self::EndpointResidentBudget => "endpoint-resident-budget",
Self::RouteTable => "route-table",
Self::LoopTable => "loop-table",
Self::CapTable => "cap-table",
Self::EndpointLease => "endpoint-lease",
Self::EndpointBounds => "endpoint-bounds",
Self::EndpointMark => "endpoint-mark",
Self::EndpointPin => "endpoint-pin",
Self::EndpointHeader => "endpoint-header",
Self::ControlLaneStorage => "control-lane-storage",
}
}
}
impl CpError {
#[inline]
pub const fn resource_exhausted(resource: ResourceScope) -> Self {
Self::ResourceExhausted { resource }
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TopologyError {
InvalidSession,
InvalidLane,
InvalidState,
GenerationMismatch,
AckTimeout,
CommitFailed,
LaneOutOfRange,
LaneMismatch,
InProgress,
NoPending,
StaleGeneration,
GenerationOverflow,
InvalidInitial,
RendezvousIdMismatch,
SeqnoMismatch,
PendingTableFull,
}
impl From<crate::rendezvous::error::TopologyError> for TopologyError {
fn from(err: crate::rendezvous::error::TopologyError) -> Self {
match err {
crate::rendezvous::error::TopologyError::LaneOutOfRange { .. } => {
TopologyError::LaneOutOfRange
}
crate::rendezvous::error::TopologyError::UnknownSession { .. } => {
TopologyError::InvalidSession
}
crate::rendezvous::error::TopologyError::LaneMismatch { .. } => {
TopologyError::LaneMismatch
}
crate::rendezvous::error::TopologyError::InProgress { .. } => TopologyError::InProgress,
crate::rendezvous::error::TopologyError::NoPending { .. } => TopologyError::NoPending,
crate::rendezvous::error::TopologyError::StaleGeneration { .. } => {
TopologyError::StaleGeneration
}
crate::rendezvous::error::TopologyError::GenerationOverflow { .. } => {
TopologyError::GenerationOverflow
}
crate::rendezvous::error::TopologyError::InvalidInitial { .. } => {
TopologyError::InvalidInitial
}
crate::rendezvous::error::TopologyError::RemoteRendezvousMismatch { .. }
| crate::rendezvous::error::TopologyError::RendezvousIdMismatch { .. } => {
TopologyError::RendezvousIdMismatch
}
crate::rendezvous::error::TopologyError::SeqnoMismatch { .. } => {
TopologyError::SeqnoMismatch
}
crate::rendezvous::error::TopologyError::PendingTableFull => {
TopologyError::PendingTableFull
}
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AbortError {
SessionNotFound,
GenerationMismatch,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum StateSnapshotError {
SessionNotFound,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum StateRestoreError {
SessionNotFound,
EpochNotFound,
EpochMismatch,
AlreadyFinalized,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TxCommitError {
SessionNotFound,
NoStateSnapshot,
AlreadyFinalized,
GenerationMismatch,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TxAbortError {
SessionNotFound,
NoStateSnapshot,
AlreadyFinalized,
GenerationMismatch,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum DelegationError {
InvalidToken,
Exhausted,
ShotMismatch,
}
impl core::fmt::Display for CpError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::Topology(e) => write!(f, "Topology error: {:?}", e),
Self::Abort(e) => write!(f, "Abort error: {:?}", e),
Self::StateSnapshot(e) => write!(f, "StateSnapshot error: {:?}", e),
Self::StateRestore(e) => write!(f, "StateRestore error: {:?}", e),
Self::TxCommit(e) => write!(f, "TxCommit error: {:?}", e),
Self::TxAbort(e) => write!(f, "TxAbort error: {:?}", e),
Self::Delegation(e) => write!(f, "Delegation error: {:?}", e),
Self::RendezvousMismatch { expected, actual } => {
write!(
f,
"Rendezvous ID mismatch: expected {}, got {}",
expected, actual
)
}
Self::RendezvousMissing { id } => {
write!(f, "Rendezvous {} is not registered", id)
}
Self::RendezvousBusy { id } => {
write!(f, "Rendezvous {} is already leased", id)
}
Self::ReplayDetected { operation, nonce } => {
write!(
f,
"Replay detected: operation {}, nonce {}",
operation, nonce
)
}
Self::GenerationViolation { expected, actual } => {
write!(
f,
"Generation ordering violation: expected {}, got {}",
expected, actual
)
}
Self::ResourceExhausted { resource } => {
write!(f, "Resource exhausted: {}", resource.as_str())
}
Self::Authorisation { operation } => {
write!(f, "Operation not authorised: {}", operation)
}
Self::UnsupportedEffect(op) => write!(f, "Unsupported effect: {}", op),
Self::LabelOutOfUniverse { max, actual } => write!(
f,
"Program label {} exceeds rendezvous label universe {}",
actual, max
),
Self::PolicyAbort { reason } => write!(f, "Policy abort requested (reason {})", reason),
Self::ResourceMismatch { expected, actual } => {
write!(
f,
"Resource kind mismatch: expected tag {}, got {}",
expected, actual
)
}
}
}
}
#[cfg(feature = "std")]
impl std::error::Error for CpError {}
impl From<TopologyError> for CpError {
fn from(e: TopologyError) -> Self {
Self::Topology(e)
}
}
impl From<AbortError> for CpError {
fn from(e: AbortError) -> Self {
Self::Abort(e)
}
}
impl From<StateSnapshotError> for CpError {
fn from(e: StateSnapshotError) -> Self {
Self::StateSnapshot(e)
}
}
impl From<StateRestoreError> for CpError {
fn from(e: StateRestoreError) -> Self {
Self::StateRestore(e)
}
}
impl From<TxCommitError> for CpError {
fn from(e: TxCommitError) -> Self {
Self::TxCommit(e)
}
}
impl From<TxAbortError> for CpError {
fn from(e: TxAbortError) -> Self {
Self::TxAbort(e)
}
}
impl From<DelegationError> for CpError {
fn from(e: DelegationError) -> Self {
Self::Delegation(e)
}
}
#[cfg(all(test, feature = "std"))]
mod tests {
use super::*;
#[test]
fn test_error_conversions() {
let topology_err: CpError = TopologyError::InvalidSession.into();
assert!(matches!(topology_err, CpError::Topology(_)));
let abort_err: CpError = AbortError::SessionNotFound.into();
assert!(matches!(abort_err, CpError::Abort(_)));
}
#[test]
fn test_error_display() {
let err = CpError::RendezvousMismatch {
expected: 1,
actual: 2,
};
let s = format!("{}", err);
assert!(s.contains("expected 1"));
assert!(s.contains("got 2"));
}
}