Skip to main content

ws_kit/
codec.rs

1//! Message codec.
2//!
3//! [`Codec`] provides `encode`/`decode` for WebSocket text frames. A blanket
4//! implementation via `serde_json` is provided for any `Serialize + DeserializeOwned`.
5
6use serde::{Serialize, de::DeserializeOwned};
7
8use crate::error::WsError;
9
10/// Trait for encoding/decoding WebSocket messages.
11///
12/// The default blanket implementation uses JSON via `serde_json`.
13///
14/// # Example
15///
16/// ```
17/// use ws_kit::codec::Codec;
18/// use serde::{Serialize, Deserialize};
19///
20/// #[derive(Serialize, Deserialize, PartialEq, Debug)]
21/// struct Msg { text: String }
22///
23/// let msg = Msg { text: "hello".into() };
24/// let s = msg.encode();
25/// let decoded = Msg::decode(&s).unwrap();
26/// assert_eq!(msg, decoded);
27/// ```
28pub trait Codec: Sized {
29    /// Encode `self` into a JSON string.
30    ///
31    /// This implementation serializes via `serde_json::to_string`.
32    /// Panics are avoided — if serialization fails the error is surfaced
33    /// through `WsError::InvalidMessage` in the `Result` variant. The
34    /// infallible `encode` variant below unwraps for convenience; use
35    /// [`Codec::encode_result`] when you need error handling.
36    fn encode(&self) -> String
37    where
38        Self: Serialize,
39    {
40        serde_json::to_string(self).unwrap_or_default()
41    }
42
43    /// Encode with error handling.
44    fn encode_result(&self) -> Result<String, WsError>
45    where
46        Self: Serialize,
47    {
48        serde_json::to_string(self).map_err(|_| WsError::InvalidMessage)
49    }
50
51    /// Decode `Self` from a JSON string.
52    fn decode(s: &str) -> Result<Self, WsError>
53    where
54        Self: DeserializeOwned,
55    {
56        serde_json::from_str(s).map_err(|_| WsError::InvalidMessage)
57    }
58}
59
60/// Blanket implementation for all serde types.
61impl<T> Codec for T where T: Serialize + DeserializeOwned {}
62
63/// JSON codec helper for types that don't want to implement [`Codec`] directly
64/// or where you need associated functions without trait dispatch.
65pub struct JsonCodec;
66
67impl JsonCodec {
68    /// Encode a value to JSON string.
69    pub fn encode<T: Serialize>(value: &T) -> Result<String, WsError> {
70        serde_json::to_string(value).map_err(|_| WsError::InvalidMessage)
71    }
72
73    /// Decode a value from JSON string.
74    pub fn decode<T: DeserializeOwned>(s: &str) -> Result<T, WsError> {
75        serde_json::from_str(s).map_err(|_| WsError::InvalidMessage)
76    }
77
78    /// Encode pretty-printed JSON (for debugging).
79    pub fn encode_pretty<T: Serialize>(value: &T) -> Result<String, WsError> {
80        serde_json::to_string_pretty(value).map_err(|_| WsError::InvalidMessage)
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87    use serde::{Deserialize, Serialize};
88
89    #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
90    struct Payload {
91        id: u32,
92        msg: String,
93    }
94
95    #[test]
96    fn codec_roundtrip_trait() {
97        let p = Payload {
98            id: 42,
99            msg: "hello".into(),
100        };
101        let s = p.encode();
102        let d = Payload::decode(&s).unwrap();
103        assert_eq!(p, d);
104    }
105
106    #[test]
107    fn codec_roundtrip_result() {
108        let p = Payload {
109            id: 1,
110            msg: "world".into(),
111        };
112        let s = p.encode_result().unwrap();
113        let d = Payload::decode(&s).unwrap();
114        assert_eq!(p, d);
115    }
116
117    #[test]
118    fn codec_invalid_decode() {
119        let err = Payload::decode("not json").unwrap_err();
120        assert_eq!(err, WsError::InvalidMessage);
121    }
122
123    #[test]
124    fn json_codec_helpers() {
125        let p = Payload {
126            id: 7,
127            msg: "test".into(),
128        };
129        let s = JsonCodec::encode(&p).unwrap();
130        let d: Payload = JsonCodec::decode(&s).unwrap();
131        assert_eq!(p, d);
132
133        let pretty = JsonCodec::encode_pretty(&p).unwrap();
134        assert!(pretty.contains('\n'));
135    }
136
137    #[test]
138    fn encode_vec() {
139        let v = vec![1, 2, 3];
140        let s = v.encode();
141        let d: Vec<i32> = Vec::decode(&s).unwrap();
142        assert_eq!(v, d);
143    }
144}