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::api::ProtoName;
5use slim_datapath::errors::{ErrorPayload, MessageContext};
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 new participant in GroupAdd message")]
33    MissingNewParticipantInGroupAdd,
34    #[error("missing group name in JoinRequest message")]
35    MissingGroupNameInJoinRequest,
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("malformed participant settings")]
51    MalformedParticipant,
52    #[error("missing participant settings")]
53    MissingParticipantSettings,
54    #[error("identity key is empty")]
55    SignatureKeyIsEmpty,
56    #[error("identity key collection failed with auth error: {0:?}")]
57    SignatureKeyCollectionFailedWithAuthErr(AuthError),
58    #[error("unexpected error")]
59    UnexpectedError { source: Box<SessionError> },
60
61    // Lookup and missing entities
62    #[error("session not found: {0}")]
63    SessionNotFound(u32),
64    #[error("subscription not found: {0}")]
65    SubscriptionNotFound(ProtoName),
66
67    // Session lifecycle and state
68    #[error("session builder: not all required fields set")]
69    SessionBuilderIncomplete,
70    #[error("message lost for session id: {0}")]
71    MessageLost(u32),
72    #[error("session closed")]
73    SessionClosed,
74    #[error("receive timeout waiting for message")]
75    ReceiveTimeout,
76    #[error("session id already used: {0}")]
77    SessionIdAlreadyUsed(u32),
78    #[error("invalid session id: {0}")]
79    InvalidSessionId(u32),
80
81    // Cryptography (MLS)
82    #[error("mls operation error")]
83    MlsOp(#[from] MlsError),
84
85    // Authorization and roles
86    #[error("auth error: {0}")]
87    Auth(#[from] AuthError),
88    #[error("duplicate control message replay: message_id={message_id}")]
89    ControlMessageReplay { message_id: u32 },
90
91    // Acknowledgements and routing
92    #[error("error receiving ack for message: {0}")]
93    AckReception(String),
94    #[error("subscription ack failed: {0}")]
95    SubscriptionAckFailed(#[source] SubscriptionAckError),
96    #[error("unknown destination: {0}")]
97    UnknownDestination(ProtoName),
98
99    // Session membership and permissions
100    #[error("participant not found in group: {0}")]
101    ParticipantNotFound(ProtoName),
102    #[error("participant already in group: {0}")]
103    ParticipantAlreadyInGroup(ProtoName),
104    #[error("cannot invite participant to point-to-point session")]
105    CannotInviteToP2P,
106    #[error("cannot remove participant from point-to-point session")]
107    CannotRemoveFromP2P,
108    #[error("cannot close a point-to-point session")]
109    CannotCloseP2P,
110    #[error("cannot rejoin a point-to-point session")]
111    CannotRejoinP2P,
112    #[error("only initiator can modify participants")]
113    NotInitiator,
114
115    // Routing and delivery failures
116    #[error("error sending session internal message to session controller")]
117    SessionControllerSendFailed,
118    #[error("error sending new session notification to app")]
119    NewSessionSendFailed,
120    #[error("error sending session delete message to session layer")]
121    SessionDeleteMessageSendFailed,
122    #[error("error sending data message to application")]
123    ApplicationMessageSendFailed,
124    #[error("error sending data message to slim")]
125    SlimMessageSendFailed,
126    #[error("send failure reported from slim: {ctx}")]
127    SlimSendFailure { ctx: Box<ErrorPayload> },
128
129    // Session lifecycle and state (continued)
130    #[error("session is draining - drop message")]
131    SessionDrainingDrop,
132    #[error("session already closed")]
133    SessionAlreadyClosed,
134    #[error("participant is offline")]
135    ParticipantOffLine,
136    #[error("rejoin failed: epoch mismatch")]
137    RejoinFailed,
138    #[error("another status change is already in progress")]
139    StatusChangeInProgress,
140    #[error("session cleanup failed: {details}")]
141    SessionCleanupFailed { details: String },
142    #[error("message send retries exhausted for id={id}")]
143    MessageSendRetryFailed { id: u32 },
144    #[error("message receive retries exhausted for id={id}")]
145    MessageReceiveRetryFailed { id: u32 },
146    #[error("session sender is shutdown, cannot send messages")]
147    SessionSenderShutdown,
148    #[error("session receiver is shutdown, cannot receive messages")]
149    SessionReceiverShutdown,
150    #[error("missing participant name on timer")]
151    MissingParticipantNameOnTimer,
152
153    // Message construction and extraction contexts
154    #[error("missing payload: {context}")]
155    MissingPayload { context: &'static str },
156    #[error("message build failed: {0}")]
157    MessageBuild(MessageError),
158    #[error("message payload extract failed in {context}: {source}")]
159    PayloadExtract {
160        context: &'static str,
161        source: MessageError,
162    },
163
164    // Participant connectivity
165    #[error("missing mls payload in welcome message")]
166    WelcomeMessageMissingMlsPayload,
167    #[error("invalid join request payload")]
168    InvalidJoinRequestPayload,
169    #[error("participant disconnected: {0}")]
170    ParticipantDisconnected(ProtoName),
171    #[error("missing participant name on disconnection event")]
172    MissingParticipantNameOnDisconnection,
173
174    // Moderator task orchestration
175    #[error("no pending requests for the given key: {0}")]
176    TimerNotFound(u32),
177    #[error("phase not supported for task")]
178    ModeratorTaskUnsupportedPhase,
179    #[error("unexpected timer id: {0}")]
180    ModeratorTaskUnexpectedTimerId(u32),
181    #[error("failed to add participant to session: {source}")]
182    ModeratorTaskAddFailed { source: Box<SessionError> },
183    #[error("failed to remove participant from session: {source}")]
184    ModeratorTaskRemoveFailed { source: Box<SessionError> },
185    #[error("failed to update session: {source}")]
186    ModeratorTaskUpdateFailed { source: Box<SessionError> },
187    #[error("failed to close session: {source}")]
188    ModeratorTaskCloseFailed { source: Box<SessionError> },
189}
190
191impl SessionError {
192    // Helper constructors for structured mapping without repeating strings.
193    pub fn build_error(err: MessageError) -> Self {
194        SessionError::MessageBuild(err)
195    }
196    pub fn extract_error(context: &'static str, err: MessageError) -> Self {
197        SessionError::PayloadExtract {
198            context,
199            source: err,
200        }
201    }
202    pub fn cleanup_failed<E: std::fmt::Display>(e: E) -> Self {
203        SessionError::SessionCleanupFailed {
204            details: e.to_string(),
205        }
206    }
207
208    // Helpers to construct new structured retry failure variants
209    pub fn send_retry_failed(id: u32) -> Self {
210        SessionError::MessageSendRetryFailed { id }
211    }
212
213    pub fn receive_retry_failed(id: u32) -> Self {
214        SessionError::MessageReceiveRetryFailed { id }
215    }
216
217    /// Extract session context from SlimSendFailure error
218    /// Returns None if the error is not a SlimSendFailure or if it lacks session context
219    pub fn session_context(&self) -> Option<&MessageContext> {
220        match self {
221            SessionError::SlimSendFailure { ctx } => ctx.session_context.as_ref(),
222            _ => None,
223        }
224    }
225
226    /// Check if this error is for a command message
227    pub fn is_command_message_error(&self) -> bool {
228        self.session_context()
229            .map(|ctx| ctx.get_session_message_type().is_command_message())
230            .unwrap_or(false)
231    }
232}