Skip to main content

webgates_sessions/
errors.rs

1//! Session-layer error types.
2//!
3//! This module defines the typed error categories used by framework-agnostic
4//! session workflows.
5
6use thiserror::Error;
7
8/// Result type for fallible session operations.
9pub type Result<T> = std::result::Result<T, SessionError>;
10
11/// Root error type for framework-agnostic session workflows.
12#[derive(Debug, Error, Clone, PartialEq, Eq)]
13pub enum SessionError {
14    /// Configuration is invalid or internally inconsistent.
15    #[error(transparent)]
16    Config(#[from] ConfigError),
17
18    /// Token generation, hashing, validation, or rotation failed.
19    #[error(transparent)]
20    Token(#[from] TokenError),
21
22    /// Renewal coordination or policy evaluation failed.
23    #[error(transparent)]
24    Renewal(#[from] RenewalError),
25
26    /// Session state could not be persisted, loaded, or atomically updated.
27    #[error(transparent)]
28    Repository(#[from] RepositoryError),
29
30    /// Logout or revocation failed.
31    #[error(transparent)]
32    Revocation(#[from] RevocationError),
33
34    /// Fallback error for unsupported or unavailable session functionality.
35    #[error("{message}")]
36    Unimplemented {
37        /// Safe, caller-facing message describing the unsupported behavior.
38        message: String,
39    },
40}
41
42impl SessionError {
43    /// Creates an error for unsupported or unavailable session behavior.
44    pub fn unimplemented(message: impl Into<String>) -> Self {
45        Self::Unimplemented {
46            message: message.into(),
47        }
48    }
49}
50
51/// Configuration-related session errors.
52#[derive(Debug, Error, Clone, PartialEq, Eq)]
53pub enum ConfigError {
54    /// A required duration or threshold was invalid.
55    #[error("invalid session configuration")]
56    Invalid,
57}
58
59/// Token-related session errors.
60#[derive(Debug, Error, Clone, PartialEq, Eq)]
61pub enum TokenError {
62    /// Token generation failed.
63    #[error("failed to generate token material")]
64    GenerationFailed,
65
66    /// Token hashing failed.
67    #[error("failed to hash token material")]
68    HashFailed,
69
70    /// Auth token issuance failed.
71    #[error("failed to issue auth token")]
72    AuthIssuanceFailed,
73
74    /// Supplied token material was malformed or unusable.
75    #[error("invalid token material")]
76    InvalidTokenMaterial,
77
78    /// Refresh-token generator configuration was invalid.
79    #[error("invalid refresh token length")]
80    InvalidRefreshTokenLength,
81
82    /// Token generation or validation failed for an unspecified reason.
83    #[error("token operation failed")]
84    Failed,
85}
86
87/// Renewal-flow errors.
88#[derive(Debug, Error, Clone, PartialEq, Eq)]
89pub enum RenewalError {
90    /// The current session is not eligible for renewal.
91    #[error("session is not eligible for renewal")]
92    NotEligible,
93
94    /// A lease could not be acquired because another renewal is in progress.
95    #[error("renewal lease unavailable")]
96    LeaseUnavailable,
97
98    /// A refresh token replay was detected.
99    #[error("refresh token replay detected")]
100    ReplayDetected,
101
102    /// Atomic rotation did not succeed.
103    #[error("session rotation failed")]
104    RotationFailed,
105}
106
107/// Repository boundary errors for session persistence.
108#[derive(Debug, Error, Clone, PartialEq, Eq)]
109pub enum RepositoryError {
110    /// The requested session record was not found.
111    #[error("session not found")]
112    SessionNotFound,
113
114    /// The requested session family record was not found.
115    #[error("session family not found")]
116    SessionFamilyNotFound,
117
118    /// An optimistic or compare-and-swap style write lost a race.
119    #[error("concurrent session update detected")]
120    Conflict,
121
122    /// Stored session data was invalid or inconsistent.
123    #[error("invalid persisted session state")]
124    InvalidState,
125
126    /// A backend-specific operation failed.
127    #[error("{message}")]
128    Backend {
129        /// Safe, caller-facing backend failure summary.
130        message: String,
131    },
132}
133
134impl RepositoryError {
135    /// Creates a backend error with a safe, caller-facing message.
136    pub fn backend(message: impl Into<String>) -> Self {
137        Self::Backend {
138            message: message.into(),
139        }
140    }
141}
142
143impl From<crate::repository::RepositoryError> for RepositoryError {
144    fn from(value: crate::repository::RepositoryError) -> Self {
145        match value {
146            crate::repository::RepositoryError::SessionNotFound => Self::SessionNotFound,
147            crate::repository::RepositoryError::SessionFamilyNotFound => {
148                Self::SessionFamilyNotFound
149            }
150            crate::repository::RepositoryError::Conflict => Self::Conflict,
151            crate::repository::RepositoryError::InvalidState => Self::InvalidState,
152            crate::repository::RepositoryError::Backend { message } => Self::Backend { message },
153        }
154    }
155}
156
157impl From<crate::repository::RepositoryError> for SessionError {
158    fn from(value: crate::repository::RepositoryError) -> Self {
159        Self::Repository(value.into())
160    }
161}
162
163/// Revocation and logout related errors.
164#[derive(Debug, Error, Clone, PartialEq, Eq)]
165pub enum RevocationError {
166    /// The target session could not be revoked because it does not exist.
167    #[error("cannot revoke missing session")]
168    SessionNotFound,
169
170    /// The target session family could not be revoked because it does not exist.
171    #[error("cannot revoke missing session family")]
172    SessionFamilyNotFound,
173
174    /// The revocation operation could not be completed.
175    #[error("revocation failed")]
176    Failed,
177}