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}