Skip to main content

slim_session/
errors.rs

1// Copyright AGNTCY Contributors (https://github.com/agntcy)
2// SPDX-License-Identifier: Apache-2.0
3
4use slim_datapath::errors::{ErrorPayload, MessageContext};
5use slim_datapath::messages::Name;
6// Third-party crates
7use thiserror::Error;
8
9// Local crate
10use slim_auth::errors::AuthError;
11use slim_datapath::api::{ProtoMessage, ProtoSessionMessageType, ProtoSessionType};
12use slim_datapath::messages::utils::MessageError;
13use slim_mls::errors::MlsError;
14use tonic::Status;
15
16use crate::SessionMessage;
17use crate::subscription_manager::SubscriptionAckError;
18
19#[derive(Error, Debug)]
20pub enum SessionError {
21    // Transport and channel errors
22    #[error("SLIM channel closed")]
23    SlimChannelClosed,
24    #[error("error receiving message from SLIM")]
25    SlimReception(#[from] Status),
26
27    // Message processing and validation errors
28    #[error("message error")]
29    MessageError(#[from] MessageError),
30    #[error("missing removed participant in GroupRemove message")]
31    MissingRemovedParticipantInGroupRemove,
32    #[error("missing group name in JoinRequest message")]
33    MissingGroupNameInJoinRequest,
34    #[error("ping state not initialized")]
35    PingStateNotInitialized,
36    #[error("missing channel name for group session")]
37    MissingChannelName,
38    #[error("session type unknown: {0:?}")]
39    SessionTypeUnknown(ProtoSessionType),
40    #[error("session message type unexpected: {0:?}")]
41    SessionMessageInternalUnexpected(Box<SessionMessage>),
42    #[error("session message type unknown: {0:?}")]
43    SessionMessageTypeUnknown(ProtoSessionMessageType),
44    #[error("message type unexpected: {0:?}")]
45    MessageTypeUnexpected(Box<ProtoMessage>),
46    #[error("session message type unexpected: {0:?}")]
47    SessionMessageTypeUnexpected(ProtoSessionMessageType),
48    #[error("error getting the participants list")]
49    ParticipantsListQueryFailed,
50    #[error("unexpected error")]
51    UnexpectedError { source: Box<SessionError> },
52
53    // Lookup and missing entities
54    #[error("session not found: {0}")]
55    SessionNotFound(u32),
56    #[error("subscription not found: {0}")]
57    SubscriptionNotFound(Name),
58
59    // Session lifecycle and state
60    #[error("session builder: not all required fields set")]
61    SessionBuilderIncomplete,
62    #[error("message lost for session id: {0}")]
63    MessageLost(u32),
64    #[error("session closed")]
65    SessionClosed,
66    #[error("receive timeout waiting for message")]
67    ReceiveTimeout,
68    #[error("session id already used: {0}")]
69    SessionIdAlreadyUsed(u32),
70    #[error("invalid session id: {0}")]
71    InvalidSessionId(u32),
72
73    // Cryptography (MLS)
74    #[error("mls operation error")]
75    MlsOp(#[from] MlsError),
76
77    // Authorization and roles
78    #[error("auth error")]
79    Auth(#[from] AuthError),
80
81    // Acknowledgements and routing
82    #[error("error receiving ack for message: {0}")]
83    AckReception(String),
84    #[error("subscription ack failed: {0}")]
85    SubscriptionAckFailed(#[source] SubscriptionAckError),
86    #[error("unknown destination: {0}")]
87    UnknownDestination(Name),
88
89    // Session membership and permissions
90    #[error("participant not found in group: {0}")]
91    ParticipantNotFound(Name),
92    #[error("participant already in group: {0}")]
93    ParticipantAlreadyInGroup(Name),
94    #[error("cannot invite participant to point-to-point session")]
95    CannotInviteToP2P,
96    #[error("cannot remove participant from point-to-point session")]
97    CannotRemoveFromP2P,
98    #[error("only initiator can modify participants")]
99    NotInitiator,
100
101    // Routing and delivery failures
102    #[error("error sending session internal message to session controller")]
103    SessionControllerSendFailed,
104    #[error("error sending new session notification to app")]
105    NewSessionSendFailed,
106    #[error("error sending session delete message to session layer")]
107    SessionDeleteMessageSendFailed,
108    #[error("error sending data message to application")]
109    ApplicationMessageSendFailed,
110    #[error("error sending data message to slim")]
111    SlimMessageSendFailed,
112    #[error("send failure reported from slim: {ctx}")]
113    SlimSendFailure { ctx: Box<ErrorPayload> },
114
115    // Session lifecycle and state (continued)
116    #[error("session is draining - drop message")]
117    SessionDrainingDrop,
118    #[error("session already closed")]
119    SessionAlreadyClosed,
120    #[error("session cleanup failed: {details}")]
121    SessionCleanupFailed { details: String },
122    #[error("message send retries exhausted for id={id}")]
123    MessageSendRetryFailed { id: u32 },
124    #[error("message receive retries exhausted for id={id}")]
125    MessageReceiveRetryFailed { id: u32 },
126    #[error("session sender is shutdown, cannot send messages")]
127    SessionSenderShutdown,
128
129    // Message construction and extraction contexts
130    #[error("missing payload: {context}")]
131    MissingPayload { context: &'static str },
132    #[error("message build failed: {0}")]
133    MessageBuild(MessageError),
134    #[error("message payload extract failed in {context}: {source}")]
135    PayloadExtract {
136        context: &'static str,
137        source: MessageError,
138    },
139
140    // Participant connectivity
141    #[error("missing mls payload in welcome message")]
142    WelcomeMessageMissingMlsPayload,
143    #[error("invalid join request payload")]
144    InvalidJoinRequestPayload,
145    #[error("participant disconnected: {0}")]
146    ParticipantDisconnected(Name),
147    #[error("missing participant name on disconnection event")]
148    MissingParticipantNameOnDisconnection,
149
150    // Moderator task orchestration
151    #[error("no pending requests for the given key: {0}")]
152    TimerNotFound(u32),
153    #[error("phase not supported for task")]
154    ModeratorTaskUnsupportedPhase,
155    #[error("unexpected timer id: {0}")]
156    ModeratorTaskUnexpectedTimerId(u32),
157    #[error("failed to add participant to session")]
158    ModeratorTaskAddFailed { source: Box<SessionError> },
159    #[error("failed to remove participant from session")]
160    ModeratorTaskRemoveFailed { source: Box<SessionError> },
161    #[error("failed to update session")]
162    ModeratorTaskUpdateFailed { source: Box<SessionError> },
163    #[error("failed to close session")]
164    ModeratorTaskCloseFailed { source: Box<SessionError> },
165}
166
167impl SessionError {
168    // Helper constructors for structured mapping without repeating strings.
169    pub fn build_error(err: MessageError) -> Self {
170        SessionError::MessageBuild(err)
171    }
172    pub fn extract_error(context: &'static str, err: MessageError) -> Self {
173        SessionError::PayloadExtract {
174            context,
175            source: err,
176        }
177    }
178    pub fn cleanup_failed<E: std::fmt::Display>(e: E) -> Self {
179        SessionError::SessionCleanupFailed {
180            details: e.to_string(),
181        }
182    }
183
184    // Helpers to construct new structured retry failure variants
185    pub fn send_retry_failed(id: u32) -> Self {
186        SessionError::MessageSendRetryFailed { id }
187    }
188
189    pub fn receive_retry_failed(id: u32) -> Self {
190        SessionError::MessageReceiveRetryFailed { id }
191    }
192
193    /// Extract session context from SlimSendFailure error
194    /// Returns None if the error is not a SlimSendFailure or if it lacks session context
195    pub fn session_context(&self) -> Option<&MessageContext> {
196        match self {
197            SessionError::SlimSendFailure { ctx } => ctx.session_context.as_ref(),
198            _ => None,
199        }
200    }
201
202    /// Check if this error is for a command message
203    pub fn is_command_message_error(&self) -> bool {
204        self.session_context()
205            .map(|ctx| ctx.get_session_message_type().is_command_message())
206            .unwrap_or(false)
207    }
208}