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