Skip to main content

openai_interface/
errors.rs

1use std::path::PathBuf;
2
3use serde::Deserialize;
4use thiserror::Error;
5
6/// The error body returned by an API on a failed request.
7///
8/// This mirrors the standard OpenAI error shape
9/// `{"error": {"message": ..., "type": ..., "code": ...}}`. Compatible
10/// providers that place these fields at the top level of the response body
11/// are handled as well.
12#[derive(Debug, Deserialize, Clone)]
13pub struct ApiError {
14    /// The HTTP status code of the failed response.
15    #[serde(default)]
16    pub status: u16,
17    /// A human-readable description of the error.
18    #[serde(default)]
19    pub message: String,
20    /// The category of the error, e.g. `invalid_request_error`.
21    #[serde(rename = "type", alias = "error_type", default)]
22    pub error_type: Option<String>,
23    /// The machine-readable error code, e.g. `context_length_exceeded`.
24    #[serde(default, deserialize_with = "deserialize_code")]
25    pub code: Option<String>,
26}
27
28/// The error `code` is a string in the official API, but some compatible
29/// providers return it as a number.
30fn deserialize_code<'de, D>(deserializer: D) -> Result<Option<String>, D::Error>
31where
32    D: serde::Deserializer<'de>,
33{
34    let value = Option::<serde_json::Value>::deserialize(deserializer)?;
35    Ok(match value {
36        None | Some(serde_json::Value::Null) => None,
37        Some(serde_json::Value::String(s)) => Some(s),
38        Some(other) => Some(other.to_string()),
39    })
40}
41
42impl std::fmt::Display for ApiError {
43    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44        write!(f, "HTTP {}: {}", self.status, self.message)?;
45        if let Some(error_type) = &self.error_type {
46            write!(f, " (type: {error_type})")?;
47        }
48        if let Some(code) = &self.code {
49            write!(f, " (code: {code})")?;
50        }
51        Ok(())
52    }
53}
54
55impl std::error::Error for ApiError {}
56
57/// Helper type for the official error envelope `{"error": {...}}`.
58#[derive(Debug, Deserialize, Clone)]
59pub(crate) struct NestedApiError {
60    pub error: ApiError,
61}
62
63#[derive(Debug, Error)]
64pub enum OapiError {
65    #[error("Failed to send request: {0}")]
66    SendError(#[from] reqwest::Error),
67    #[error("Response error: {0}")]
68    ResponseError(String),
69    #[error("API request failed: {0}")]
70    ApiError(#[from] ApiError),
71    #[error("Failed to parse SSE stream: {0}")]
72    SseParseError(String),
73    /// If the request is a streaming request, but the context is not streaming.
74    #[error("You cannot post a streaming request in a non-streaming context")]
75    NonStreamingViolation,
76    /// If the request is a non-streaming request, but the context is streaming.
77    #[error("You cannot post a non-streaming request in a streaming context")]
78    StreamingViolation,
79    #[error("Deserialization error:\n{0}\n\nPlease report this error in the project issue.")]
80    DeserializationError(String),
81    #[error("File not found at: {0}")]
82    FileNotFoundError(PathBuf),
83    #[error("Failed to read file: {0}")]
84    FileReadError(#[from] std::io::Error),
85    #[error("Failed to operate on url: {0}")]
86    UrlError(#[from] url::ParseError),
87    #[error("Cannot be base: {0}")]
88    UrlCannotBeBase(String),
89}