Skip to main content

stdiobus_core/
error.rs

1// SPDX-License-Identifier: Apache-2.0
2// Copyright (c) 2026-present Raman Marozau <raman@worktif.com>
3// Copyright (c) 2026-present stdiobus contributors
4
5//! Error types for stdio_bus
6//!
7//! Canonical error codes matching spec/host-api.md
8
9use thiserror::Error;
10
11/// Canonical error codes for stdio_bus operations
12#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
13#[repr(i32)]
14pub enum ErrorCode {
15    /// Invalid argument provided
16    InvalidArgument = -1,
17    /// Operation not valid in current state
18    InvalidState = -2,
19    /// Request timed out
20    Timeout = -3,
21    /// Request was cancelled
22    Cancelled = -4,
23    /// Transport-level failure
24    TransportError = -5,
25    /// Protocol negotiation failed
26    NegotiationFailed = -6,
27    /// Required extension not available
28    ExtensionUnavailable = -7,
29    /// Operation denied by policy
30    PolicyDenied = -8,
31    /// Internal error
32    InternalError = -9,
33    /// Resource exhausted
34    ResourceExhausted = -10,
35}
36
37impl std::fmt::Display for ErrorCode {
38    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
39        match self {
40            Self::InvalidArgument => write!(f, "INVALID_ARGUMENT"),
41            Self::InvalidState => write!(f, "INVALID_STATE"),
42            Self::Timeout => write!(f, "TIMEOUT"),
43            Self::Cancelled => write!(f, "CANCELLED"),
44            Self::TransportError => write!(f, "TRANSPORT_ERROR"),
45            Self::NegotiationFailed => write!(f, "NEGOTIATION_FAILED"),
46            Self::ExtensionUnavailable => write!(f, "EXTENSION_UNAVAILABLE"),
47            Self::PolicyDenied => write!(f, "POLICY_DENIED"),
48            Self::InternalError => write!(f, "INTERNAL_ERROR"),
49            Self::ResourceExhausted => write!(f, "RESOURCE_EXHAUSTED"),
50        }
51    }
52}
53
54/// Main error type for stdio_bus operations
55#[derive(Error, Debug)]
56pub enum Error {
57    #[error("Invalid argument: {message}")]
58    InvalidArgument { message: String },
59
60    #[error("Invalid state: expected {expected}, got {actual}")]
61    InvalidState { expected: String, actual: String },
62
63    #[error("Request timed out after {timeout_ms}ms")]
64    Timeout { timeout_ms: u64 },
65
66    #[error("Request cancelled")]
67    Cancelled,
68
69    #[error("Transport error: {message}")]
70    TransportError { message: String },
71
72    #[error("Negotiation failed: {message}")]
73    NegotiationFailed { message: String },
74
75    #[error("Extension unavailable: {extension}")]
76    ExtensionUnavailable { extension: String },
77
78    #[error("Policy denied: {message}")]
79    PolicyDenied { message: String },
80
81    #[error("Internal error: {message}")]
82    InternalError { message: String },
83
84    #[error("Resource exhausted: {resource}")]
85    ResourceExhausted { resource: String },
86
87    #[error("JSON error: {0}")]
88    Json(#[from] serde_json::Error),
89
90    #[error("IO error: {0}")]
91    Io(#[from] std::io::Error),
92}
93
94impl Error {
95    /// Get the canonical error code
96    pub fn code(&self) -> ErrorCode {
97        match self {
98            Self::InvalidArgument { .. } => ErrorCode::InvalidArgument,
99            Self::InvalidState { .. } => ErrorCode::InvalidState,
100            Self::Timeout { .. } => ErrorCode::Timeout,
101            Self::Cancelled => ErrorCode::Cancelled,
102            Self::TransportError { .. } => ErrorCode::TransportError,
103            Self::NegotiationFailed { .. } => ErrorCode::NegotiationFailed,
104            Self::ExtensionUnavailable { .. } => ErrorCode::ExtensionUnavailable,
105            Self::PolicyDenied { .. } => ErrorCode::PolicyDenied,
106            Self::InternalError { .. } => ErrorCode::InternalError,
107            Self::ResourceExhausted { .. } => ErrorCode::ResourceExhausted,
108            Self::Json(_) => ErrorCode::InvalidArgument,
109            Self::Io(_) => ErrorCode::TransportError,
110        }
111    }
112
113    /// Check if error is retryable
114    pub fn is_retryable(&self) -> bool {
115        matches!(
116            self.code(),
117            ErrorCode::Timeout | ErrorCode::TransportError | ErrorCode::ResourceExhausted
118        )
119    }
120}
121
122/// Result type alias for stdio_bus operations
123pub type Result<T> = std::result::Result<T, Error>;