Skip to main content

phoenix_chan/
message.rs

1//! Messages sent from and to the phoenix channel.
2
3use std::borrow::Cow;
4use std::fmt::{Debug, Display};
5
6use serde::de::DeserializeOwned;
7use serde::{Deserialize, Serialize};
8
9use crate::client::Id;
10
11/// Message received from the channel.
12#[derive(Debug, Clone, PartialEq, Eq)]
13#[non_exhaustive]
14pub struct Message<P> {
15    /// The `join_reference` is also chosen by the client and should also be a unique value.
16    ///
17    /// It only needs to be sent for a `phx_join` event; for other messages it can be null. It is
18    /// used as a message reference for push messages from the server, meaning those that are not
19    /// replies to a specific client message. For example, imagine something like "a new user just
20    /// joined the chat room".
21    pub join_reference: Option<String>,
22    /// The `message_reference` is chosen by the client and should be a unique value.
23    ///
24    /// The server includes it in its reply so that the client knows which message the reply is for.
25    pub message_reference: Option<String>,
26    /// The `topic_name` must be a known topic for the socket endpoint, and a client must join that
27    /// topic before sending any messages on it.
28    pub topic_name: String,
29    /// The `event_name` must match the first argument of a `handle_in` function on the server channel
30    /// module.
31    pub event_name: String,
32    /// The `payload` should be a map and is passed as the second argument to that `handle_in`
33    /// function.
34    pub payload: P,
35}
36
37impl<P> Message<P> {
38    pub(crate) fn info(&self) -> String {
39        format!(
40            "[{:?}, {:?}, {:?}, {:?}, <payload>]",
41            self.join_reference, self.message_reference, self.topic_name, self.event_name
42        )
43    }
44}
45
46impl<'a, P> From<ChannelMsg<'a, P>> for Message<P> {
47    fn from(value: ChannelMsg<'a, P>) -> Self {
48        Self {
49            join_reference: value.join_reference.map(Cow::into),
50            message_reference: value.message_reference.map(Cow::into),
51            topic_name: value.topic_name.into(),
52            event_name: value.event_name.into(),
53            payload: value.payload,
54        }
55    }
56}
57
58impl Message<serde_json::Value> {
59    /// Deserialize the value in a specific payload type.
60    ///
61    /// This makes it possible to match on the [`topic_name`](Message::topic_name) and
62    /// [`event_name`](Message::event_name) to differentiate the various responses.
63    pub fn deserialize_payload<P>(self) -> Result<Message<P>, serde_json::error::Error>
64    where
65        P: DeserializeOwned,
66    {
67        let payload = serde_json::from_value(self.payload)?;
68
69        Ok(Message {
70            join_reference: self.join_reference,
71            message_reference: self.message_reference,
72            topic_name: self.topic_name,
73            event_name: self.event_name,
74            payload,
75        })
76    }
77}
78
79impl<P> Display for Message<P>
80where
81    P: Serialize + Debug,
82{
83    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
84        write!(f, "[")?;
85        ser_or_debug(&self.join_reference, f)?;
86        write!(f, ", ")?;
87        ser_or_debug(&self.message_reference, f)?;
88        write!(f, ", ")?;
89        ser_or_debug(&self.topic_name, f)?;
90        write!(f, ", ")?;
91        ser_or_debug(&self.event_name, f)?;
92        write!(f, ", ")?;
93        ser_or_debug(&self.payload, f)?;
94        write!(f, "]")
95    }
96}
97
98fn ser_or_debug<T>(v: &T, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result
99where
100    T: Serialize + Debug,
101{
102    if let Ok(s) = serde_json::to_string(v) {
103        write!(f, "{s}")
104    } else {
105        write!(f, "{v:?}")
106    }
107}
108
109#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
110pub(crate) struct ChannelMsg<'a, P> {
111    pub(crate) join_reference: Option<Cow<'a, str>>,
112    pub(crate) message_reference: Option<Cow<'a, str>>,
113    pub(crate) topic_name: Cow<'a, str>,
114    pub(crate) event_name: Cow<'a, str>,
115    pub(crate) payload: P,
116}
117
118impl<'a, P> ChannelMsg<'a, P> {
119    pub(crate) fn new(
120        join_reference: Option<Id>,
121        message_reference: Option<Id>,
122        topic_name: &'a str,
123        event_name: &'a str,
124        payload: P,
125    ) -> Self {
126        Self {
127            join_reference: join_reference.map(|id| Cow::Owned(id.to_string())),
128            message_reference: message_reference.map(|id| Cow::Owned(id.to_string())),
129            topic_name: Cow::Borrowed(topic_name),
130            event_name: Cow::Borrowed(event_name),
131            payload,
132        }
133    }
134
135    pub(crate) fn into_err(self) -> Message<()> {
136        Message {
137            join_reference: self.join_reference.map(Cow::into),
138            message_reference: self.message_reference.map(Cow::into),
139            topic_name: self.topic_name.into(),
140            event_name: self.event_name.into(),
141            payload: (),
142        }
143    }
144}
145
146impl<P> Serialize for ChannelMsg<'_, P>
147where
148    P: Serialize,
149{
150    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
151    where
152        S: serde::Serializer,
153    {
154        let Self {
155            join_reference,
156            message_reference,
157            topic_name,
158            event_name,
159            payload,
160        } = self;
161
162        (
163            join_reference,
164            message_reference,
165            topic_name,
166            event_name,
167            payload,
168        )
169            .serialize(serializer)
170    }
171}
172
173impl<'de, 'a, P> Deserialize<'de> for ChannelMsg<'a, P>
174where
175    P: Deserialize<'de>,
176{
177    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
178    where
179        D: serde::Deserializer<'de>,
180    {
181        let (join_reference, message_reference, topic_name, event_name, payload) =
182            Deserialize::deserialize(deserializer)?;
183
184        Ok(Self {
185            join_reference,
186            message_reference,
187            topic_name,
188            event_name,
189            payload,
190        })
191    }
192}
193
194impl<P> Display for ChannelMsg<'_, P>
195where
196    P: Serialize + Debug,
197{
198    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
199        let Ok(s) = serde_json::to_string(self) else {
200            return write!(
201                f,
202                "[{:?}, {:?}, {:?}, {:?}, {:?}]",
203                self.join_reference,
204                self.message_reference,
205                self.topic_name,
206                self.event_name,
207                self.payload,
208            );
209        };
210
211        write!(f, "{s}")
212    }
213}
214
215#[cfg(test)]
216mod tests {
217    use pretty_assertions::assert_eq;
218
219    use crate::Map;
220
221    use super::*;
222
223    #[test]
224    fn deserialize_payload_to_struct() {
225        let join = Message {
226            join_reference: Some("0".to_string()),
227            message_reference: Some("0".to_string()),
228            topic_name: "phoenix".to_string(),
229            event_name: "phx_reply".to_string(),
230            payload: serde_json::Value::Object(serde_json::Map::from_iter([
231                (
232                    "status".to_string(),
233                    serde_json::Value::String("ok".to_string()),
234                ),
235                (
236                    "response".to_string(),
237                    serde_json::Value::Object(serde_json::Map::default()),
238                ),
239            ])),
240        };
241
242        #[derive(Debug, Deserialize, PartialEq, Eq)]
243        struct Payload {
244            status: String,
245            response: Map,
246        }
247
248        let exp = Message {
249            join_reference: join.join_reference.clone(),
250            message_reference: join.message_reference.clone(),
251            topic_name: join.topic_name.clone(),
252            event_name: join.event_name.clone(),
253            payload: Payload {
254                status: "ok".to_string(),
255                response: Map::default(),
256            },
257        };
258
259        let message: Message<Payload> = join.deserialize_payload().unwrap();
260
261        assert_eq!(message, exp);
262    }
263
264    #[test]
265    fn serialize_deserialize_join() {
266        let join = r#"["0","0","miami:weather","phx_join",{"some":"param"}]"#;
267
268        let message: ChannelMsg<Map> = serde_json::from_str(join).unwrap();
269
270        let exp = ChannelMsg::new(
271            Some(0),
272            Some(0),
273            "miami:weather",
274            "phx_join",
275            Map::from_iter([("some".to_string(), "param".to_string())]),
276        );
277
278        assert_eq!(message, exp);
279
280        let json = serde_json::to_string(&message).unwrap();
281
282        assert_eq!(json, join);
283    }
284
285    #[test]
286    fn serialize_deserialize_leave() {
287        let join = r#"[null,"1","miami:weather","phx_leave",{}]"#;
288
289        let message: ChannelMsg<Map> = serde_json::from_str(join).unwrap();
290
291        let exp = ChannelMsg::new(None, Some(1), "miami:weather", "phx_leave", Map::default());
292
293        assert_eq!(message, exp);
294
295        let json = serde_json::to_string(&message).unwrap();
296
297        assert_eq!(json, join);
298    }
299
300    #[test]
301    fn serialize_deserialize_heartbit() {
302        let join = r#"[null,"2","phoenix","heartbeat",{}]"#;
303
304        let message: ChannelMsg<Map> = serde_json::from_str(join).unwrap();
305
306        let exp = ChannelMsg::new(None, Some(2), "phoenix", "heartbeat", Map::default());
307
308        assert_eq!(message, exp);
309
310        let json = serde_json::to_string(&message).unwrap();
311
312        assert_eq!(json, join);
313    }
314
315    #[test]
316    fn serialize_deserialize_send_example() {
317        let join = r#"[null,"3","miami:weather","report_emergency",{"category":"sharknado"}]"#;
318
319        let message: ChannelMsg<Map> = serde_json::from_str(join).unwrap();
320
321        let exp = ChannelMsg::new(
322            None,
323            Some(3),
324            "miami:weather",
325            "report_emergency",
326            Map::from_iter([("category".to_string(), "sharknado".to_string())]),
327        );
328
329        assert_eq!(message, exp);
330
331        let json = serde_json::to_string(&message).unwrap();
332
333        assert_eq!(json, join);
334    }
335
336    #[test]
337    fn serialize_deserialize_no_message_reference() {
338        let join =
339            r#"[null,null,"rooms:test:dashboard_oSgokqqBReiKRg_c1nYqMQ_9899","watch_added",{}]"#;
340
341        let message: ChannelMsg<Map> = serde_json::from_str(join).unwrap();
342
343        let exp = ChannelMsg::new(
344            None,
345            None,
346            "rooms:test:dashboard_oSgokqqBReiKRg_c1nYqMQ_9899",
347            "watch_added",
348            Map::default(),
349        );
350
351        assert_eq!(message, exp);
352
353        let json = serde_json::to_string(&message).unwrap();
354
355        assert_eq!(json, join);
356    }
357}