Skip to main content

vtcode_core/open_responses/
error.rs

1//! Structured error handling for Open Responses.
2//!
3//! Provides consistent error objects with type, code, param, and message
4//! fields as defined by the Open Responses specification.
5
6use serde::{Deserialize, Serialize};
7
8/// Structured error object for Open Responses.
9///
10/// Errors are designed to provide clear, actionable feedback with
11/// machine-readable types and codes alongside human-readable messages.
12#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize, thiserror::Error)]
13#[error("{error_type}: {message}")]
14pub struct OpenResponseError {
15    /// Category of the error.
16    #[serde(rename = "type")]
17    pub error_type: OpenResponseErrorType,
18
19    /// Machine-readable error code providing additional detail.
20    #[serde(skip_serializing_if = "Option::is_none")]
21    pub code: Option<OpenResponseErrorCode>,
22
23    /// The input parameter related to the error, if applicable.
24    #[serde(skip_serializing_if = "Option::is_none")]
25    pub param: Option<String>,
26
27    /// Human-readable explanation of what went wrong.
28    pub message: String,
29}
30
31impl OpenResponseError {
32    /// Creates a new error with the given type and message.
33    pub fn new(error_type: OpenResponseErrorType, message: impl Into<String>) -> Self {
34        Self {
35            error_type,
36            code: None,
37            param: None,
38            message: message.into(),
39        }
40    }
41
42    /// Creates a server error.
43    pub fn server_error(message: impl Into<String>) -> Self {
44        Self::new(OpenResponseErrorType::ServerError, message)
45    }
46
47    /// Creates an invalid request error.
48    pub fn invalid_request(message: impl Into<String>) -> Self {
49        Self::new(OpenResponseErrorType::InvalidRequest, message)
50    }
51
52    /// Creates an invalid request error with a parameter reference.
53    pub fn invalid_param(param: impl Into<String>, message: impl Into<String>) -> Self {
54        Self {
55            error_type: OpenResponseErrorType::InvalidRequest,
56            code: None,
57            param: Some(param.into()),
58            message: message.into(),
59        }
60    }
61
62    /// Creates a model error.
63    pub fn model_error(message: impl Into<String>) -> Self {
64        Self::new(OpenResponseErrorType::ModelError, message)
65    }
66
67    /// Creates a not found error.
68    pub fn not_found(message: impl Into<String>) -> Self {
69        Self::new(OpenResponseErrorType::NotFound, message)
70    }
71
72    /// Creates a rate limit error.
73    pub fn rate_limit(message: impl Into<String>) -> Self {
74        Self::new(OpenResponseErrorType::TooManyRequests, message)
75    }
76
77    /// Sets the error code.
78    pub fn with_code(mut self, code: OpenResponseErrorCode) -> Self {
79        self.code = Some(code);
80        self
81    }
82
83    /// Sets the parameter reference.
84    pub fn with_param(mut self, param: impl Into<String>) -> Self {
85        self.param = Some(param.into());
86        self
87    }
88}
89
90/// Category of error that occurred.
91#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
92#[serde(rename_all = "snake_case")]
93pub enum OpenResponseErrorType {
94    /// Internal server error.
95    ServerError,
96
97    /// Invalid request parameters.
98    InvalidRequest,
99
100    /// Resource not found.
101    NotFound,
102
103    /// Model-specific error.
104    ModelError,
105
106    /// Rate limit exceeded.
107    TooManyRequests,
108
109    /// Authentication error.
110    AuthenticationError,
111
112    /// Permission denied.
113    PermissionDenied,
114}
115
116impl std::fmt::Display for OpenResponseErrorType {
117    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
118        match self {
119            Self::ServerError => write!(f, "server_error"),
120            Self::InvalidRequest => write!(f, "invalid_request"),
121            Self::NotFound => write!(f, "not_found"),
122            Self::ModelError => write!(f, "model_error"),
123            Self::TooManyRequests => write!(f, "too_many_requests"),
124            Self::AuthenticationError => write!(f, "authentication_error"),
125            Self::PermissionDenied => write!(f, "permission_denied"),
126        }
127    }
128}
129
130/// Specific error codes providing additional detail.
131#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
132#[serde(rename_all = "snake_case")]
133pub enum OpenResponseErrorCode {
134    /// Invalid API key.
135    InvalidApiKey,
136
137    /// Insufficient quota.
138    InsufficientQuota,
139
140    /// Context length exceeded.
141    ContextLengthExceeded,
142
143    /// Invalid model.
144    InvalidModel,
145
146    /// Content filter triggered.
147    ContentFilter,
148
149    /// Tool execution failed.
150    ToolExecutionFailed,
151
152    /// Timeout occurred.
153    Timeout,
154
155    /// Rate limit exceeded.
156    RateLimitExceeded,
157
158    /// Provider-specific error code.
159    #[serde(untagged)]
160    Custom(String),
161}
162
163impl std::fmt::Display for OpenResponseErrorCode {
164    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
165        match self {
166            Self::InvalidApiKey => write!(f, "invalid_api_key"),
167            Self::InsufficientQuota => write!(f, "insufficient_quota"),
168            Self::ContextLengthExceeded => write!(f, "context_length_exceeded"),
169            Self::InvalidModel => write!(f, "invalid_model"),
170            Self::ContentFilter => write!(f, "content_filter"),
171            Self::ToolExecutionFailed => write!(f, "tool_execution_failed"),
172            Self::Timeout => write!(f, "timeout"),
173            Self::RateLimitExceeded => write!(f, "rate_limit_exceeded"),
174            Self::Custom(code) => write!(f, "{}", code),
175        }
176    }
177}
178
179#[cfg(test)]
180mod tests {
181    use super::*;
182
183    #[test]
184    fn test_error_creation() {
185        let err = OpenResponseError::invalid_param("model", "Invalid model ID");
186        assert_eq!(err.error_type, OpenResponseErrorType::InvalidRequest);
187        assert_eq!(err.param, Some("model".to_string()));
188        assert_eq!(err.message, "Invalid model ID");
189    }
190
191    #[test]
192    fn test_error_serialization() {
193        let err = OpenResponseError::server_error("Internal error")
194            .with_code(OpenResponseErrorCode::Timeout);
195        let json = serde_json::to_string(&err).unwrap();
196        assert!(json.contains("\"type\":\"server_error\""));
197        assert!(json.contains("\"code\":\"timeout\""));
198    }
199}