Skip to main content

zulip_api/message/send_message/
request.rs

1use std::borrow::Cow;
2
3use serde::{Deserialize, Serialize};
4
5use crate::message::{MessageTo, MessageType};
6use crate::uuid::Uuid;
7use crate::{field_access, impl_default, impl_display};
8
9/// Represents a message to send to a Zulip server.
10#[derive(Clone, Debug, Eq, PartialEq, Deserialize, Serialize)]
11pub struct Request {
12    #[serde(rename = "type")]
13    msg_type: MessageType,
14    to: MessageTo,
15    content: String,
16    #[serde(skip_serializing_if = "Option::is_none")]
17    topic: Option<String>,
18    #[serde(skip_serializing_if = "Option::is_none")]
19    queue_id: Option<Uuid>,
20    #[serde(skip_serializing_if = "Option::is_none")]
21    local_id: Option<String>,
22    #[serde(skip_serializing_if = "Option::is_none")]
23    read_by_sender: Option<bool>,
24}
25
26impl Request {
27    /// Creates a new [Request].
28    pub const fn new() -> Self {
29        Self {
30            msg_type: MessageType::new(),
31            to: MessageTo::new(),
32            content: String::new(),
33            topic: None,
34            queue_id: None,
35            local_id: None,
36            read_by_sender: None,
37        }
38    }
39
40    /// Gets the message `queue_id` field.
41    ///
42    /// For clients supporting
43    /// [local echo](https://zulip.readthedocs.io/en/latest/subsystems/sending-messages.html#local-echo),
44    /// the [event queue](https://zulip.com/api/register-queue)
45    /// ID for the client.
46    ///
47    /// If passed, `local_id` is required. If the message is
48    /// successfully sent, the server will include `local_id` in the `message` event
49    /// that the client with this `queue_id` will receive notifying it of the new message
50    /// via [`GET /events`](https://zulip.com/api/get-events). This lets the client know unambiguously
51    /// that it should replace the locally echoed message, rather than adding this new
52    /// message (which would be correct if the user had sent the new message from another
53    /// device).
54    pub const fn queue_id(&self) -> Option<Uuid> {
55        self.queue_id
56    }
57
58    /// Sets the message `queue_id` and `local_id` fields.
59    ///
60    /// If set, the `queue_id` field requires setting the `local_id` field.
61    ///
62    /// For clients supporting
63    /// [local echo](https://zulip.readthedocs.io/en/latest/subsystems/sending-messages.html#local-echo),
64    /// the [event queue](https://zulip.com/api/register-queue)
65    /// ID for the client.
66    ///
67    /// If passed, `local_id` is required. If the message is
68    /// successfully sent, the server will include `local_id` in the `message` event
69    /// that the client with this `queue_id` will receive notifying it of the new message
70    /// via [`GET /events`](https://zulip.com/api/get-events). This lets the client know unambiguously
71    /// that it should replace the locally echoed message, rather than adding this new
72    /// message (which would be correct if the user had sent the new message from another
73    /// device).
74    pub fn set_queue_id<'a, U: Into<Uuid>, S: Into<Cow<'a, str>>>(
75        &mut self,
76        queue_id: U,
77        local_id: S,
78    ) {
79        self.queue_id = Some(queue_id.into());
80        self.local_id = Some(S::into(local_id).into());
81    }
82
83    /// Unsets the message `queue_id` and `local_id` fields.
84    ///
85    /// If set, the `queue_id` field requires setting the `local_id` field.
86    ///
87    /// For clients supporting
88    /// [local echo](https://zulip.readthedocs.io/en/latest/subsystems/sending-messages.html#local-echo),
89    /// the [event queue](https://zulip.com/api/register-queue)
90    /// ID for the client.
91    ///
92    /// If passed, `local_id` is required. If the message is
93    /// successfully sent, the server will include `local_id` in the `message` event
94    /// that the client with this `queue_id` will receive notifying it of the new message
95    /// via [`GET /events`](https://zulip.com/api/get-events). This lets the client know unambiguously
96    /// that it should replace the locally echoed message, rather than adding this new
97    /// message (which would be correct if the user had sent the new message from another
98    /// device).
99    pub fn unset_queue_id(&mut self) -> (Option<Uuid>, Option<String>) {
100        (self.queue_id.take(), self.local_id.take())
101    }
102
103    /// Builder function that sets the message `queue_id` and `local_id` fields.
104    ///
105    /// If set, the `queue_id` field requires setting the `local_id` field.
106    ///
107    /// For clients supporting
108    /// [local echo](https://zulip.readthedocs.io/en/latest/subsystems/sending-messages.html#local-echo),
109    /// the [event queue](https://zulip.com/api/register-queue)
110    /// ID for the client.
111    ///
112    /// If passed, `local_id` is required. If the message is
113    /// successfully sent, the server will include `local_id` in the `message` event
114    /// that the client with this `queue_id` will receive notifying it of the new message
115    /// via [`GET /events`](https://zulip.com/api/get-events). This lets the client know unambiguously
116    /// that it should replace the locally echoed message, rather than adding this new
117    /// message (which would be correct if the user had sent the new message from another
118    /// device).
119    pub fn with_queue_id<'a, U: Into<Uuid>, S: Into<Cow<'a, str>>>(
120        self,
121        queue_id: U,
122        local_id: S,
123    ) -> Self {
124        Self {
125            queue_id: Some(queue_id.into()),
126            local_id: Some(S::into(local_id).into()),
127            ..self
128        }
129    }
130
131    /// Gets the message `local_id` field.
132    ///
133    /// For clients supporting local echo, a unique string-format identifier
134    /// chosen freely by the client; the server will pass it back to the client without
135    /// inspecting it, as described in the `queue_id` description.
136    pub fn local_id(&self) -> Option<&str> {
137        self.local_id.as_deref()
138    }
139}
140
141field_access! {
142    Request {
143        /// The type of message to be sent.
144        ///
145        /// `"direct"` for a direct message and `"stream"` or `"channel"` for a
146        /// channel message.
147        ///
148        /// **Changes**: In Zulip 9.0 (feature level 248), `"channel"` was added as
149        /// an additional value for this parameter to request a channel message.
150        ///
151        /// In Zulip 7.0 (feature level 174), `"direct"` was added as
152        /// the preferred way to request a direct message, deprecating the original
153        /// `"private"`. While `"private"` is still supported for requesting direct
154        /// messages, clients are encouraged to use to the modern convention with
155        /// servers that support it, because support for `"private"` will eventually
156        /// be removed.
157        msg_type: MessageType,
158    }
159}
160
161field_access! {
162    Request {
163        /// For channel messages, either the name or integer ID of the channel. For
164        /// direct messages, either a list containing integer user IDs or a list
165        /// containing string Zulip API email addresses.
166        ///
167        /// **Changes**: Support for using user/channel IDs was added in Zulip 2.0.0.
168        to: as_ref { &MessageTo, MessageTo },
169    }
170}
171
172field_access! {
173    Request {
174        /// The content of the message.
175        ///
176        /// Clients should use the `max_message_length` returned by the
177        /// [`POST /register`](https://zulip.com/api/register-queue) endpoint to determine
178        /// the maximum message size.
179        content: cow { str },
180    }
181}
182
183field_access! {
184    Request {
185        /// The topic of the message.
186        ///
187        /// Only required for channel messages
188        /// (`"type": "stream"` or `"type": "channel"`), ignored otherwise.
189        ///
190        /// Clients should use the `max_topic_length` returned by the
191        /// [`POST /register`](https://zulip.com/api/register-queue) endpoint to determine
192        /// the maximum topic length.
193        ///
194        /// Note: When `"(no topic)"` or the value of `realm_empty_topic_display_name`
195        /// found in the [POST /register](https://zulip.com/api/register-queue) response is used for this
196        /// parameter, it is interpreted as an empty string.
197        ///
198        /// When [topics are required](https://zulip.com/help/require-topics), this parameter can't
199        /// be `"(no topic)"`, an empty string, or the value of `realm_empty_topic_display_name`.
200        ///
201        /// **Changes**: Before Zulip 10.0 (feature level 370), `"(no topic)"`
202        /// was not interpreted as an empty string.
203        ///
204        /// Before Zulip 10.0 (feature level 334), empty string
205        /// was not a valid topic name for channel messages.
206        ///
207        /// New in Zulip 2.0.0. Previous Zulip releases encoded
208        /// this as `subject`, which is currently a deprecated alias.
209        topic: option_cow_deref { str, String },
210    }
211}
212
213field_access! {
214    Request {
215        /// Whether the message should be initially marked read by its
216        /// sender. If unspecified, the server uses a heuristic based
217        /// on the client name.
218        ///
219        /// **Changes**: New in Zulip 8.0 (feature level 236).
220        read_by_sender: option { bool },
221    }
222}
223
224impl_default!(Request);
225impl_display!(Request);
226
227#[cfg(test)]
228mod tests {
229    use super::*;
230
231    #[test]
232    fn test_message_minimal() {
233        let message_ty = MessageType::Channel;
234        let ty_json = serde_json::to_string(&message_ty).unwrap();
235
236        let message_to = MessageTo::ChannelId(42);
237        let to_json = serde_json::to_string(&message_to).unwrap();
238
239        let content = "test message";
240        let content_json = serde_json::to_string(&content).unwrap();
241
242        let message = Request::new()
243            .with_msg_type(message_ty)
244            .with_to(message_to.clone())
245            .with_content(content);
246
247        let message_json =
248            format!(r#"{{"type":{ty_json},"to":{to_json},"content":{content_json}}}"#);
249
250        assert_eq!(serde_json::to_string(&message).unwrap(), message_json);
251        assert_eq!(
252            serde_json::from_str::<Request>(&message_json).unwrap(),
253            message
254        );
255        assert_eq!(message.msg_type(), message_ty);
256        assert_eq!(message.to(), &message_to);
257        assert_eq!(message.content(), content);
258    }
259
260    #[test]
261    fn test_message_full() {
262        let message_ty = MessageType::Channel;
263        let ty_json = serde_json::to_string(&message_ty).unwrap();
264
265        let message_to = MessageTo::ChannelId(42);
266        let to_json = serde_json::to_string(&message_to).unwrap();
267
268        let content = "test message";
269        let content_json = serde_json::to_string(&content).unwrap();
270
271        let topic = "test topic";
272        let topic_json = serde_json::to_string(&topic).unwrap();
273
274        let queue_id = Uuid::try_from("936da01f-9abd-4d9d-80c7-02af85c822a8").unwrap();
275        let queue_id_json = serde_json::to_string(&queue_id).unwrap();
276
277        let local_id = "test.local_id";
278        let local_id_json = serde_json::to_string(&local_id).unwrap();
279
280        let read_by_sender = false;
281        let read_by_sender_json = serde_json::to_string(&read_by_sender).unwrap();
282
283        let message = Request::new()
284            .with_msg_type(message_ty)
285            .with_to(message_to.clone())
286            .with_content(content)
287            .with_topic(topic)
288            .with_queue_id(queue_id, local_id)
289            .with_read_by_sender(read_by_sender);
290
291        let message_json = format!(
292            r#"{{"type":{ty_json},"to":{to_json},"content":{content_json},"topic":{topic_json},"queue_id":{queue_id_json},"local_id":{local_id_json},"read_by_sender":{read_by_sender_json}}}"#
293        );
294
295        assert_eq!(serde_json::to_string(&message).unwrap(), message_json);
296        assert_eq!(
297            serde_json::from_str::<Request>(&message_json).unwrap(),
298            message
299        );
300        assert_eq!(message.msg_type(), message_ty);
301        assert_eq!(message.to(), &message_to);
302        assert_eq!(message.content(), content);
303        assert_eq!(message.topic(), Some(topic));
304        assert_eq!(message.queue_id(), Some(queue_id));
305        assert_eq!(message.local_id(), Some(local_id));
306        assert_eq!(message.read_by_sender(), Some(read_by_sender));
307    }
308}