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}