use nautilus_network::http::{HttpClientError, StatusCode};
use serde::Deserialize;
use thiserror::Error;
use crate::common::consts::should_retry_error_code;
#[derive(Debug, Error)]
pub enum BuildError {
#[error("Missing required instrument ID")]
MissingInstId,
#[error("Missing required bar interval")]
MissingBar,
#[error("Cannot specify both 'after' and 'before' cursors")]
BothCursors,
#[error(
"Invalid time range: after_ms ({after_ms}) must be greater than before_ms ({before_ms})"
)]
InvalidTimeRange { after_ms: i64, before_ms: i64 },
#[error("Cursor timestamp appears to be in nanoseconds (> 13 digits)")]
CursorIsNanoseconds,
#[error("Limit exceeds maximum of 300")]
LimitTooHigh,
}
#[derive(Clone, Debug, Deserialize)]
pub struct OKXErrorResponse {
pub error: OKXErrorMessage,
}
#[derive(Clone, Debug, Deserialize)]
pub struct OKXErrorMessage {
pub message: String,
pub name: String,
}
#[derive(Debug, Error)]
pub enum OKXHttpError {
#[error("Missing credentials for authenticated request")]
MissingCredentials,
#[error("OKX error {error_code}: {message}")]
OkxError { error_code: String, message: String },
#[error("JSON error: {0}")]
JsonError(String),
#[error("Parameter validation error: {0}")]
ValidationError(String),
#[error("Request canceled: {0}")]
Canceled(String),
#[error("Network error: {0}")]
HttpClientError(#[from] HttpClientError),
#[error("Unexpected HTTP status code {status}: {body}")]
UnexpectedStatus { status: StatusCode, body: String },
}
impl From<String> for OKXHttpError {
fn from(error: String) -> Self {
Self::ValidationError(error)
}
}
impl From<serde_json::Error> for OKXHttpError {
fn from(error: serde_json::Error) -> Self {
Self::JsonError(error.to_string())
}
}
impl OKXHttpError {
#[must_use]
pub fn is_retryable(&self) -> bool {
match self {
Self::HttpClientError(_) => true,
Self::UnexpectedStatus { status, .. } => {
status.as_u16() >= 500 || status.as_u16() == 429
}
Self::OkxError { error_code, .. } => should_retry_error_code(error_code),
_ => false,
}
}
}
#[cfg(test)]
mod tests {
use rstest::rstest;
use super::*;
#[rstest]
#[case(OKXHttpError::HttpClientError(HttpClientError::Error("timeout".to_string())), true)]
#[case(OKXHttpError::UnexpectedStatus { status: StatusCode::INTERNAL_SERVER_ERROR, body: String::new() }, true)]
#[case(OKXHttpError::UnexpectedStatus { status: StatusCode::TOO_MANY_REQUESTS, body: String::new() }, true)]
#[case(OKXHttpError::UnexpectedStatus { status: StatusCode::FORBIDDEN, body: String::new() }, false)]
#[case(OKXHttpError::OkxError { error_code: "50001".to_string(), message: String::new() }, true)]
#[case(OKXHttpError::OkxError { error_code: "50011".to_string(), message: String::new() }, true)]
#[case(OKXHttpError::OkxError { error_code: "51000".to_string(), message: String::new() }, false)]
#[case(OKXHttpError::JsonError("bad".to_string()), false)]
#[case(OKXHttpError::ValidationError("bad".to_string()), false)]
#[case(OKXHttpError::MissingCredentials, false)]
#[case(OKXHttpError::Canceled("shutdown".to_string()), false)]
fn test_is_retryable(#[case] error: OKXHttpError, #[case] expected: bool) {
assert_eq!(error.is_retryable(), expected);
}
}