Skip to main content

axonflow_sdk_rust/
error.rs

1use crate::types::decisions::RateLimitEnvelope;
2use thiserror::Error;
3
4#[derive(Error, Debug)]
5pub enum AxonFlowError {
6    #[error("HTTP request failed: {0}")]
7    HttpError(#[from] reqwest::Error),
8    #[error("Serialization/Deserialization failed: {0}")]
9    SerdeError(#[from] serde_json::Error),
10    #[error("API error ({status}): {message}")]
11    ApiError { status: u16, message: String },
12    /// Tier-cap 429 with a parsed V1 upgrade envelope. Distinct from a
13    /// generic 429 ApiError because callers should branch on the upgrade
14    /// fields (tier / compare_url / buy_url) without re-parsing the
15    /// raw body. Mirrors the cross-SDK 429-with-envelope pattern
16    /// (#1982 / #1958). Boxed to keep `AxonFlowError` small —
17    /// `RateLimitEnvelope` is ~176 bytes and would dominate the enum
18    /// otherwise (clippy::result_large_err).
19    #[error("Rate limited (tier={}, limit_type={}): {}", .envelope.tier, .envelope.limit_type, .envelope.error)]
20    RateLimited { envelope: Box<RateLimitEnvelope> },
21    #[error("Configuration error: {0}")]
22    ConfigError(String),
23    #[error("AxonFlow platform is unavailable: {0}")]
24    Unavailable(String),
25    /// A Decision Mode `redact_pii` obligation could not be discharged through
26    /// the engine — it named no request-phase fulfillment, advertised a
27    /// content-type the PEP is not holding, named an endpoint this client will
28    /// not call, the engine call failed / returned non-200, or the engine
29    /// reported the redactor did not run (`redaction_evaluated=false`).
30    ///
31    /// This is the fail-closed signal of the PEP contract (ADR-056, #2563): the
32    /// caller MUST block, never forward the unredacted content. There is NO code
33    /// path in which the SDK redacts locally — fulfillment is always the engine
34    /// round-trip — so an obligation the engine cannot discharge fails closed
35    /// here rather than leaking PII.
36    #[error("Obligation not engine-fulfillable: {0}")]
37    ObligationNotFulfillable(String),
38}
39
40impl AxonFlowError {
41    pub fn is_retryable(&self) -> bool {
42        match self {
43            AxonFlowError::HttpError(e) => e.is_timeout() || e.is_connect(),
44            AxonFlowError::ApiError { status, .. } => *status >= 500 || *status == 429,
45            AxonFlowError::RateLimited { .. } => true,
46            AxonFlowError::Unavailable(_) => true,
47            _ => false,
48        }
49    }
50
51    pub fn is_fail_open_eligible(&self) -> bool {
52        match self {
53            AxonFlowError::HttpError(e) => e.is_timeout() || e.is_connect(),
54            AxonFlowError::ApiError { status, .. } => *status >= 500 || *status == 429,
55            AxonFlowError::RateLimited { .. } => true,
56            AxonFlowError::Unavailable(_) => true,
57            _ => false,
58        }
59    }
60}