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}