Skip to main content

stygian_charon/token_lifecycle/
error.rs

1//! Errors returned by the token lifecycle module (T91).
2
3use thiserror::Error;
4
5use crate::token_lifecycle::invalidation::InvalidationReason;
6use crate::vendor_classifier::VendorId;
7
8/// Errors returned by
9/// [`TokenValidator`][crate::token_lifecycle::TokenValidator]
10/// when it rejects a token submission.
11///
12/// Carries the structured [`InvalidationReason`] (which itself
13/// embeds the vendor family + challenge class) and a short
14/// human-readable message suitable for operator logs. The
15/// diagnostic payload the runner exposes can switch on the
16/// [`InvalidationReason::kind`][crate::token_lifecycle::InvalidationReason::kind]
17/// tag to route the failure into the correct per-family audit
18/// log.
19#[derive(Debug, Clone, PartialEq, Eq, Error)]
20#[error("{message}")]
21pub struct TokenLifecycleError {
22    /// Structured reason the validator rejected the token.
23    pub reason: InvalidationReason,
24    /// Human-readable message suitable for operator logs.
25    pub message: String,
26}
27
28impl TokenLifecycleError {
29    /// Build a [`TokenLifecycleError`] from a reason + a
30    /// human-readable message.
31    ///
32    /// # Example
33    ///
34    /// ```
35    /// use stygian_charon::token_lifecycle::{
36    ///     ChallengeClass, InvalidationReason, TokenLifecycleError,
37    /// };
38    /// use stygian_charon::vendor_classifier::VendorId;
39    ///
40    /// let err = TokenLifecycleError::new(
41    ///     InvalidationReason::Expired {
42    ///         vendor: VendorId::Cloudflare,
43    ///         challenge_class: ChallengeClass::Interstitial,
44    ///         age_secs: 1900,
45    ///         ttl_secs: 1800,
46    ///     },
47    ///     "Cloudflare token expired",
48    /// );
49    /// assert_eq!(err.message, "Cloudflare token expired");
50    /// assert_eq!(err.reason.vendor_family(), VendorId::Cloudflare);
51    /// ```
52    #[must_use]
53    pub fn new(reason: InvalidationReason, message: impl Into<String>) -> Self {
54        Self {
55            reason,
56            message: message.into(),
57        }
58    }
59
60    /// Vendor family the error is attributed to.
61    #[must_use]
62    pub const fn vendor_family(&self) -> VendorId {
63        self.reason.vendor_family()
64    }
65}