Skip to main content

trillium_channels/
channel_event.rs

1use crate::Version;
2use serde::{Deserialize, Serialize};
3use serde_json::Value;
4use std::borrow::Cow;
5
6/// # The messages passed between server and connected clients.
7///
8/// ChannelEvents contain a topic, event, payload, and if sent from a
9/// client, a unique reference identifier that can be used to respond to
10/// this event.
11///
12/// Most interfaces in this crate take an `Into<ChannelEvent>` instead of a ChannelEvent directly,
13/// so that you can either implement `Into<ChannelEvent>` for relevant types, or use these tuple
14/// From implementations:
15///
16/// ```
17/// use trillium_channels::ChannelEvent;
18/// use serde_json::{json, Value, to_string};
19/// let event: ChannelEvent = ("topic", "event").into();
20/// assert_eq!(event.topic(), "topic");
21/// assert_eq!(event.event(), "event");
22/// assert_eq!(event.payload(), &json!({}));
23///
24/// let event: ChannelEvent = ("topic", "event", &json!({"some": "payload"})).into();
25/// assert_eq!(event.topic(), "topic");
26/// assert_eq!(event.event(), "event");
27/// assert_eq!(to_string(event.payload()).unwrap(), r#"{"some":"payload"}"#);
28///
29/// #[derive(serde::Serialize)]
30/// struct SomePayload { payload: &'static str };
31/// let event: ChannelEvent = ("topic", "event", &SomePayload { payload: "anything" }).into();
32/// assert_eq!(event.topic(), "topic");
33/// assert_eq!(event.event(), "event");
34/// assert_eq!(to_string(event.payload()).unwrap(), r#"{"payload":"anything"}"#);
35
36#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Serialize)]
37pub struct ChannelEvent {
38    pub(crate) topic: Cow<'static, str>,
39    pub(crate) event: Cow<'static, str>,
40    pub(crate) payload: Value,
41
42    #[serde(rename = "ref")]
43    pub(crate) reference: Option<Cow<'static, str>>,
44
45    #[serde(rename = "join_ref")]
46    pub(crate) join_reference: Option<Cow<'static, str>>,
47}
48
49impl ChannelEvent {
50    /// Construct a new ChannelEvent with the same reference as this
51    /// ChannelEvent. Note that this is the only way of setting the
52    /// reference on an event.
53    ///
54    /// The `event` argument can be either a `String` or, more commonly, a `&'static str`.
55    ///
56    /// The topic will always be the same as the source ChannelEvent's topic.
57    pub fn build_reply(
58        &self,
59        event: impl Into<Cow<'static, str>>,
60        payload: &impl Serialize,
61    ) -> ChannelEvent {
62        ChannelEvent {
63            topic: self.topic.clone(),
64            event: event.into(),
65            payload: match serde_json::to_value(payload).unwrap() {
66                Value::Null => Value::Object(Default::default()),
67                other => other,
68            },
69            reference: self.reference.clone(),
70            join_reference: self.join_reference.clone(),
71        }
72    }
73
74    pub(crate) fn serialize(&self, version: Version) -> serde_json::Result<String> {
75        match version {
76            Version::V1 => serde_json::to_string(&self),
77
78            Version::V2 => serde_json::to_string(&(
79                &self.join_reference,
80                &self.reference,
81                &self.topic,
82                &self.event,
83                &self.payload,
84            )),
85        }
86    }
87
88    pub(crate) fn deserialize(string: &str, version: Version) -> serde_json::Result<Self> {
89        match version {
90            Version::V1 => serde_json::from_str(string),
91            Version::V2 => {
92                let (join_reference, reference, topic, event, payload): (
93                    Option<String>,
94                    Option<String>,
95                    String,
96                    String,
97                    Value,
98                ) = serde_json::from_str(string)?;
99                Ok(Self {
100                    join_reference: join_reference.map(Into::into),
101                    reference: reference.map(Into::into),
102                    topic: topic.into(),
103                    event: event.into(),
104                    payload,
105                })
106            }
107        }
108    }
109
110    /// Returns this ChannelEvent's topic
111    pub fn topic(&self) -> &str {
112        &self.topic
113    }
114
115    /// Returns this ChannelEvent's event
116    pub fn event(&self) -> &str {
117        &self.event
118    }
119
120    /// Returns this ChannelEvent's payload as a Value
121    pub fn payload(&self) -> &Value {
122        &self.payload
123    }
124
125    /// Returns the reference field ("ref" in json) for this ChannelEvent,
126    /// if one was provided by the client
127    pub fn reference(&self) -> Option<&str> {
128        self.reference.as_deref()
129    }
130
131    /// Constructs a new ChannelEvent from topic, event, and a
132    /// serializable payload. Use &() if no payload is needed.
133    ///
134    /// Note that the reference cannot be set this way. To set a
135    /// reference, use [`ChannelEvent::build_reply`]
136    pub fn new(
137        topic: impl Into<Cow<'static, str>>,
138        event: impl Into<Cow<'static, str>>,
139        payload: &impl Serialize,
140    ) -> Self {
141        Self {
142            topic: topic.into(),
143            event: event.into(),
144            payload: match serde_json::to_value(payload).unwrap() {
145                Value::Null => Value::Object(Default::default()),
146                other => other,
147            },
148            reference: None,
149            join_reference: None,
150        }
151    }
152
153    /// returns true if this ChannelEvent is used by the phoenix-channels compatability layer
154    ///
155    /// currently that means the topic is `"phoenix"` or the event is `"phx_join"` or `"phx_leave"`
156    pub(crate) fn is_system_event(&self) -> bool {
157        self.topic == "phoenix" || self.event == "phx_join" || self.event == "phx_leave"
158    }
159}
160
161impl<T, E> From<(T, E)> for ChannelEvent
162where
163    T: Into<Cow<'static, str>>,
164    E: Into<Cow<'static, str>>,
165{
166    fn from(te: (T, E)) -> Self {
167        let (topic, event) = te;
168        Self::new(topic, event, &())
169    }
170}
171
172impl<T, E, P> From<(T, E, P)> for ChannelEvent
173where
174    T: Into<Cow<'static, str>>,
175    E: Into<Cow<'static, str>>,
176    P: Serialize,
177{
178    fn from(tep: (T, E, P)) -> Self {
179        let (topic, event, payload) = tep;
180        Self::new(topic, event, &payload)
181    }
182}