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>;