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