Skip to main content

affinidi_messaging_mediator_common/
errors.rs

1use crate::types::{
2    messages::GenericDataStruct,
3    problem_report::{ProblemReport, ProblemReportScope, ProblemReportSorter},
4};
5use axum::{
6    Json,
7    http::StatusCode,
8    response::{IntoResponse, Response},
9};
10use rand::{RngExt, distr::Alphanumeric};
11use serde::{Deserialize, Serialize};
12use std::fmt;
13use thiserror::Error;
14use tracing::{Level, event};
15
16/// Session ID is a random string of 12 characters
17type SessId = String;
18
19/// Error Code (unique code for each error)
20type ErrorCode = u16;
21
22/// Structured context carried alongside an error for logging and client responses.
23///
24/// Attach this to an [`AppError`] via [`AppError::with_context`] so that
25/// `request_id`, `session_id`, and `did_hash` appear automatically in log
26/// output. Only `request_id` is serialized to the client in [`ErrorResponse`].
27#[derive(Debug, Default, Clone, Serialize)]
28pub struct ErrorContext {
29    #[serde(skip_serializing_if = "Option::is_none")]
30    pub request_id: Option<String>,
31    #[serde(skip_serializing_if = "Option::is_none")]
32    pub session_id: Option<String>,
33    #[serde(skip_serializing_if = "Option::is_none")]
34    pub did_hash: Option<String>,
35}
36
37/// Wrapper that converts a [`MediatorError`] into an Axum HTTP response.
38pub struct AppError {
39    error: MediatorError,
40    context: ErrorContext,
41}
42
43impl AppError {
44    /// Attach structured context (request_id, session_id, did_hash) to this error.
45    ///
46    /// The context fields will be included in log output and — where appropriate —
47    /// in the JSON response sent to the client.
48    pub fn with_context(mut self, ctx: ErrorContext) -> Self {
49        self.context = ctx;
50        self
51    }
52}
53
54impl<E> From<E> for AppError
55where
56    E: Into<MediatorError>,
57{
58    fn from(err: E) -> Self {
59        Self {
60            error: err.into(),
61            context: ErrorContext::default(),
62        }
63    }
64}
65
66/// Errors relating to the Mediator Processors. These are largely internal to the Mediator errors
67/// They should not be exposed to the user
68#[derive(Clone, Error, Debug)]
69pub enum ProcessorError {
70    /// A general-purpose internal error in a background processor.
71    #[error("CommonError: {0}")]
72    CommonError(String),
73    /// An error that occurred while forwarding a message to a remote mediator.
74    #[error("ForwardingError: {0}")]
75    ForwardingError(String),
76    /// An error during the periodic cleanup of expired messages.
77    #[error("MessageExpiryCleanupError: {0}")]
78    MessageExpiryCleanupError(String),
79}
80
81/// MediatorError the first String is always the session_id
82#[derive(Error, Debug)]
83#[non_exhaustive]
84pub enum MediatorError {
85    #[error("Internal error handling failure: {2}")]
86    ErrorHandlingError(ErrorCode, SessId, String),
87    #[error("{2}")]
88    InternalError(ErrorCode, SessId, String),
89    #[error("Couldn't parse ({2}). Reason: {3}")]
90    ParseError(ErrorCode, SessId, String, String),
91    #[error("Permission Error: {2}")]
92    PermissionError(ErrorCode, SessId, String),
93    #[error("Request is invalid: {2}")]
94    RequestDataError(ErrorCode, SessId, String),
95    #[error("Service Limit exceeded: {2}")]
96    ServiceLimitError(ErrorCode, SessId, String),
97    #[error("Unauthorized: {2}")]
98    Unauthorized(ErrorCode, SessId, String),
99    #[error("DID error: did({2}) Reason: {3}")]
100    DIDError(ErrorCode, SessId, String, String),
101    #[error("Configuration Error: {2}")]
102    ConfigError(ErrorCode, SessId, String),
103    #[error("Database Error: {2}")]
104    DatabaseError(ErrorCode, SessId, String),
105    #[error("Failed to unpack message: {2}")]
106    MessageUnpackError(ErrorCode, SessId, String),
107    #[error("MessageExpired: expiry({2}) now({3})")]
108    MessageExpired(ErrorCode, SessId, String, String),
109    #[error("Failed to pack message: {2}")]
110    MessagePackError(ErrorCode, SessId, String),
111    #[error("Feature not implemented: {2}")]
112    NotImplemented(ErrorCode, SessId, String),
113    #[error("Authorization Session ({1}) error: {2}")]
114    SessionError(ErrorCode, SessId, String),
115    #[error("Anonymous message error: {2}")]
116    AnonymousMessageError(ErrorCode, SessId, String),
117    #[error("Forwarding/Routing message error: {2}")]
118    ForwardMessageError(ErrorCode, SessId, String),
119    #[error("Authentication error: {1}")]
120    AuthenticationError(ErrorCode, String),
121    #[error("ACL denied: {1}")]
122    ACLDenied(ErrorCode, String),
123    #[error("Processor ({1}) error: {2}")]
124    ProcessorError(ErrorCode, ProcessorError, String),
125
126    /// This is a catch-all for any error that is using DIDComm Problem Reports
127    /// `ErrorCode` - Unique Error code
128    /// `SessId` - Session ID
129    /// `Option<String>` - MSG ID responding to
130    /// `ProblemReport` - DIDComm Problem Report
131    /// `u16` - HTTP status code
132    /// `String` - Log message
133    #[error("Mediator Error: code({3}): {4}")]
134    MediatorError(
135        ErrorCode,
136        SessId,
137        Option<String>,
138        Box<ProblemReport>,
139        u16,
140        String,
141    ),
142}
143
144impl MediatorError {
145    /// Creates a `MediatorError::MediatorError` with a DIDComm Problem Report.
146    ///
147    /// The `comment` is also used as the log message. For cases where the log
148    /// message should differ (e.g., when comment has `{1}` placeholders but the
149    /// log should have interpolated values), use [`problem_with_log`](Self::problem_with_log).
150    #[allow(clippy::too_many_arguments)]
151    pub fn problem(
152        code: u16,
153        session_id: impl Into<String>,
154        msg_id: Option<String>,
155        sorter: ProblemReportSorter,
156        scope: ProblemReportScope,
157        descriptor: &str,
158        comment: &str,
159        args: Vec<String>,
160        http_status: StatusCode,
161    ) -> Self {
162        Self::MediatorError(
163            code,
164            session_id.into(),
165            msg_id,
166            Box::new(ProblemReport::new(
167                sorter,
168                scope,
169                descriptor.into(),
170                comment.into(),
171                args,
172                None,
173            )),
174            http_status.as_u16(),
175            comment.to_string(),
176        )
177    }
178
179    /// Like [`problem`](Self::problem) but with a separate log message.
180    ///
181    /// Use this when the Problem Report comment has `{1}`, `{2}` placeholders
182    /// but the log message should contain the actual interpolated values.
183    #[allow(clippy::too_many_arguments)]
184    pub fn problem_with_log(
185        code: u16,
186        session_id: impl Into<String>,
187        msg_id: Option<String>,
188        sorter: ProblemReportSorter,
189        scope: ProblemReportScope,
190        descriptor: &str,
191        comment: &str,
192        args: Vec<String>,
193        http_status: StatusCode,
194        log_msg: impl Into<String>,
195    ) -> Self {
196        Self::MediatorError(
197            code,
198            session_id.into(),
199            msg_id,
200            Box::new(ProblemReport::new(
201                sorter,
202                scope,
203                descriptor.into(),
204                comment.into(),
205                args,
206                None,
207            )),
208            http_status.as_u16(),
209            log_msg.into(),
210        )
211    }
212}
213
214impl From<MediatorError> for ProcessorError {
215    fn from(error: MediatorError) -> Self {
216        ProcessorError::CommonError(error.to_string())
217    }
218}
219
220impl From<ProcessorError> for MediatorError {
221    fn from(error: ProcessorError) -> Self {
222        MediatorError::ProcessorError(0, error.clone(), error.to_string())
223    }
224}
225
226impl IntoResponse for AppError {
227    fn into_response(self) -> Response {
228        let ctx = &self.context;
229
230        // Build a context suffix for log lines. Contains whichever of
231        // request_id / session_id / did_hash are present.
232        let ctx_log: String = {
233            let mut parts = Vec::new();
234            if let Some(ref rid) = ctx.request_id {
235                parts.push(format!("request_id={rid}"));
236            }
237            if let Some(ref sid) = ctx.session_id {
238                parts.push(format!("session_id={sid}"));
239            }
240            if let Some(ref dh) = ctx.did_hash {
241                parts.push(format!("did_hash={dh}"));
242            }
243            if parts.is_empty() {
244                String::new()
245            } else {
246                format!(" [{}]", parts.join(" "))
247            }
248        };
249
250        let request_id = ctx.request_id.clone();
251
252        let response = match self.error {
253            MediatorError::ErrorHandlingError(error_code, session_id, msg) => {
254                event!(
255                    Level::WARN,
256                    "{}: ErrorHandlingError({}): {}{}",
257                    session_id,
258                    error_code,
259                    msg,
260                    ctx_log
261                );
262                ErrorResponse {
263                    http_code: StatusCode::INTERNAL_SERVER_ERROR.as_u16(),
264                    session_id,
265                    request_id,
266                    error_code,
267                    error_code_str: "ErrorHandlingError".to_string(),
268                    message: "Internal server error".to_string(),
269                }
270            }
271            MediatorError::InternalError(error_code, session_id, msg) => {
272                event!(
273                    Level::WARN,
274                    "{}: InternalError({}): {}{}",
275                    session_id,
276                    error_code,
277                    msg,
278                    ctx_log
279                );
280                ErrorResponse {
281                    http_code: StatusCode::INTERNAL_SERVER_ERROR.as_u16(),
282                    session_id,
283                    request_id,
284                    error_code,
285                    error_code_str: "InternalError".to_string(),
286                    message: "Internal server error".to_string(),
287                }
288            }
289            MediatorError::ParseError(error_code, session_id, what, msg) => {
290                event!(
291                    Level::WARN,
292                    "{}: ParseError({}): couldn't parse ({}). Reason: {}{}",
293                    session_id,
294                    error_code,
295                    what,
296                    msg,
297                    ctx_log
298                );
299                ErrorResponse {
300                    http_code: StatusCode::BAD_REQUEST.as_u16(),
301                    session_id,
302                    request_id,
303                    error_code,
304                    error_code_str: "BadRequest: ParseError".to_string(),
305                    message: format!("Couldn't parse ({what})"),
306                }
307            }
308            MediatorError::PermissionError(error_code, session_id, msg) => {
309                let response = ErrorResponse {
310                    http_code: StatusCode::FORBIDDEN.as_u16(),
311                    session_id: session_id.to_string(),
312                    request_id,
313                    error_code,
314                    error_code_str: "Forbidden: PermissionError".to_string(),
315                    message: msg.to_string(),
316                };
317                event!(Level::WARN, "{}{}", response.to_string(), ctx_log);
318                response
319            }
320            MediatorError::RequestDataError(error_code, session_id, msg) => {
321                let response = ErrorResponse {
322                    http_code: StatusCode::BAD_REQUEST.as_u16(),
323                    session_id: session_id.to_string(),
324                    request_id,
325                    error_code,
326                    error_code_str: "BadRequest: RequestDataError".to_string(),
327                    message: format!("Bad Request: ({msg})"),
328                };
329                event!(Level::WARN, "{}{}", response.to_string(), ctx_log);
330                response
331            }
332            MediatorError::ServiceLimitError(error_code, session_id, msg) => {
333                let response = ErrorResponse {
334                    http_code: StatusCode::BAD_REQUEST.as_u16(),
335                    session_id: session_id.to_string(),
336                    request_id,
337                    error_code,
338                    error_code_str: "BadRequest: ServiceLimitError".to_string(),
339                    message: msg.to_string(),
340                };
341                event!(Level::WARN, "{}{}", response.to_string(), ctx_log);
342                response
343            }
344            MediatorError::Unauthorized(error_code, session_id, msg) => {
345                let response = ErrorResponse {
346                    http_code: StatusCode::UNAUTHORIZED.as_u16(),
347                    session_id: session_id.to_string(),
348                    request_id,
349                    error_code,
350                    error_code_str: "Unauthorized".to_string(),
351                    message: "Unauthorized access".to_string(),
352                };
353                event!(
354                    Level::WARN,
355                    "{}: Unauthorized({}): {}{}",
356                    session_id,
357                    error_code,
358                    msg,
359                    ctx_log
360                );
361                response
362            }
363            MediatorError::DIDError(error_code, session_id, did, msg) => {
364                event!(
365                    Level::WARN,
366                    "{}: DIDError({}): did({}) Error: {}{}",
367                    session_id,
368                    error_code,
369                    did,
370                    msg,
371                    ctx_log
372                );
373                ErrorResponse {
374                    http_code: StatusCode::BAD_REQUEST.as_u16(),
375                    session_id,
376                    request_id,
377                    error_code,
378                    error_code_str: "DIDError".to_string(),
379                    message: format!("did({did}) Error: invalid or unresolvable DID"),
380                }
381            }
382            MediatorError::ConfigError(error_code, session_id, message) => {
383                event!(
384                    Level::WARN,
385                    "{}: ConfigError({}): {}{}",
386                    session_id,
387                    error_code,
388                    message,
389                    ctx_log
390                );
391                ErrorResponse {
392                    http_code: StatusCode::SERVICE_UNAVAILABLE.as_u16(),
393                    session_id,
394                    request_id,
395                    error_code,
396                    error_code_str: "ConfigError".to_string(),
397                    message: "Service configuration error".to_string(),
398                }
399            }
400            MediatorError::DatabaseError(error_code, session_id, message) => {
401                event!(
402                    Level::WARN,
403                    "{}: DatabaseError({}): {}{}",
404                    session_id,
405                    error_code,
406                    message,
407                    ctx_log
408                );
409                ErrorResponse {
410                    http_code: StatusCode::SERVICE_UNAVAILABLE.as_u16(),
411                    session_id,
412                    request_id,
413                    error_code,
414                    error_code_str: "DatabaseError".to_string(),
415                    message: "Service temporarily unavailable".to_string(),
416                }
417            }
418            MediatorError::MessageUnpackError(error_code, session_id, message) => {
419                event!(
420                    Level::WARN,
421                    "{}: MessageUnpackError({}): {}{}",
422                    session_id,
423                    error_code,
424                    message,
425                    ctx_log
426                );
427                ErrorResponse {
428                    http_code: StatusCode::BAD_REQUEST.as_u16(),
429                    session_id,
430                    request_id,
431                    error_code,
432                    error_code_str: "MessageUnpackError".to_string(),
433                    message: "Failed to unpack message".to_string(),
434                }
435            }
436            MediatorError::MessageExpired(error_code, session_id, expired, now) => {
437                let response = ErrorResponse {
438                    http_code: StatusCode::UNPROCESSABLE_ENTITY.as_u16(),
439                    session_id: session_id.to_string(),
440                    request_id,
441                    error_code,
442                    error_code_str: "MessageExpired".to_string(),
443                    message: format!("Message expired: expiry({expired}) now({now})"),
444                };
445                event!(Level::WARN, "{}{}", response.to_string(), ctx_log);
446                response
447            }
448            MediatorError::MessagePackError(error_code, session_id, message) => {
449                event!(
450                    Level::WARN,
451                    "{}: MessagePackError({}): {}{}",
452                    session_id,
453                    error_code,
454                    message,
455                    ctx_log
456                );
457                ErrorResponse {
458                    http_code: StatusCode::BAD_REQUEST.as_u16(),
459                    session_id,
460                    request_id,
461                    error_code,
462                    error_code_str: "MessagePackError".to_string(),
463                    message: "Failed to pack message".to_string(),
464                }
465            }
466            MediatorError::NotImplemented(error_code, session_id, message) => {
467                let response = ErrorResponse {
468                    http_code: StatusCode::NOT_IMPLEMENTED.as_u16(),
469                    session_id: session_id.to_string(),
470                    request_id,
471                    error_code,
472                    error_code_str: "NotImplemented".to_string(),
473                    message,
474                };
475                event!(Level::WARN, "{}{}", response.to_string(), ctx_log);
476                response
477            }
478            MediatorError::SessionError(error_code, session_id, message) => {
479                let response = ErrorResponse {
480                    http_code: StatusCode::NOT_ACCEPTABLE.as_u16(),
481                    session_id: session_id.to_string(),
482                    request_id,
483                    error_code,
484                    error_code_str: "SessionError".to_string(),
485                    message,
486                };
487                event!(Level::WARN, "{}{}", response.to_string(), ctx_log);
488                response
489            }
490            MediatorError::AnonymousMessageError(error_code, session_id, message) => {
491                let response = ErrorResponse {
492                    http_code: StatusCode::NOT_ACCEPTABLE.as_u16(),
493                    session_id: session_id.to_string(),
494                    request_id,
495                    error_code,
496                    error_code_str: "AnonymousMessageError".to_string(),
497                    message,
498                };
499                event!(Level::WARN, "{}{}", response.to_string(), ctx_log);
500                response
501            }
502            MediatorError::ForwardMessageError(error_code, session_id, message) => {
503                let response = ErrorResponse {
504                    http_code: StatusCode::NOT_ACCEPTABLE.as_u16(),
505                    session_id: session_id.to_string(),
506                    request_id,
507                    error_code,
508                    error_code_str: "ForwardMessageError".to_string(),
509                    message,
510                };
511                event!(Level::WARN, "{}{}", response.to_string(), ctx_log);
512                response
513            }
514            MediatorError::AuthenticationError(error_code, message) => {
515                event!(
516                    Level::WARN,
517                    "AuthenticationError({}): {}{}",
518                    error_code,
519                    message,
520                    ctx_log
521                );
522                ErrorResponse {
523                    http_code: StatusCode::UNAUTHORIZED.as_u16(),
524                    session_id: "NO-SESSION".to_string(),
525                    request_id,
526                    error_code,
527                    error_code_str: "AuthenticationError".to_string(),
528                    message: "Authentication failed".to_string(),
529                }
530            }
531            MediatorError::ACLDenied(error_code, message) => {
532                let response = ErrorResponse {
533                    http_code: StatusCode::UNAUTHORIZED.as_u16(),
534                    session_id: "NO-SESSION".to_string(),
535                    request_id,
536                    error_code,
537                    error_code_str: "ACLDenied".to_string(),
538                    message,
539                };
540                event!(Level::WARN, "{}{}", response.to_string(), ctx_log);
541                response
542            }
543            MediatorError::ProcessorError(error_code, processor, message) => {
544                event!(
545                    Level::WARN,
546                    "ProcessorError({}): Processor ({}): {}{}",
547                    error_code,
548                    processor,
549                    message,
550                    ctx_log
551                );
552                ErrorResponse {
553                    http_code: StatusCode::INTERNAL_SERVER_ERROR.as_u16(),
554                    session_id: "NO-SESSION".to_string(),
555                    request_id,
556                    error_code,
557                    error_code_str: "ProcessorError".to_string(),
558                    message: "Internal server error".to_string(),
559                }
560            }
561            MediatorError::MediatorError(
562                error_code,
563                session_id,
564                _,
565                problem_report,
566                http_code,
567                log_text,
568            ) => {
569                event!(Level::WARN, "{}{}", log_text, ctx_log);
570                ErrorResponse {
571                    http_code,
572                    session_id,
573                    request_id,
574                    error_code,
575                    error_code_str: "DIDCommProblemReport".to_string(),
576                    message: serde_json::to_string(&problem_report)
577                        .unwrap_or_else(|_| "Failed to serialize Problem Report".to_string()),
578                }
579            }
580        };
581        (
582            StatusCode::from_u16(response.http_code).unwrap_or(StatusCode::INTERNAL_SERVER_ERROR),
583            Json(response),
584        )
585            .into_response()
586    }
587}
588
589/// JSON error response body returned by the mediator's HTTP API.
590#[derive(Serialize, Debug)]
591#[serde(rename_all = "camelCase")]
592pub struct ErrorResponse {
593    pub session_id: String,
594    #[serde(skip_serializing_if = "Option::is_none")]
595    pub request_id: Option<String>,
596    pub http_code: u16,
597    pub error_code: u16,
598    pub error_code_str: String,
599    pub message: String,
600}
601
602impl fmt::Display for ErrorResponse {
603    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
604        write!(
605            f,
606            "{}: httpcode({}) errorCode({}), errorCodeStr({}) message({})",
607            self.session_id, self.http_code, self.error_code, self.error_code_str, self.message,
608        )?;
609        if let Some(ref rid) = self.request_id {
610            write!(f, " request_id({rid})")?;
611        }
612        Ok(())
613    }
614}
615/// JSON success response body returned by the mediator's HTTP API.
616#[derive(Serialize, Deserialize, Debug)]
617#[serde(rename_all = "camelCase")]
618pub struct SuccessResponse<T: GenericDataStruct> {
619    pub session_id: String,
620    pub http_code: u16,
621    pub error_code: i32,
622    pub error_code_str: String,
623    pub message: String,
624    #[serde(bound(deserialize = ""))]
625    pub data: Option<T>,
626}
627
628impl<T: GenericDataStruct> fmt::Display for SuccessResponse<T> {
629    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
630        write!(
631            f,
632            "{}: httpcode({}) errorCode({}), errorCodeStr({}) message({})",
633            self.session_id, self.http_code, self.error_code, self.error_code_str, self.message,
634        )
635    }
636}
637
638impl<T: GenericDataStruct> SuccessResponse<T> {
639    pub fn response(
640        session_id: &str,
641        http_code: StatusCode,
642        msg: &str,
643        data: Option<T>,
644    ) -> Json<SuccessResponse<T>> {
645        let response = SuccessResponse {
646            session_id: session_id.to_string(),
647            http_code: http_code.as_u16(),
648            error_code: 0,
649            error_code_str: "Ok".to_string(),
650            message: msg.to_string(),
651            data,
652        };
653        event!(Level::INFO, "{response}");
654        Json(response)
655    }
656}
657
658/// Creates a random 12-character alphanumeric session identifier.
659pub fn create_session_id() -> String {
660    rand::rng()
661        .sample_iter(&Alphanumeric)
662        .take(12)
663        .map(char::from)
664        .collect()
665}
666
667#[cfg(test)]
668mod tests {
669    use super::*;
670    use crate::types::problem_report::{ProblemReportScope, ProblemReportSorter};
671    use axum::http::StatusCode;
672
673    #[test]
674    fn test_problem_creates_mediator_error() {
675        let err = MediatorError::problem(
676            44,
677            "test-session",
678            None,
679            ProblemReportSorter::Error,
680            ProblemReportScope::Protocol,
681            "authorization.send",
682            "DID isn't allowed to send",
683            vec![],
684            StatusCode::FORBIDDEN,
685        );
686        match err {
687            MediatorError::MediatorError(code, session, msg_id, report, http, log) => {
688                assert_eq!(code, 44);
689                assert_eq!(session, "test-session");
690                assert!(msg_id.is_none());
691                assert_eq!(report.code, "e.p.authorization.send");
692                assert_eq!(report.comment, "DID isn't allowed to send");
693                assert_eq!(http, 403);
694                assert_eq!(log, "DID isn't allowed to send");
695            }
696            _ => panic!("Expected MediatorError::MediatorError variant"),
697        }
698    }
699
700    #[test]
701    fn test_problem_with_log_separate_messages() {
702        let err = MediatorError::problem_with_log(
703            19,
704            "sess-123",
705            Some("msg-456".to_string()),
706            ProblemReportSorter::Warning,
707            ProblemReportScope::Message,
708            "message.serialize",
709            "Couldn't serialize: {1}",
710            vec!["parse error".to_string()],
711            StatusCode::BAD_REQUEST,
712            "Couldn't serialize message",
713        );
714        match err {
715            MediatorError::MediatorError(code, session, msg_id, report, http, log) => {
716                assert_eq!(code, 19);
717                assert_eq!(session, "sess-123");
718                assert_eq!(msg_id, Some("msg-456".to_string()));
719                assert_eq!(report.code, "w.m.message.serialize");
720                assert_eq!(report.comment, "Couldn't serialize: {1}");
721                assert_eq!(report.args, vec!["parse error"]);
722                assert_eq!(http, 400);
723                assert_eq!(log, "Couldn't serialize message");
724            }
725            _ => panic!("Expected MediatorError::MediatorError variant"),
726        }
727    }
728
729    #[test]
730    fn test_problem_with_args() {
731        let err = MediatorError::problem(
732            63,
733            "s1",
734            Some("m1".to_string()),
735            ProblemReportSorter::Warning,
736            ProblemReportScope::Message,
737            "protocol.forwarding.attachments.too_many",
738            "Too many attachments ({1}). Limit ({2})",
739            vec!["5".to_string(), "1".to_string()],
740            StatusCode::BAD_REQUEST,
741        );
742        match err {
743            MediatorError::MediatorError(_, _, _, report, _, _) => {
744                assert_eq!(report.args.len(), 2);
745                assert_eq!(report.args[0], "5");
746                assert_eq!(report.args[1], "1");
747            }
748            _ => panic!("Expected MediatorError::MediatorError variant"),
749        }
750    }
751
752    #[test]
753    fn test_create_session_id_length() {
754        let id = create_session_id();
755        assert_eq!(id.len(), 12);
756        assert!(id.chars().all(|c| c.is_ascii_alphanumeric()));
757    }
758
759    #[test]
760    fn test_create_session_id_uniqueness() {
761        let id1 = create_session_id();
762        let id2 = create_session_id();
763        assert_ne!(id1, id2, "Two session IDs should not be identical");
764    }
765}