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::{de::DeserializeOwned, Serialize};
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// Tests exercise failure paths and invariants directly; unwrap/expect,
85// slicing, and panicking asserts are acceptable here — violations
86// surface as test failures, not production panics.
87#[allow(
88    clippy::unwrap_used,
89    clippy::expect_used,
90    clippy::indexing_slicing,
91    clippy::panic
92)]
93#[cfg(test)]
94mod tests {
95    use super::*;
96    use serde::{Deserialize, Serialize};
97
98    #[derive(Serialize, Deserialize, Debug, PartialEq, Clone)]
99    struct Payload {
100        id: u32,
101        msg: String,
102    }
103
104    #[test]
105    fn codec_roundtrip_trait() {
106        let p = Payload {
107            id: 42,
108            msg: "hello".into(),
109        };
110        let s = p.encode();
111        let d = Payload::decode(&s).unwrap();
112        assert_eq!(p, d);
113    }
114
115    #[test]
116    fn codec_roundtrip_result() {
117        let p = Payload {
118            id: 1,
119            msg: "world".into(),
120        };
121        let s = p.encode_result().unwrap();
122        let d = Payload::decode(&s).unwrap();
123        assert_eq!(p, d);
124    }
125
126    #[test]
127    fn codec_invalid_decode() {
128        let err = Payload::decode("not json").unwrap_err();
129        assert_eq!(err, WsError::InvalidMessage);
130    }
131
132    #[test]
133    fn json_codec_helpers() {
134        let p = Payload {
135            id: 7,
136            msg: "test".into(),
137        };
138        let s = JsonCodec::encode(&p).unwrap();
139        let d: Payload = JsonCodec::decode(&s).unwrap();
140        assert_eq!(p, d);
141
142        let pretty = JsonCodec::encode_pretty(&p).unwrap();
143        assert!(pretty.contains('\n'));
144    }
145
146    #[test]
147    fn encode_vec() {
148        let v = vec![1, 2, 3];
149        let s = v.encode();
150        let d: Vec<i32> = Vec::decode(&s).unwrap();
151        assert_eq!(v, d);
152    }
153}