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
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
use std::cmp::Ordering;
use std::fmt::Debug;
use message::formatted_detail::FormattedMessageDetail;
use serde::{Serialize, Deserialize};
use crate::message;
use crate::message::author::Author;
use crate::message::component::Component;

pub mod formatted_detail;
pub mod author;
pub mod component;
pub mod builder;
pub mod detail_builder;

/// A Message represents a [`MessageDestination`] independent way to send a message to a platform.
/// **However** - not every destination will support every type of formatting, or may have length / size
/// restrictions - so be aware of this. Destinations should do their best to receive the message, even if they
/// have to ignore formatting to do so.
///
/// To construct a Message, consider using [`MessageBuilder`] or [`MessageDetailBuilder`] to make
/// your life easier.
///
/// # Parts of a Message #
/// - [Level] - Severity of message
/// - Title - Optional, short snappy summary of what the message is about
/// - [MessageDetail] - Structured body text supporting formatting
/// - [Component] - Optional, indicating what the message is about, e.g a program or server.
/// - [Author] - Who created the message
/// - Timestamp - The unix timestamp in milliseconds, showing when the message was sent.
///
/// [`MessageDestination`]: crate::destination::MessageDestination
/// [`MessageBuilder`]: builder::MessageBuilder
/// [`MessageDetailBuilder`]: detail_builder::MessageDetailBuilder
#[derive(Debug, Clone, PartialEq)]
pub struct Message {
    level: Level,
    title: Option<String>,
    message_detail: MessageDetail,
    component: Option<Component>,
    author: Author,
    unix_timestamp_millis: i64,
}

impl Message {
    pub fn new(level: Level, title: Option<String>,
               message_detail: MessageDetail, component: Option<Component>,
               author: Author, unix_timestamp_millis: i64,
    ) -> Self {
        Self {
            level,
            title,
            message_detail,
            component,
            author,
            unix_timestamp_millis
        }
    }

    pub fn get_level(&self) -> &Level {
        &self.level
    }

    pub fn get_title(&self) -> &Option<String> {
        &self.title
    }

    pub fn get_message_detail(&self) -> &MessageDetail {
        &self.message_detail
    }

    pub fn get_unix_timestamp_millis(&self) -> i64 {
        self.unix_timestamp_millis
    }

    pub fn get_author(&self) -> &Author {
        &self.author
    }

    pub fn get_component(&self) -> &Option<Component> {
        &self.component
    }
}

#[derive(Debug, Clone, PartialEq)]
pub enum MessageDetail {
    Raw(String),
    Formatted(FormattedMessageDetail),
}

impl MessageDetail {
    pub fn raw(&self) -> &str {
        match &self {
            MessageDetail::Raw(raw) => raw,
            MessageDetail::Formatted(formatted) => formatted.raw()
        }
    }

    pub fn has_formatting(&self) -> bool {
        matches!(&self, MessageDetail::Formatted(_))
    }
}

impl Default for MessageDetail {
    fn default() -> Self {
        Self::Raw(String::new())
    }
}

/// The level / severity of the [Message]. This can be thought of as the log level.
/// This is used in conjunction to [Component] to indicate how a message should be
/// routed.
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[cfg_attr(feature = "binary", derive(clap::ArgEnum))]
pub enum Level {
    /// Indicates an informational message when everything is working properly.
    /// # Examples #
    /// - A job has completed successfully, e.g a backup
    /// - A daily status update to confirm that everything is running correctly.
    Info,
    /// Used when something unexpected occurs that could be the source of an error,
    /// but requires the user to check whether it is actually an issue.
    ///
    /// # Examples #
    /// - A job took longer than normal
    /// - A job failed that fails fairly regularly, but will presumably sort itself out soon.
    Warn,
    /// Indicates a failure has occurred.
    ///
    /// # Examples #
    /// - A program crashed / had a non-zero exit code
    /// - A monitoring program detected that another program has not completed its job
    ///     - Possibly indicating the program is not running
    ///     - Or the program is malfunctioning
    /// - A program restarted in attempt to recover itself, but is expected to recover safely
    Error,
    /// Indicates a failure in the notifications own configuration / workings.
    ///
    /// # Examples #
    /// - Sent by [`MessageRouter`] to [`Root`] level [`MessageDestination`]s when a message
    /// cannot be sent to one or more destinations (e.g. network failure, invalid tokens)
    ///
    /// [`Root`]: crate::destination::routed_destination::MessageRoutingBehaviour::Root
    /// [`MessageDestination`]: crate::destination::MessageDestination
    /// [`MessageRouter`]: crate::message_router::MessageRouter
    SelfError,
}

impl Default for Level {
    fn default() -> Self {
        Self::Info
    }
}

impl Level {
    pub(crate) fn get_priority(&self) -> u32 {
        match &self {
            Self::Info => 1,
            Self::Warn => 3,
            Self::Error => 4,
            Self::SelfError => 5,
        }
    }

    /// Gets the least severe [Level]
    pub fn min() -> Level {
        Level::Info
    }

    /// Gets the most severe [Level]
    pub fn max() -> Level {
        Level::SelfError
    }
}

impl PartialOrd<Self> for Level {
    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
        Some(self.cmp(other))
    }
}

impl Ord for Level {
    fn cmp(&self, other: &Self) -> Ordering {
        self.get_priority().cmp(&other.get_priority())
    }
}