use nautilus_network::http::HttpClientError;
use serde::{Deserialize, Serialize};
use thiserror::Error;
#[derive(Debug, Clone, Error)]
pub enum BybitBuildError {
#[error("Missing required category")]
MissingCategory,
#[error("Missing required symbol")]
MissingSymbol,
#[error("Missing required interval")]
MissingInterval,
#[error("Invalid limit: must be between 1 and 1000")]
InvalidLimit,
#[error("Invalid time range: start ({start}) must be less than end ({end})")]
InvalidTimeRange { start: i64, end: i64 },
#[error("Cannot specify both 'orderId' and 'orderLinkId'")]
BothOrderIds,
#[error("Missing required order identifier (orderId or orderLinkId)")]
MissingOrderId,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub struct BybitErrorResponse {
pub ret_code: i32,
pub ret_msg: String,
#[serde(default)]
pub ret_ext_info: Option<serde_json::Value>,
}
#[derive(Debug, Clone, Error)]
pub enum BybitHttpError {
#[error("Missing credentials for authenticated request")]
MissingCredentials,
#[error("Bybit error {error_code}: {message}")]
BybitError { error_code: i32, message: String },
#[error("JSON error: {0}")]
JsonError(String),
#[error("Parameter validation error: {0}")]
ValidationError(String),
#[error("Build error: {0}")]
BuildError(#[from] BybitBuildError),
#[error("Request canceled: {0}")]
Canceled(String),
#[error("Network error: {0}")]
NetworkError(String),
#[error("Unexpected HTTP status code {status}: {body}")]
UnexpectedStatus { status: u16, body: String },
}
impl From<HttpClientError> for BybitHttpError {
fn from(error: HttpClientError) -> Self {
Self::NetworkError(error.to_string())
}
}
impl From<String> for BybitHttpError {
fn from(error: String) -> Self {
Self::ValidationError(error)
}
}
impl From<serde_json::Error> for BybitHttpError {
fn from(error: serde_json::Error) -> Self {
Self::JsonError(error.to_string())
}
}
impl From<BybitErrorResponse> for BybitHttpError {
fn from(error: BybitErrorResponse) -> Self {
Self::BybitError {
error_code: error.ret_code,
message: error.ret_msg,
}
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
#[rstest]
fn test_bybit_build_error_display() {
let error = BybitBuildError::MissingSymbol;
assert_eq!(error.to_string(), "Missing required symbol");
let error = BybitBuildError::InvalidLimit;
assert_eq!(
error.to_string(),
"Invalid limit: must be between 1 and 1000"
);
let error = BybitBuildError::InvalidTimeRange {
start: 100,
end: 50,
};
assert_eq!(
error.to_string(),
"Invalid time range: start (100) must be less than end (50)"
);
}
#[rstest]
fn test_bybit_http_error_from_error_response() {
let error_response = BybitErrorResponse {
ret_code: 10001,
ret_msg: "Parameter error".to_string(),
ret_ext_info: None,
};
let http_error: BybitHttpError = error_response.into();
assert_eq!(http_error.to_string(), "Bybit error 10001: Parameter error");
}
#[rstest]
fn test_bybit_http_error_from_json_error() {
let json_err = serde_json::from_str::<BybitErrorResponse>("invalid json").unwrap_err();
let http_error: BybitHttpError = json_err.into();
assert!(http_error.to_string().contains("JSON error"));
}
#[rstest]
fn test_bybit_http_error_from_string() {
let error_msg = "Invalid parameter value".to_string();
let http_error: BybitHttpError = error_msg.into();
assert_eq!(
http_error.to_string(),
"Parameter validation error: Invalid parameter value"
);
}
#[rstest]
fn test_unexpected_status_error() {
let error = BybitHttpError::UnexpectedStatus {
status: 502,
body: "Server error".to_string(),
};
assert_eq!(
error.to_string(),
"Unexpected HTTP status code 502: Server error"
);
}
}