Skip to main content

dhan_rs/
error.rs

1//! Error types for the `dhan-rs` crate.
2//!
3//! All fallible operations in this crate return [`Result<T>`], which is an
4//! alias for `std::result::Result<T, DhanError>`.
5//!
6//! [`DhanError`] covers:
7//! - **API errors** — Structured error responses from DhanHQ (codes DH-901 to DH-910)
8//! - **HTTP status errors** — Unexpected status codes with response body
9//! - **HTTP transport errors** — Network, TLS, timeout failures
10//! - **JSON errors** — Deserialization failures
11//! - **WebSocket errors** — Connection and protocol errors
12//! - **URL errors** — Malformed URL construction
13//! - **Invalid arguments** — Client-side validation errors
14
15use std::fmt;
16
17/// Error response returned by the DhanHQ API.
18#[derive(Debug, Clone, serde::Deserialize)]
19#[serde(rename_all = "camelCase")]
20pub struct ApiErrorBody {
21    /// Category of the error (e.g. "Invalid Authentication").
22    #[serde(default)]
23    pub error_type: Option<String>,
24    /// Dhan error code (e.g. "DH-901").
25    #[serde(default)]
26    pub error_code: Option<String>,
27    /// Human-readable description of the error.
28    #[serde(default)]
29    pub error_message: Option<String>,
30}
31
32impl fmt::Display for ApiErrorBody {
33    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
34        write!(
35            f,
36            "[{}] {}: {}",
37            self.error_code.as_deref().unwrap_or("UNKNOWN"),
38            self.error_type.as_deref().unwrap_or("Unknown Error"),
39            self.error_message.as_deref().unwrap_or("No message"),
40        )
41    }
42}
43
44/// All possible errors produced by the `dhan-rs` client.
45#[derive(Debug, thiserror::Error)]
46pub enum DhanError {
47    /// An error response returned by the DhanHQ REST API.
48    #[error("API error: {0}")]
49    Api(ApiErrorBody),
50
51    /// The server returned an unexpected HTTP status code.
52    #[error("HTTP {status}: {body}")]
53    HttpStatus {
54        /// The HTTP status code.
55        status: reqwest::StatusCode,
56        /// The response body text.
57        body: String,
58    },
59
60    /// A network or transport-level error from `reqwest`.
61    #[error("HTTP request failed: {0}")]
62    Http(#[from] reqwest::Error),
63
64    /// Failed to deserialize a JSON response body.
65    #[error("JSON deserialization error: {0}")]
66    Json(#[from] serde_json::Error),
67
68    /// A WebSocket-level error (boxed to keep `Result<T>` small).
69    #[error("WebSocket error: {0}")]
70    WebSocket(Box<tokio_tungstenite::tungstenite::Error>),
71
72    /// An error building or parsing a URL.
73    #[error("URL error: {0}")]
74    Url(#[from] url::ParseError),
75
76    /// The caller provided an invalid argument.
77    #[error("Invalid argument: {0}")]
78    InvalidArgument(String),
79}
80
81impl From<tokio_tungstenite::tungstenite::Error> for DhanError {
82    fn from(err: tokio_tungstenite::tungstenite::Error) -> Self {
83        DhanError::WebSocket(Box::new(err))
84    }
85}
86
87/// Convenience alias used throughout the crate.
88pub type Result<T> = std::result::Result<T, DhanError>;