1use serde::de::DeserializeOwned;
2use serde::{Deserialize, Serialize};
3use serde_json::{Map, Value};
4use thiserror::Error;
5
6pub type PondMessage = Map<String, Value>;
7pub type PondPresence = Map<String, Value>;
8pub type PondAssigns = Map<String, Value>;
9pub type JoinParams = Map<String, Value>;
10
11pub trait PondEvent {
12 type Payload: Serialize + DeserializeOwned + Send + Sync + 'static;
13 type Response: Serialize + DeserializeOwned + Send + Sync + 'static;
14
15 const NAME: &'static str;
16}
17
18pub trait PondSchema {
19 type Presence: Serialize + DeserializeOwned + Send + Sync + 'static;
20 type Assigns: Serialize + DeserializeOwned + Send + Sync + 'static;
21 type JoinParams: Serialize + DeserializeOwned + Send + Sync + 'static;
22}
23
24pub fn to_pond_value<T>(value: &T) -> serde_json::Result<Value>
25where
26 T: Serialize + ?Sized,
27{
28 serde_json::to_value(value)
29}
30
31pub fn to_pond_map<T>(value: &T) -> serde_json::Result<Map<String, Value>>
32where
33 T: Serialize + ?Sized,
34{
35 match serde_json::to_value(value)? {
36 Value::Object(map) => Ok(map),
37 Value::Null => Ok(Map::new()),
38 value => {
39 let mut map = Map::new();
40 map.insert("value".to_owned(), value);
41 Ok(map)
42 }
43 }
44}
45
46pub fn from_pond_value<T>(value: Value) -> serde_json::Result<T>
47where
48 T: DeserializeOwned,
49{
50 serde_json::from_value(value)
51}
52
53pub fn from_pond_map<T>(value: Map<String, Value>) -> serde_json::Result<T>
54where
55 T: DeserializeOwned,
56{
57 serde_json::from_value(Value::Object(value))
58}
59
60#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
61#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
62pub enum PresenceEventType {
63 Join,
64 Leave,
65 Update,
66}
67
68#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
69#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
70pub enum ClientAction {
71 JoinChannel,
72 LeaveChannel,
73 Broadcast,
74}
75
76#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
77#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
78pub enum ServerAction {
79 Presence,
80 System,
81 Broadcast,
82 Error,
83 Connect,
84}
85
86#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
87#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
88pub enum ChannelState {
89 Idle,
90 Joining,
91 Joined,
92 Stalled,
93 Closed,
94 Declined,
95}
96
97#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
98#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
99pub enum ErrorType {
100 UnauthorizedConnection,
101 UnauthorizedJoinRequest,
102 UnauthorizedBroadcast,
103 InvalidMessage,
104 HandlerNotFound,
105 PresenceError,
106 ChannelError,
107 EndpointError,
108 InternalServerError,
109}
110
111#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
112#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
113pub enum EventName {
114 Acknowledge,
115 ExitAcknowledge,
116 Connection,
117 InternalError,
118 NotFound,
119 Unauthorized,
120}
121
122#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
123pub struct ClientMessage {
124 pub event: String,
125 #[serde(rename = "requestId")]
126 pub request_id: String,
127 #[serde(rename = "channelName")]
128 pub channel_name: String,
129 #[serde(default)]
130 pub payload: PondMessage,
131 pub action: ClientAction,
132}
133
134#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
135pub struct ServerMessage {
136 pub event: String,
137 #[serde(rename = "requestId")]
138 pub request_id: String,
139 #[serde(rename = "channelName")]
140 pub channel_name: String,
141 #[serde(default)]
142 pub payload: PondMessage,
143 pub action: ServerAction,
144}
145
146#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
147pub struct PresencePayload {
148 #[serde(default)]
149 pub presence: Vec<PondPresence>,
150 #[serde(default)]
151 pub changed: PondPresence,
152}
153
154#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
155pub struct PresenceMessage {
156 #[serde(rename = "requestId")]
157 pub request_id: String,
158 #[serde(rename = "channelName")]
159 pub channel_name: String,
160 pub event: PresenceEventType,
161 pub action: PresenceAction,
162 pub payload: PresencePayload,
163}
164
165#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
166#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
167pub enum PresenceAction {
168 Presence,
169}
170
171#[derive(Debug, Clone, PartialEq)]
172pub enum ChannelEvent {
173 Message(ServerMessage),
174 Presence(PresenceMessage),
175}
176
177#[derive(Debug, Clone, Error, PartialEq, Eq)]
178#[error("{path}: {message}")]
179pub struct ValidationError {
180 pub path: String,
181 pub message: String,
182}
183
184impl ValidationError {
185 pub fn new(path: impl Into<String>, message: impl Into<String>) -> Self {
186 Self {
187 path: path.into(),
188 message: message.into(),
189 }
190 }
191}
192
193pub fn uuid() -> String {
194 uuid::Uuid::new_v4().to_string()
195}
196
197pub fn parse_client_message(data: &str) -> Result<ClientMessage, ValidationError> {
198 let value = parse_object(data, "clientMessage")?;
199 serde_json::from_value(value).map_err(|e| ValidationError::new("clientMessage", e.to_string()))
200}
201
202pub fn parse_server_message(data: &str) -> Result<ServerMessage, ValidationError> {
203 let value = parse_object(data, "serverMessage")?;
204 serde_json::from_value(value).map_err(|e| ValidationError::new("serverMessage", e.to_string()))
205}
206
207pub fn parse_channel_event(data: &str) -> Result<ChannelEvent, ValidationError> {
208 let value = parse_object(data, "channelEvent")?;
209 let action = value
210 .get("action")
211 .and_then(Value::as_str)
212 .ok_or_else(|| ValidationError::new("action", "Missing required field"))?;
213 if action == "PRESENCE" {
214 let msg: PresenceMessage = serde_json::from_value(value)
215 .map_err(|e| ValidationError::new("presenceMessage", e.to_string()))?;
216 Ok(ChannelEvent::Presence(msg))
217 } else {
218 let msg: ServerMessage = serde_json::from_value(value)
219 .map_err(|e| ValidationError::new("serverMessage", e.to_string()))?;
220 Ok(ChannelEvent::Message(msg))
221 }
222}
223
224fn parse_object(data: &str, root: &str) -> Result<Value, ValidationError> {
225 let value: Value =
226 serde_json::from_str(data).map_err(|e| ValidationError::new(root, e.to_string()))?;
227 if value.is_object() {
228 Ok(value)
229 } else {
230 Err(ValidationError::new(root, "Expected object"))
231 }
232}
233
234pub fn message_to_json(message: &ServerMessage) -> serde_json::Result<String> {
235 serde_json::to_string(message)
236}
237
238#[cfg(test)]
239mod tests {
240 use super::*;
241
242 #[test]
243 fn parses_client_message_with_camel_case_fields() {
244 let msg = parse_client_message(
245 r#"{"action":"BROADCAST","event":"message","channelName":"/chat/1","requestId":"r1","payload":{"text":"hi"}}"#,
246 )
247 .unwrap();
248 assert_eq!(msg.action, ClientAction::Broadcast);
249 assert_eq!(msg.channel_name, "/chat/1");
250 }
251
252 #[test]
253 fn routes_presence_channel_event() {
254 let ev = parse_channel_event(
255 r#"{"action":"PRESENCE","event":"JOIN","channelName":"/chat/1","requestId":"r1","payload":{"presence":[{"id":"u1"}],"changed":{"id":"u1"}}}"#,
256 )
257 .unwrap();
258 assert!(matches!(ev, ChannelEvent::Presence(_)));
259 }
260
261 #[test]
262 fn adv_presence_frame_with_unknown_event_is_dropped() {
263 let ev = parse_channel_event(
264 r#"{"action":"PRESENCE","event":"EVICTED","channelName":"/c","requestId":"r1","payload":{"presence":[],"changed":{}}}"#,
265 );
266 assert!(ev.is_err());
267 }
268
269 #[test]
270 fn adv_presence_frame_with_non_array_presence_is_dropped() {
271 let ev = parse_channel_event(
272 r#"{"action":"PRESENCE","event":"JOIN","channelName":"/c","requestId":"r1","payload":{"presence":{},"changed":{}}}"#,
273 );
274 assert!(ev.is_err());
275 }
276
277 #[test]
278 fn adv_presence_frame_missing_changed_parses_with_default() {
279 let ev = parse_channel_event(
280 r#"{"action":"PRESENCE","event":"JOIN","channelName":"/c","requestId":"r1","payload":{"presence":[{"id":"u1"}]}}"#,
281 )
282 .unwrap();
283 match ev {
284 ChannelEvent::Presence(message) => {
285 assert!(message.payload.changed.is_empty());
286 assert_eq!(message.payload.presence.len(), 1);
287 }
288 other => panic!("expected presence event, got {other:?}"),
289 }
290 }
291
292 #[test]
293 fn adv_system_evicted_frame_routes_as_message_not_presence() {
294 let ev = parse_channel_event(
295 r#"{"action":"SYSTEM","event":"EVICTED","channelName":"/c","requestId":"r1","payload":{"reason":"kicked"}}"#,
296 )
297 .unwrap();
298 match ev {
299 ChannelEvent::Message(message) => {
300 assert_eq!(message.event, "EVICTED");
301 assert_eq!(message.action, ServerAction::System);
302 }
303 other => panic!("expected message event, got {other:?}"),
304 }
305 }
306
307 #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
308 struct Sample {
309 id: String,
310 count: u32,
311 }
312
313 #[test]
314 fn pond_value_round_trips_through_from_pond_value() {
315 let sample = Sample {
316 id: "a".to_owned(),
317 count: 3,
318 };
319 let value = to_pond_value(&sample).unwrap();
320 assert_eq!(from_pond_value::<Sample>(value).unwrap(), sample);
321 }
322
323 #[test]
324 fn pond_map_round_trips_through_from_pond_map() {
325 let sample = Sample {
326 id: "b".to_owned(),
327 count: 7,
328 };
329 let map = to_pond_map(&sample).unwrap();
330 assert_eq!(map["id"], Value::from("b"));
331 assert_eq!(from_pond_map::<Sample>(map).unwrap(), sample);
332 }
333
334 #[test]
335 fn to_pond_map_wraps_scalar_under_value_key() {
336 let map = to_pond_map(&42u32).unwrap();
337 assert_eq!(map["value"], Value::from(42));
338 }
339
340 #[test]
341 fn to_pond_map_maps_null_to_empty_map() {
342 let map = to_pond_map(&Option::<Sample>::None).unwrap();
343 assert!(map.is_empty());
344 }
345}