Skip to main content

pondsocket_common/
lib.rs

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}