nautilus_derive/http/
error.rs1use std::time::Duration;
19
20use nautilus_network::{http::HttpClientError, retry::RetryError};
21use serde_json::Value;
22use thiserror::Error;
23
24use crate::signing::auth::AuthError;
25
26pub type Result<T> = std::result::Result<T, DeriveHttpError>;
28
29#[derive(Debug, Error)]
31pub enum DeriveHttpError {
32 #[error("transport error: {0}")]
34 Transport(String),
35
36 #[error("HTTP {status}: {message}")]
39 Http {
40 status: u16,
42 message: String,
44 retry_after: Option<Duration>,
46 },
47
48 #[error("JSON-RPC error {code}: {message}")]
50 JsonRpc {
51 code: i64,
53 message: String,
55 data: Option<Value>,
57 retry_after: Option<Duration>,
59 },
60
61 #[error("missing `result` in JSON-RPC response for `{method}`")]
63 MissingResult {
64 method: String,
66 },
67
68 #[error("decode error: {0}")]
70 Decode(String),
71
72 #[error("serde error: {0}")]
74 Serde(#[from] serde_json::Error),
75
76 #[error("retry error: {0}")]
78 Retry(#[from] RetryError),
79
80 #[error("auth error: {0}")]
82 Auth(#[from] AuthError),
83
84 #[error("missing credentials for private endpoint `{method}`")]
86 MissingCredentials {
87 method: String,
89 },
90}
91
92impl DeriveHttpError {
93 #[must_use]
95 pub fn transport(msg: impl Into<String>) -> Self {
96 Self::Transport(msg.into())
97 }
98
99 #[must_use]
101 pub fn http(status: u16, message: impl Into<String>) -> Self {
102 Self::Http {
103 status,
104 message: message.into(),
105 retry_after: None,
106 }
107 }
108
109 #[must_use]
111 pub fn decode(msg: impl Into<String>) -> Self {
112 Self::Decode(msg.into())
113 }
114
115 #[must_use]
117 pub const fn retry_after(&self) -> Option<Duration> {
118 match self {
119 Self::Http { retry_after, .. } | Self::JsonRpc { retry_after, .. } => *retry_after,
120 _ => None,
121 }
122 }
123
124 #[must_use]
128 pub fn is_transport_error(&self) -> bool {
129 matches!(self, Self::Transport(_))
130 }
131}
132
133impl From<HttpClientError> for DeriveHttpError {
134 fn from(value: HttpClientError) -> Self {
135 Self::Transport(value.to_string())
136 }
137}
138
139#[cfg(test)]
140mod tests {
141 use rstest::rstest;
142 use serde_json::json;
143
144 use super::*;
145
146 #[rstest]
147 fn test_transport_is_transport_error() {
148 assert!(DeriveHttpError::transport("conn reset").is_transport_error());
149 }
150
151 #[rstest]
152 fn test_http_is_not_transport_error() {
153 assert!(!DeriveHttpError::http(503, "service unavailable").is_transport_error());
154 }
155
156 #[rstest]
157 fn test_jsonrpc_error_carries_code_and_data() {
158 let err = DeriveHttpError::JsonRpc {
159 code: -32602,
160 message: "Invalid params".to_string(),
161 data: Some(json!({"field": "currency"})),
162 retry_after: None,
163 };
164
165 let text = err.to_string();
166 assert!(text.contains("-32602"));
167 assert!(text.contains("Invalid params"));
168 assert!(!err.is_transport_error());
169 }
170
171 #[rstest]
172 fn test_missing_credentials_names_method() {
173 let err = DeriveHttpError::MissingCredentials {
174 method: "private/order".to_string(),
175 };
176
177 assert!(err.to_string().contains("private/order"));
178 }
179
180 #[rstest]
181 fn test_http_client_error_maps_to_transport() {
182 let upstream = HttpClientError::Error("boom".to_string());
183 let mapped: DeriveHttpError = upstream.into();
184 assert!(mapped.is_transport_error());
185 }
186}