borsa_core/
error.rs

1use thiserror::Error;
2
3/// Unified error type for the borsa workspace.
4///
5/// This wraps capability mismatches, argument validation errors, provider-tagged
6/// failures, not-found conditions, and an aggregate for multi-provider attempts.
7#[derive(Debug, Error)]
8pub enum BorsaError {
9    /// The requested capability is not implemented by the target connector.
10    #[error("unsupported capability: {capability}")]
11    Unsupported {
12        /// A capability string describing what was requested (e.g. "history/crypto").
13        capability: &'static str,
14    },
15
16    /// Issues with the returned or expected data (missing fields, etc.).
17    #[error("data issue: {0}")]
18    Data(String),
19
20    /// Invalid input argument.
21    #[error("invalid argument: {0}")]
22    InvalidArg(String),
23
24    /// An individual connector returned an error.
25    #[error("{connector} failed: {msg}")]
26    Connector {
27        /// Connector name that failed.
28        connector: String,
29        /// Human-readable error message.
30        msg: String,
31    },
32
33    /// Unknown/opaque error.
34    #[error("unknown error: {0}")]
35    Other(String),
36
37    /// A resource or symbol could not be found.
38    #[error("not found: {what}")]
39    NotFound {
40        /// Description of missing resource, e.g. "quote for AAPL".
41        what: String,
42    },
43
44    /// All selected providers failed; contains the individual failures.
45    #[error("all providers failed: {0:?}")]
46    AllProvidersFailed(Vec<BorsaError>),
47
48    /// An individual provider call exceeded the configured timeout.
49    #[error("provider timed out: {capability} via {connector}")]
50    ProviderTimeout {
51        /// Connector name that timed out.
52        connector: String,
53        /// Capability label (e.g. "history", "search", "quote").
54        capability: &'static str,
55    },
56
57    /// The overall request exceeded the configured deadline.
58    #[error("request timed out: {capability}")]
59    RequestTimeout {
60        /// Capability label for which the request timed out.
61        capability: &'static str,
62    },
63
64    /// All attempted providers timed out for the requested capability.
65    #[error("all providers timed out: {capability}")]
66    AllProvidersTimedOut {
67        /// Capability label that timed out across all providers.
68        capability: &'static str,
69    },
70}
71
72impl BorsaError {
73    /// Helper: build an `Unsupported` error for a capability string.
74    #[must_use]
75    pub const fn unsupported(cap: &'static str) -> Self {
76        Self::Unsupported { capability: cap }
77    }
78    /// Helper: build a `Connector` error with the connector name and message.
79    pub fn connector(connector: impl Into<String>, msg: impl Into<String>) -> Self {
80        Self::Connector {
81            connector: connector.into(),
82            msg: msg.into(),
83        }
84    }
85
86    /// Helper: build a `NotFound` error for a description of the missing resource.
87    pub fn not_found(what: impl Into<String>) -> Self {
88        Self::NotFound { what: what.into() }
89    }
90
91    /// Helper: build a `ProviderTimeout` error.
92    pub fn provider_timeout(connector: impl Into<String>, capability: &'static str) -> Self {
93        Self::ProviderTimeout {
94            connector: connector.into(),
95            capability,
96        }
97    }
98
99    /// Helper: build a `RequestTimeout` error.
100    #[must_use]
101    pub const fn request_timeout(capability: &'static str) -> Self {
102        Self::RequestTimeout { capability }
103    }
104}
105
106impl From<paft::Error> for BorsaError {
107    fn from(err: paft::Error) -> Self {
108        use paft::Error as E;
109        match err {
110            // Money runtime issues indicate a data/operation problem at runtime
111            E::Money(_) => Self::Data(err.to_string()),
112            // Input/validation problems from parsers, request builders, or canonicalization
113            E::Core(_) | E::Domain(_) | E::Market(_) | E::MoneyParse(_) | E::Canonical(_) => {
114                Self::InvalidArg(err.to_string())
115            }
116        }
117    }
118}
119
120impl From<paft::market::MarketError> for BorsaError {
121    fn from(e: paft::market::MarketError) -> Self {
122        Self::InvalidArg(e.to_string())
123    }
124}
125
126impl From<paft::domain::DomainError> for BorsaError {
127    fn from(e: paft::domain::DomainError) -> Self {
128        Self::InvalidArg(e.to_string())
129    }
130}
131
132impl From<paft::core::PaftError> for BorsaError {
133    fn from(e: paft::core::PaftError) -> Self {
134        Self::InvalidArg(e.to_string())
135    }
136}
137
138impl From<paft::money::MoneyError> for BorsaError {
139    fn from(e: paft::money::MoneyError) -> Self {
140        Self::Data(e.to_string())
141    }
142}