Skip to main content

cpop_protocol/codec/
json.rs

1// SPDX-License-Identifier: Apache-2.0
2
3//! JSON encoding/decoding for backwards compatibility.
4//!
5//! Provides human-readable serialization format for debugging and legacy support.
6
7use serde::{de::DeserializeOwned, Serialize};
8use std::io::{Read, Write};
9
10use super::{CodecError, Result};
11
12/// Serialize to pretty-printed JSON bytes.
13pub fn encode<T: Serialize>(value: &T) -> Result<Vec<u8>> {
14    serde_json::to_vec_pretty(value).map_err(|e| CodecError::JsonEncode(e.to_string()))
15}
16
17/// Serialize to compact (no whitespace) JSON bytes.
18pub fn encode_compact<T: Serialize>(value: &T) -> Result<Vec<u8>> {
19    serde_json::to_vec(value).map_err(|e| CodecError::JsonEncode(e.to_string()))
20}
21
22/// Deserialize from JSON bytes.
23pub fn decode<T: DeserializeOwned>(data: &[u8]) -> Result<T> {
24    serde_json::from_slice(data).map_err(|e| CodecError::JsonDecode(e.to_string()))
25}
26
27/// Serialize pretty-printed JSON into a writer.
28pub fn encode_to<T: Serialize, W: Write>(value: &T, mut writer: W) -> Result<()> {
29    let bytes = encode(value)?;
30    writer.write_all(&bytes)?;
31    Ok(())
32}
33
34/// Serialize compact JSON into a writer.
35pub fn encode_to_compact<T: Serialize, W: Write>(value: &T, mut writer: W) -> Result<()> {
36    let bytes = encode_compact(value)?;
37    writer.write_all(&bytes)?;
38    Ok(())
39}
40
41/// Deserialize from a JSON reader.
42pub fn decode_from<T: DeserializeOwned, R: Read>(reader: R) -> Result<T> {
43    serde_json::from_reader(reader).map_err(|e| CodecError::JsonDecode(e.to_string()))
44}
45
46/// Serialize to a pretty-printed JSON `String`.
47pub fn to_string<T: Serialize>(value: &T) -> Result<String> {
48    serde_json::to_string_pretty(value).map_err(|e| CodecError::JsonEncode(e.to_string()))
49}
50
51/// Serialize to a compact JSON `String`.
52pub fn to_string_compact<T: Serialize>(value: &T) -> Result<String> {
53    serde_json::to_string(value).map_err(|e| CodecError::JsonEncode(e.to_string()))
54}
55
56/// Deserialize from a JSON `&str`.
57pub fn from_string<T: DeserializeOwned>(s: &str) -> Result<T> {
58    serde_json::from_str(s).map_err(|e| CodecError::JsonDecode(e.to_string()))
59}
60
61#[cfg(test)]
62mod tests {
63    use super::*;
64    use serde::{Deserialize, Serialize};
65
66    #[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
67    struct TestData {
68        name: String,
69        count: u32,
70        items: Vec<String>,
71    }
72
73    #[test]
74    fn test_json_roundtrip() {
75        let original = TestData {
76            name: "test".to_string(),
77            count: 42,
78            items: vec!["a".to_string(), "b".to_string()],
79        };
80
81        let encoded = encode(&original).unwrap();
82        let decoded: TestData = decode(&encoded).unwrap();
83
84        assert_eq!(original, decoded);
85    }
86
87    #[test]
88    fn test_json_string_roundtrip() {
89        let original = TestData {
90            name: "string_test".to_string(),
91            count: 100,
92            items: vec!["x".to_string()],
93        };
94
95        let json_string = to_string(&original).unwrap();
96        let decoded: TestData = from_string(&json_string).unwrap();
97
98        assert_eq!(original, decoded);
99    }
100
101    #[test]
102    fn test_compact_vs_pretty() {
103        let data = TestData {
104            name: "compact".to_string(),
105            count: 1,
106            items: vec![],
107        };
108
109        let pretty = encode(&data).unwrap();
110        let compact = encode_compact(&data).unwrap();
111
112        assert!(compact.len() < pretty.len());
113
114        let decoded_pretty: TestData = decode(&pretty).unwrap();
115        let decoded_compact: TestData = decode(&compact).unwrap();
116
117        assert_eq!(decoded_pretty, decoded_compact);
118    }
119
120    #[test]
121    fn test_decode_invalid_json() {
122        let garbage = b"not valid json {{{";
123        let result: Result<TestData> = decode(garbage);
124        assert!(matches!(result, Err(CodecError::JsonDecode(_))));
125    }
126
127    #[test]
128    fn test_encode_to_decode_from_json() {
129        let original = TestData {
130            name: "stream".to_string(),
131            count: 55,
132            items: vec!["one".to_string(), "two".to_string()],
133        };
134
135        let mut buf = Vec::new();
136        encode_to(&original, &mut buf).unwrap();
137        let decoded: TestData = decode_from(&buf[..]).unwrap();
138        assert_eq!(original, decoded);
139    }
140
141    #[test]
142    fn test_encode_to_compact_writer() {
143        let data = TestData {
144            name: "cw".to_string(),
145            count: 0,
146            items: vec![],
147        };
148
149        let mut pretty_buf = Vec::new();
150        encode_to(&data, &mut pretty_buf).unwrap();
151
152        let mut compact_buf = Vec::new();
153        encode_to_compact(&data, &mut compact_buf).unwrap();
154
155        assert!(compact_buf.len() < pretty_buf.len());
156
157        let decoded: TestData = decode(&compact_buf).unwrap();
158        assert_eq!(data, decoded);
159    }
160
161    #[test]
162    fn test_from_string_invalid() {
163        let result: Result<TestData> = from_string("}{bad");
164        assert!(matches!(result, Err(CodecError::JsonDecode(_))));
165    }
166
167    #[test]
168    fn test_to_string_compact_roundtrip() {
169        let data = TestData {
170            name: "compact_str".to_string(),
171            count: 999,
172            items: vec!["z".to_string()],
173        };
174
175        let compact = to_string_compact(&data).unwrap();
176        assert!(!compact.contains('\n'));
177
178        let decoded: TestData = from_string(&compact).unwrap();
179        assert_eq!(data, decoded);
180    }
181}