Skip to main content

alat/
error.rs

1//! Error types for the Wema ALAT SDK.
2//!
3//! Every fallible SDK call returns [`Result<T>`], which is an alias for
4//! `std::result::Result<T, Error>`. The [`enum@Error`] enum models the four
5//! distinct ways a call can fail, so that callers can branch on the *kind* of
6//! problem (e.g. retry on a transport error, surface a validation message to a
7//! user, or alert on a bank-side rejection) rather than parsing strings.
8
9use thiserror::Error;
10
11/// All errors that can occur while using the Wema ALAT SDK.
12///
13/// The variants are ordered from "lowest level" (transport) to "highest level"
14/// (a perfectly well-formed HTTP exchange in which the *bank* reported a
15/// business failure inside the response envelope).
16#[derive(Debug, Error)]
17pub enum Error {
18    /// A transport-level failure from the underlying HTTP client (`reqwest`).
19    ///
20    /// This means the request never produced a usable HTTP response: DNS could
21    /// not be resolved, the connection timed out, TLS negotiation failed, etc.
22    /// These are typically transient and safe to retry **for idempotent reads**
23    /// (never blindly retry a transfer — use a status-check endpoint instead).
24    #[error("network/transport error: {0}")]
25    Network(#[from] reqwest::Error),
26
27    /// The server returned a non-success HTTP status code (4xx/5xx).
28    ///
29    /// The gateway itself rejected the request before (or instead of) producing
30    /// a business response — e.g. `401 Unauthorized` (bad subscription/API key),
31    /// `403 Forbidden` (not subscribed to the product), `429 Too Many Requests`,
32    /// or `500`. The raw response body is preserved verbatim for diagnostics.
33    #[error("HTTP {status} from ALAT gateway: {body}")]
34    Http {
35        /// The HTTP status code returned by the Azure APIM gateway.
36        status: reqwest::StatusCode,
37        /// The raw, un-parsed response body (often a JSON or plain-text reason).
38        body: String,
39    },
40
41    /// The HTTP call succeeded (2xx) but the JSON body could not be decoded into
42    /// the expected Rust type.
43    ///
44    /// This usually signals a drift between this SDK's models and the live API
45    /// schema. The offending body (truncated) is included so the mismatch can be
46    /// diagnosed without re-running the request.
47    #[error("failed to decode ALAT response: {message}\n--- body ---\n{body}")]
48    Decode {
49        /// The `serde_json` error message describing the mismatch.
50        message: String,
51        /// The response body that failed to decode (truncated for readability).
52        body: String,
53    },
54
55    /// The bank reported a *business* failure inside an otherwise valid response.
56    ///
57    /// The HTTP exchange was fine (2xx, well-formed JSON), but the response
58    /// envelope's success flag was negative (`hasError: true`, `successful:
59    /// false`, or `status: false`). Examples: "Invalid BVN", "Insufficient
60    /// funds", "Customer not found". The human-readable [`message`](Self::Api::message),
61    /// machine [`code`](Self::Api::code), and any field-level [`errors`](Self::Api::errors)
62    /// are surfaced directly from the envelope.
63    #[error("ALAT API rejected the request: {message}{}", .code.as_deref().map(|c| format!(" (code: {c})")).unwrap_or_default())]
64    Api {
65        /// Human-readable message from the response envelope.
66        message: String,
67        /// Optional machine-readable error code (e.g. `"InvalidBvn"`).
68        code: Option<String>,
69        /// Optional list of granular, field-level error messages.
70        errors: Vec<String>,
71    },
72
73    /// The SDK could not be configured or a request could not be constructed.
74    ///
75    /// Raised before any network I/O — e.g. a subscription/API key that contains
76    /// bytes that are not valid in an HTTP header, or a required credential
77    /// (such as the bills/airtime `access` key) that was not supplied.
78    #[error("configuration error: {0}")]
79    Configuration(String),
80
81    /// Client-side validation rejected the input before a request was sent.
82    ///
83    /// Used for fail-fast guards that save a doomed round-trip, such as an empty
84    /// salt key when signing a transfer.
85    #[error("validation error: {0}")]
86    Validation(String),
87}
88
89/// Convenience alias used by every fallible operation in this crate.
90pub type Result<T> = std::result::Result<T, Error>;