Skip to main content

deepseek_sdk/
error.rs

1//! Error types for DeepSeek API interactions.
2use serde::Deserialize;
3use std::error::Error;
4use std::fmt;
5
6/// Error payload returned by DeepSeek APIs.
7#[derive(Debug, Clone, Eq, PartialEq, Deserialize)]
8pub struct ApiError {
9    pub message: String,
10    #[serde(rename = "type")]
11    pub error_type: String,
12    /// Present only for API error payloads.
13    pub param: Option<String>,
14    /// Present only for API error payloads.
15    pub code: Option<String>,
16}
17
18/// Envelope used by some API endpoints: `{ "error": { ... } }`.
19#[derive(Debug, Clone, Eq, PartialEq, Deserialize)]
20pub(crate) struct ApiErrorEnvelope {
21    pub error: ApiError,
22}
23
24/// Categorized reqwest error kinds for diagnostics.
25#[non_exhaustive]
26#[derive(Debug, Clone, Eq, PartialEq)]
27pub enum ReqwestErrorKind {
28    Decode,
29    Timeout,
30    Connect,
31    Request,
32    Body,
33    Status,
34}
35
36impl ReqwestErrorKind {
37    fn as_str(&self) -> &'static str {
38        match self {
39            ReqwestErrorKind::Decode => "decode",
40            ReqwestErrorKind::Timeout => "timeout",
41            ReqwestErrorKind::Connect => "connect",
42            ReqwestErrorKind::Request => "request",
43            ReqwestErrorKind::Body => "body",
44            ReqwestErrorKind::Status => "status",
45        }
46    }
47}
48
49/// Transport-level failure from reqwest.
50#[derive(Debug)]
51pub struct TransportError {
52    pub source: reqwest::Error,
53    pub kind: Option<ReqwestErrorKind>,
54}
55
56/// Unified error type for this crate.
57#[non_exhaustive]
58#[derive(Debug)]
59pub enum DeepSeekError {
60    /// API returned a structured error payload.
61    Api {
62        error: ApiError,
63        status: Option<u16>,
64        body: Option<String>,
65    },
66    /// Non-JSON or otherwise unrecognized HTTP error.
67    Http { status: u16, body: Option<String> },
68    /// Response could not be decoded into the expected schema.
69    Decode {
70        message: String,
71        body: Option<String>,
72    },
73    /// Transport errors from reqwest.
74    Transport(TransportError),
75    /// Local filesystem error (e.g. reading a file to upload).
76    Io(std::io::Error),
77}
78
79impl DeepSeekError {
80    pub(crate) fn api(error: ApiError, status: Option<u16>, body: Option<String>) -> Self {
81        DeepSeekError::Api {
82            error,
83            status,
84            body,
85        }
86    }
87
88    pub(crate) fn http(status: u16, body: String) -> Self {
89        DeepSeekError::Http {
90            status,
91            body: Some(body),
92        }
93    }
94
95    pub(crate) fn decode(message: String, body: String) -> Self {
96        DeepSeekError::Decode {
97            message,
98            body: Some(body),
99        }
100    }
101}
102
103impl fmt::Display for DeepSeekError {
104    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
105        match self {
106            DeepSeekError::Api { error, status, .. } => write!(
107                f,
108                "DeepSeek API error: {} (type={}, param={:?}, code={:?}, status={:?})",
109                error.message, error.error_type, error.param, error.code, status
110            ),
111            DeepSeekError::Http { status, body } => {
112                write!(f, "HTTP error: status={}, body={:?}", status, body)
113            }
114            DeepSeekError::Decode { message, body } => {
115                write!(f, "Decode error: {} (body={:?})", message, body)
116            }
117            DeepSeekError::Transport(transport) => {
118                if let Some(kind) = &transport.kind {
119                    write!(f, "reqwest {} error: {}", kind.as_str(), transport.source)
120                } else {
121                    write!(f, "reqwest error: {}", transport.source)
122                }
123            }
124            DeepSeekError::Io(source) => write!(f, "IO error: {source}"),
125        }
126    }
127}
128
129impl Error for DeepSeekError {
130    fn source(&self) -> Option<&(dyn Error + 'static)> {
131        match self {
132            DeepSeekError::Transport(transport) => Some(&transport.source),
133            DeepSeekError::Io(source) => Some(source),
134            _ => None,
135        }
136    }
137}
138
139impl From<reqwest::Error> for DeepSeekError {
140    fn from(value: reqwest::Error) -> Self {
141        let kind = if value.is_decode() {
142            Some(ReqwestErrorKind::Decode)
143        } else if value.is_timeout() {
144            Some(ReqwestErrorKind::Timeout)
145        } else if value.is_connect() {
146            Some(ReqwestErrorKind::Connect)
147        } else if value.is_request() {
148            Some(ReqwestErrorKind::Request)
149        } else if value.is_body() {
150            Some(ReqwestErrorKind::Body)
151        } else if value.is_status() {
152            Some(ReqwestErrorKind::Status)
153        } else {
154            None
155        };
156
157        DeepSeekError::Transport(TransportError {
158            source: value,
159            kind,
160        })
161    }
162}
163
164impl From<std::io::Error> for DeepSeekError {
165    fn from(source: std::io::Error) -> Self {
166        DeepSeekError::Io(source)
167    }
168}