Skip to main content

pondsocket_common/
lib.rs

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}