Skip to main content

nautilus_deribit/http/
error.rs

1// -------------------------------------------------------------------------------------------------
2//  Copyright (C) 2015-2026 Nautech Systems Pty Ltd. All rights reserved.
3//  https://nautechsystems.io
4//
5//  Licensed under the GNU Lesser General Public License Version 3.0 (the "License");
6//  You may not use this file except in compliance with the License.
7//  You may obtain a copy of the License at https://www.gnu.org/licenses/lgpl-3.0.en.html
8//
9//  Unless required by applicable law or agreed to in writing, software
10//  distributed under the License is distributed on an "AS IS" BASIS,
11//  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12//  See the License for the specific language governing permissions and
13//  limitations under the License.
14// -------------------------------------------------------------------------------------------------
15
16//! Deribit HTTP client error types.
17
18use std::fmt;
19
20use crate::common::consts::should_retry_error_code;
21
22/// Represents HTTP client errors for the Deribit adapter.
23#[derive(Debug, Clone)]
24pub enum DeribitHttpError {
25    /// Missing API credentials
26    MissingCredentials,
27    /// Deribit-specific error with code and message
28    DeribitError { error_code: i64, message: String },
29    /// JSON serialization/deserialization error
30    JsonError(String),
31    /// Input validation error
32    ValidationError(String),
33    /// Network-related error
34    NetworkError(String),
35    /// Request timeout
36    Timeout(String),
37    /// Request canceled
38    Canceled(String),
39    /// Unexpected HTTP status
40    UnexpectedStatus { status: u16, body: String },
41}
42
43impl fmt::Display for DeribitHttpError {
44    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
45        match self {
46            Self::MissingCredentials => write!(f, "Missing API credentials"),
47            Self::DeribitError {
48                error_code,
49                message,
50            } => write!(f, "Deribit error {error_code}: {message}"),
51            Self::JsonError(msg) => write!(f, "JSON error: {msg}"),
52            Self::ValidationError(msg) => write!(f, "Validation error: {msg}"),
53            Self::NetworkError(msg) => write!(f, "Network error: {msg}"),
54            Self::Timeout(msg) => write!(f, "Timeout: {msg}"),
55            Self::Canceled(msg) => write!(f, "Canceled: {msg}"),
56            Self::UnexpectedStatus { status, body } => {
57                write!(f, "Unexpected status {status}: {body}")
58            }
59        }
60    }
61}
62
63impl std::error::Error for DeribitHttpError {}
64
65impl From<serde_json::Error> for DeribitHttpError {
66    fn from(error: serde_json::Error) -> Self {
67        Self::JsonError(error.to_string())
68    }
69}
70
71impl From<anyhow::Error> for DeribitHttpError {
72    fn from(error: anyhow::Error) -> Self {
73        Self::NetworkError(error.to_string())
74    }
75}
76
77impl DeribitHttpError {
78    /// Returns whether this error is retryable.
79    #[must_use]
80    pub fn is_retryable(&self) -> bool {
81        match self {
82            Self::NetworkError(_) => true,
83            Self::UnexpectedStatus { status, .. } => *status >= 500 || *status == 429,
84            Self::DeribitError { error_code, .. } => should_retry_error_code(*error_code),
85            _ => false,
86        }
87    }
88
89    /// Maps a JSON-RPC error to the appropriate error variant.
90    ///
91    /// Standard JSON-RPC error codes (-32xxx) are mapped to `ValidationError`,
92    /// while Deribit-specific error codes are mapped to `DeribitError`.
93    ///
94    /// # Arguments
95    ///
96    /// * `error_code` - The JSON-RPC error code
97    /// * `message` - The error message
98    /// * `data` - Optional additional error data
99    pub fn from_jsonrpc_error(
100        error_code: i64,
101        message: String,
102        data: Option<&serde_json::Value>,
103    ) -> Self {
104        match error_code {
105            // JSON-RPC 2.0 standard error codes
106            -32700 => Self::ValidationError(format!("Parse error: {message}")),
107            -32600 => Self::ValidationError(format!("Invalid request: {message}")),
108            -32601 => Self::ValidationError(format!("Method not found: {message}")),
109            -32602 => {
110                // Try to extract parameter details from data field
111                let detail = data
112                    .and_then(|d| d.as_object())
113                    .and_then(|obj| {
114                        let param = obj.get("param")?.as_str()?;
115                        let reason = obj.get("reason")?.as_str()?;
116                        Some(format!(" (parameter '{param}': {reason})"))
117                    })
118                    .unwrap_or_default();
119                Self::ValidationError(format!("Invalid params: {message}{detail}"))
120            }
121            -32603 => Self::ValidationError(format!("Internal error: {message}")),
122            // All other error codes are Deribit-specific
123            _ => Self::DeribitError {
124                error_code,
125                message,
126            },
127        }
128    }
129}
130
131#[cfg(test)]
132mod tests {
133    use rstest::rstest;
134
135    use super::*;
136
137    #[rstest]
138    #[case(DeribitHttpError::NetworkError("timeout".to_string()), true)]
139    #[case(DeribitHttpError::Timeout("read".to_string()), false)]
140    #[case(DeribitHttpError::UnexpectedStatus { status: 500, body: String::new() }, true)]
141    #[case(DeribitHttpError::UnexpectedStatus { status: 502, body: String::new() }, true)]
142    #[case(DeribitHttpError::UnexpectedStatus { status: 429, body: String::new() }, true)]
143    #[case(DeribitHttpError::UnexpectedStatus { status: 403, body: String::new() }, false)]
144    #[case(DeribitHttpError::DeribitError { error_code: 10028, message: String::new() }, true)]
145    #[case(DeribitHttpError::DeribitError { error_code: 13888, message: String::new() }, true)]
146    #[case(DeribitHttpError::DeribitError { error_code: -32600, message: String::new() }, false)]
147    #[case(DeribitHttpError::JsonError("bad".to_string()), false)]
148    #[case(DeribitHttpError::ValidationError("bad".to_string()), false)]
149    #[case(DeribitHttpError::MissingCredentials, false)]
150    #[case(DeribitHttpError::Canceled("shutdown".to_string()), false)]
151    fn test_is_retryable(#[case] error: DeribitHttpError, #[case] expected: bool) {
152        assert_eq!(error.is_retryable(), expected);
153    }
154}