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