1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
use crate::middleware::OUTGOING_MAILBOX;
use std::fmt::{Debug, Display, Formatter};

#[derive(serde::Deserialize, serde::Serialize, Clone)]
/// A **one-time** user notification.
///
/// Flash messages are made of a [`Level`] and a string of content.  
/// The message level can be used for filtering and rendering - for example:
///
/// - Only show flash messages at `info` level or above in a production environment, while retaining `debug` level messages for local development;
/// - Use different colours, in the UI, to display messages (e.g. red for errors, orange for warnings, etc.);
///
/// You can build a flash message via [`FlashMessage::new`] by specifying its content and [`Level`].
/// You can also use the shorter level-based constructors - e.g. [`FlashMessage::info`].
pub struct FlashMessage {
    content: String,
    level: Level,
}

impl FlashMessage {
    /// Build a [`FlashMessage`] by specifying its content and [`Level`].
    pub fn new(content: String, level: Level) -> Self {
        Self { content, level }
    }

    /// The string content of this flash message.
    pub fn content(&self) -> &str {
        &self.content
    }

    /// The [`Level`] of this flash message.
    pub fn level(&self) -> Level {
        self.level
    }

    /// Build an info-level [`FlashMessage`] by specifying its content.
    pub fn info<S: Into<String>>(content: S) -> Self {
        Self {
            content: content.into(),
            level: Level::Info,
        }
    }

    /// Build a debug-level [`FlashMessage`] by specifying its content.
    pub fn debug<S: Into<String>>(content: S) -> Self {
        Self {
            content: content.into(),
            level: Level::Debug,
        }
    }

    /// Build a success-level [`FlashMessage`] by specifying its content.
    pub fn success<S: Into<String>>(content: S) -> Self {
        Self {
            content: content.into(),
            level: Level::Success,
        }
    }

    /// Build a warning-level [`FlashMessage`] by specifying its content.
    pub fn warning<S: Into<String>>(content: S) -> Self {
        Self {
            content: content.into(),
            level: Level::Warning,
        }
    }

    /// Build an error-level [`FlashMessage`] by specifying its content.
    pub fn error<S: Into<String>>(content: S) -> Self {
        Self {
            content: content.into(),
            level: Level::Error,
        }
    }

    /// Attach this [`FlashMessage`] to the outgoing request.
    ///
    /// The message will be dropped if its [`Level`] is below the minimum level
    /// specified when configuring [`FlashMessagesFramework`] via [`FlashMessagesFrameworkBuilder::minimum_level`].
    ///
    /// This method will **panic** if [`FlashMessagesFramework`] has not been registered as a middleware.
    ///
    /// [`FlashMessagesFramework`]: crate::FlashMessagesFramework
    /// [`FlashMessagesFrameworkBuilder::minimum_level`]: crate::FlashMessagesFrameworkBuilder::minimum_level
    pub fn send(self) {
        let result = OUTGOING_MAILBOX.try_with(|mailbox| {
            if self.level as u8 >= mailbox.minimum_level as u8 {
                mailbox.messages.borrow_mut().push(self);
            }
        });

        if result.is_err() {
            panic!("Failed to send flash message!\n\
                To use `FlashMessages::send` you need to add `FlashMessageFramework` as a middleware \
                on your `actix-web` application using `wrap`. Check out `actix-web-flash-messages`'s documentation for more details.")
        }
    }
}

#[derive(serde::Deserialize, serde::Serialize, Clone, Copy)]
/// The severity level of a [`FlashMessage`].
///
/// Levels can be used for filtering and rendering - for example:
///
/// - Only show flash messages at `info` level or above in a production environment, while retaining `debug` level messages for local development;
/// - Use different colours, in the UI, to display messages (e.g. red for errors, orange for warnings, etc.).
pub enum Level {
    /// Development-related messages. Often ignored in a production environment.
    Debug = 0,
    /// Informational messages for the user - e.g. "Your last login was two days ago".
    Info = 1,
    /// Positive feedback after an action was successful - e.g. "You logged in successfully!".
    Success = 2,
    /// Notifying the user about an action that they must take imminently to prevent an error in the future.
    Warning = 3,
    /// An action was **not** successful - e.g. "The provided login credentials are invalid".
    Error = 4,
}

impl Debug for Level {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", level_to_str(self))
    }
}

impl Display for Level {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", level_to_str(self))
    }
}

fn level_to_str(l: &Level) -> &'static str {
    match l {
        Level::Debug => "debug",
        Level::Info => "info",
        Level::Success => "success",
        Level::Warning => "warning",
        Level::Error => "error",
    }
}