use nautilus_network::http::{HttpClientError, StatusCode};
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Debug, Clone, Error)]
pub enum BitmexBuildError {
#[error("Missing required symbol")]
MissingSymbol,
#[error("Invalid count: must be between 1 and 500")]
InvalidCount,
#[error("Invalid start: must be non-negative")]
InvalidStart,
#[error(
"Invalid time range: start_time ({start_time}) must be less than end_time ({end_time})"
)]
InvalidTimeRange { start_time: i64, end_time: i64 },
#[error("Cannot specify both 'orderID' and 'clOrdID'")]
BothOrderIds,
#[error("Missing required order identifier (orderID or clOrdID)")]
MissingOrderId,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct BitmexErrorResponse {
pub error: BitmexErrorMessage,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct BitmexErrorMessage {
pub message: String,
pub name: String,
}
#[derive(Debug, Clone, Error)]
pub enum BitmexHttpError {
#[error("Missing credentials for authenticated request")]
MissingCredentials,
#[error("BitMEX error {error_name}: {message}")]
BitmexError { error_name: String, message: String },
#[error("JSON error: {0}")]
JsonError(String),
#[error("Parameter validation error: {0}")]
ValidationError(String),
#[error("Build error: {0}")]
BuildError(#[from] BitmexBuildError),
#[error("Request canceled: {0}")]
Canceled(String),
#[error("Network error: {0}")]
NetworkError(String),
#[error("Unexpected HTTP status code {status}: {body}")]
UnexpectedStatus { status: StatusCode, body: String },
}
impl From<HttpClientError> for BitmexHttpError {
fn from(error: HttpClientError) -> Self {
Self::NetworkError(error.to_string())
}
}
impl From<String> for BitmexHttpError {
fn from(error: String) -> Self {
Self::ValidationError(error)
}
}
impl From<serde_json::Error> for BitmexHttpError {
fn from(error: serde_json::Error) -> Self {
Self::JsonError(error.to_string())
}
}
impl From<BitmexErrorResponse> for BitmexHttpError {
fn from(error: BitmexErrorResponse) -> Self {
Self::BitmexError {
error_name: error.error.name,
message: error.error.message,
}
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
use crate::common::testing::load_test_json;
#[rstest]
fn test_bitmex_build_error_display() {
let error = BitmexBuildError::MissingSymbol;
assert_eq!(error.to_string(), "Missing required symbol");
let error = BitmexBuildError::InvalidCount;
assert_eq!(
error.to_string(),
"Invalid count: must be between 1 and 500"
);
let error = BitmexBuildError::InvalidTimeRange {
start_time: 100,
end_time: 50,
};
assert_eq!(
error.to_string(),
"Invalid time range: start_time (100) must be less than end_time (50)"
);
}
#[rstest]
fn test_bitmex_error_response_from_json() {
let json = load_test_json("http_error_response.json");
let error_response: BitmexErrorResponse = serde_json::from_str(&json).unwrap();
assert_eq!(error_response.error.message, "Invalid API Key.");
assert_eq!(error_response.error.name, "HTTPError");
}
#[rstest]
fn test_bitmex_http_error_from_error_response() {
let error_response = BitmexErrorResponse {
error: BitmexErrorMessage {
message: "Rate limit exceeded".to_string(),
name: "RateLimitError".to_string(),
},
};
let http_error: BitmexHttpError = error_response.into();
assert_eq!(
http_error.to_string(),
"BitMEX error RateLimitError: Rate limit exceeded"
);
}
#[rstest]
fn test_bitmex_http_error_from_json_error() {
let json_err = serde_json::from_str::<BitmexErrorResponse>("invalid json").unwrap_err();
let http_error: BitmexHttpError = json_err.into();
assert!(http_error.to_string().contains("JSON error"));
}
#[rstest]
fn test_bitmex_http_error_from_string() {
let error_msg = "Invalid parameter value".to_string();
let http_error: BitmexHttpError = error_msg.into();
assert_eq!(
http_error.to_string(),
"Parameter validation error: Invalid parameter value"
);
}
#[rstest]
fn test_unexpected_status_error() {
let error = BitmexHttpError::UnexpectedStatus {
status: StatusCode::BAD_GATEWAY,
body: "Server error".to_string(),
};
assert_eq!(
error.to_string(),
"Unexpected HTTP status code 502 Bad Gateway: Server error"
);
}
}