1use serde::{Deserialize, Serialize};
2use serde_json::{Map, Value};
3use thiserror::Error;
4
5pub type PondMessage = Map<String, Value>;
6pub type PondPresence = Map<String, Value>;
7pub type PondAssigns = Map<String, Value>;
8pub type JoinParams = Map<String, Value>;
9
10#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
11#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
12pub enum PresenceEventType {
13 Join,
14 Leave,
15 Update,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
19#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
20pub enum ClientAction {
21 JoinChannel,
22 LeaveChannel,
23 Broadcast,
24}
25
26#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
27#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
28pub enum ServerAction {
29 Presence,
30 System,
31 Broadcast,
32 Error,
33 Connect,
34}
35
36#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
37#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
38pub enum ChannelState {
39 Idle,
40 Joining,
41 Joined,
42 Stalled,
43 Closed,
44 Declined,
45}
46
47#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
48#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
49pub enum ErrorType {
50 UnauthorizedConnection,
51 UnauthorizedJoinRequest,
52 UnauthorizedBroadcast,
53 InvalidMessage,
54 HandlerNotFound,
55 PresenceError,
56 ChannelError,
57 EndpointError,
58 InternalServerError,
59}
60
61#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
62#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
63pub enum EventName {
64 Acknowledge,
65 ExitAcknowledge,
66 Connection,
67 InternalError,
68 NotFound,
69 Unauthorized,
70}
71
72#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
73pub struct ClientMessage {
74 pub event: String,
75 #[serde(rename = "requestId")]
76 pub request_id: String,
77 #[serde(rename = "channelName")]
78 pub channel_name: String,
79 #[serde(default)]
80 pub payload: PondMessage,
81 pub action: ClientAction,
82}
83
84#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
85pub struct ServerMessage {
86 pub event: String,
87 #[serde(rename = "requestId")]
88 pub request_id: String,
89 #[serde(rename = "channelName")]
90 pub channel_name: String,
91 #[serde(default)]
92 pub payload: PondMessage,
93 pub action: ServerAction,
94}
95
96#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
97pub struct PresencePayload {
98 #[serde(default)]
99 pub presence: Vec<PondPresence>,
100 #[serde(default)]
101 pub changed: PondPresence,
102}
103
104#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
105pub struct PresenceMessage {
106 #[serde(rename = "requestId")]
107 pub request_id: String,
108 #[serde(rename = "channelName")]
109 pub channel_name: String,
110 pub event: PresenceEventType,
111 pub action: PresenceAction,
112 pub payload: PresencePayload,
113}
114
115#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
116#[serde(rename_all = "SCREAMING_SNAKE_CASE")]
117pub enum PresenceAction {
118 Presence,
119}
120
121#[derive(Debug, Clone, PartialEq)]
122pub enum ChannelEvent {
123 Message(ServerMessage),
124 Presence(PresenceMessage),
125}
126
127#[derive(Debug, Clone, Error, PartialEq, Eq)]
128#[error("{path}: {message}")]
129pub struct ValidationError {
130 pub path: String,
131 pub message: String,
132}
133
134impl ValidationError {
135 pub fn new(path: impl Into<String>, message: impl Into<String>) -> Self {
136 Self {
137 path: path.into(),
138 message: message.into(),
139 }
140 }
141}
142
143pub fn uuid() -> String {
144 uuid::Uuid::new_v4().to_string()
145}
146
147pub fn parse_client_message(data: &str) -> Result<ClientMessage, ValidationError> {
148 let value = parse_object(data, "clientMessage")?;
149 serde_json::from_value(value).map_err(|e| ValidationError::new("clientMessage", e.to_string()))
150}
151
152pub fn parse_server_message(data: &str) -> Result<ServerMessage, ValidationError> {
153 let value = parse_object(data, "serverMessage")?;
154 serde_json::from_value(value).map_err(|e| ValidationError::new("serverMessage", e.to_string()))
155}
156
157pub fn parse_channel_event(data: &str) -> Result<ChannelEvent, ValidationError> {
158 let value = parse_object(data, "channelEvent")?;
159 let action = value
160 .get("action")
161 .and_then(Value::as_str)
162 .ok_or_else(|| ValidationError::new("action", "Missing required field"))?;
163 if action == "PRESENCE" {
164 let msg: PresenceMessage = serde_json::from_value(value)
165 .map_err(|e| ValidationError::new("presenceMessage", e.to_string()))?;
166 Ok(ChannelEvent::Presence(msg))
167 } else {
168 let msg: ServerMessage = serde_json::from_value(value)
169 .map_err(|e| ValidationError::new("serverMessage", e.to_string()))?;
170 Ok(ChannelEvent::Message(msg))
171 }
172}
173
174fn parse_object(data: &str, root: &str) -> Result<Value, ValidationError> {
175 let value: Value =
176 serde_json::from_str(data).map_err(|e| ValidationError::new(root, e.to_string()))?;
177 if value.is_object() {
178 Ok(value)
179 } else {
180 Err(ValidationError::new(root, "Expected object"))
181 }
182}
183
184pub fn message_to_json(message: &ServerMessage) -> serde_json::Result<String> {
185 serde_json::to_string(message)
186}
187
188#[cfg(test)]
189mod tests {
190 use super::*;
191
192 #[test]
193 fn parses_client_message_with_camel_case_fields() {
194 let msg = parse_client_message(
195 r#"{"action":"BROADCAST","event":"message","channelName":"/chat/1","requestId":"r1","payload":{"text":"hi"}}"#,
196 )
197 .unwrap();
198 assert_eq!(msg.action, ClientAction::Broadcast);
199 assert_eq!(msg.channel_name, "/chat/1");
200 }
201
202 #[test]
203 fn routes_presence_channel_event() {
204 let ev = parse_channel_event(
205 r#"{"action":"PRESENCE","event":"JOIN","channelName":"/chat/1","requestId":"r1","payload":{"presence":[{"id":"u1"}],"changed":{"id":"u1"}}}"#,
206 )
207 .unwrap();
208 assert!(matches!(ev, ChannelEvent::Presence(_)));
209 }
210}