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
//! Common data shared among input/output/services and utilities related to it.

use std::collections::HashMap;

/// Common data shared among input/output/services.
/// This is the language `service-io` talk.
/// Each input/output/service understand this structure.
///
/// # Example
/// ```rust
/// use service_io::message::Message;
///
/// let request = Message::default()
///     .user("user_01")
///     .service_name("my_service")
///     .args(["arg0", "arg1", "arg2", "this is arg3"])
///     .body("body of the message")
///     .attach([
///         ("file1.txt", b"content data".to_vec()),
///         ("file2.txt", b"1234".to_vec())
///     ]);
/// ```
///
#[derive(Default, Debug, Clone, PartialEq)]
pub struct Message {
    /// The user this message is related to.
    /// If the message is in the input side, this user means the originator of the message.
    /// If the message is in the output side, this user means the recipient of the message.
    pub user: String,

    /// The service name this message going to/come from.
    /// The value of this field should match to any name used for register services.
    ///
    /// See also: [`Engine::add_service()`]
    ///
    /// [`Engine::add_service()`]: crate::engine::Engine::add_service()
    pub service_name: String,

    /// Arguments of the message.
    /// Each service implementation will understand these values in their own way.
    pub args: Vec<String>,

    /// Main body of the message.
    /// Each service implementation will understand this value in their own way.
    pub body: String,

    /// Attached content of the message.
    /// Each service implementation will understand these values in their own way.
    pub attached_data: HashMap<String, Vec<u8>>,
}

impl Message {
    /// Sugar to perform a response of a received message.
    /// Creates an empty message with same [`Message::user`]
    /// and [`Message::service_name`] as the passed message.
    ///
    /// # Example
    /// ```rust
    /// use service_io::message::Message;
    ///
    /// let request = Message::default()
    ///     .user("user_01")
    ///     .service_name("my_service")
    ///     .body("1234");
    ///
    /// let response = Message::response(&request);
    ///
    /// // Name and service_name are copied
    /// assert_eq!(request.user, response.user);
    /// assert_eq!(request.service_name, response.service_name);
    ///
    /// // But other fields as body are not copied.
    /// assert_ne!(request.body, response.body);
    /// ```
    pub fn response(message: &Message) -> Message {
        Message {
            user: message.user.clone(),
            service_name: message.service_name.clone(),
            ..Default::default()
        }
    }

    /// Set a user for the message
    pub fn user(mut self, user: impl Into<String>) -> Self {
        self.user = user.into();
        self
    }

    /// Set a service name for the message
    pub fn service_name(mut self, service_name: impl Into<String>) -> Self {
        self.service_name = service_name.into();
        self
    }

    /// Set args for the message
    pub fn args<S: Into<String>>(mut self, args: impl IntoIterator<Item = S>) -> Self {
        self.args = args.into_iter().map(|s| s.into()).collect();
        self
    }

    /// Set a body for the message
    pub fn body(mut self, body: impl Into<String>) -> Self {
        self.body = body.into();
        self
    }

    /// Set attached data for the message
    pub fn attach<S: Into<String>>(
        mut self,
        attached: impl IntoIterator<Item = (S, Vec<u8>)>,
    ) -> Self {
        self.attached_data = attached
            .into_iter()
            .map(|(name, data)| (name.into(), data))
            .collect();
        self
    }
}

/// Utilities related to the `Message`
pub mod util {
    use super::Message;

    /// Modify the [`Message::service_name`] value to make the first letter lowercase.
    ///
    /// This utility can be used in [`Engine::map_input()`] to send always
    /// a first letter lowercase version of the service_name to the engine to make the correct
    /// service match.
    ///
    /// This is useful because some users could specify a first capital letter without realizing
    /// (usually in email clients where the first letter is uppercase by default).
    /// If the service_name is registered with lowercase, their message will not match.
    ///
    /// [`Engine::map_input()`]: crate::engine::Engine::map_input()
    pub fn service_name_first_char_to_lowercase(mut message: Message) -> Message {
        let mut chars = message.service_name.chars();
        message.service_name = match chars.next() {
            Some(first_letter) => first_letter.to_lowercase().collect::<String>() + chars.as_str(),
            None => String::new(),
        };
        message
    }
}