1use rskit_errors::{AppError, AppResult};
2use serde_json::Value;
3
4use crate::codec::Codec;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
8#[non_exhaustive]
9pub enum JsonStyle {
10 #[default]
12 Pretty,
13 Compact,
16}
17
18#[derive(Debug, Clone, Copy, Default)]
24pub struct JsonCodec {
25 style: JsonStyle,
26}
27
28impl JsonCodec {
29 #[must_use]
31 pub const fn pretty() -> Self {
32 Self {
33 style: JsonStyle::Pretty,
34 }
35 }
36
37 #[must_use]
39 pub const fn compact() -> Self {
40 Self {
41 style: JsonStyle::Compact,
42 }
43 }
44}
45
46impl Codec for JsonCodec {
47 fn name(&self) -> &'static str {
48 "json"
49 }
50
51 fn encode_value(&self, value: &Value) -> AppResult<String> {
52 let result = match self.style {
53 JsonStyle::Pretty => serde_json::to_string_pretty(value),
54 JsonStyle::Compact => serde_json::to_string(value),
55 };
56 result.map_err(|err| {
57 AppError::invalid_input("codec", "failed to serialize value as JSON").with_cause(err)
58 })
59 }
60
61 fn decode_value(&self, contents: &str) -> AppResult<Value> {
62 serde_json::from_str(contents)
63 .map_err(|err| AppError::invalid_input("codec", "failed to parse JSON").with_cause(err))
64 }
65}
66
67#[cfg(test)]
68mod tests {
69 use super::*;
70
71 #[test]
72 fn round_trips_value() {
73 let codec = JsonCodec::default();
74 let value: Value = serde_json::json!({ "a": 1, "nested": { "b": true } });
75
76 let encoded = codec.encode_value(&value).unwrap();
77 let decoded = codec.decode_value(&encoded).unwrap();
78
79 assert_eq!(decoded, value);
80 assert_eq!(codec.name(), "json");
81 }
82
83 #[test]
84 fn pretty_is_multiline_compact_is_single_line() {
85 let value: Value = serde_json::json!({ "a": 1, "b": 2 });
86 assert!(
87 JsonCodec::pretty()
88 .encode_value(&value)
89 .unwrap()
90 .contains('\n')
91 );
92 assert!(
93 !JsonCodec::compact()
94 .encode_value(&value)
95 .unwrap()
96 .contains('\n')
97 );
98 }
99
100 #[test]
101 fn rejects_malformed_input() {
102 let err = JsonCodec::default().decode_value("{ not json").unwrap_err();
103 assert!(err.to_string().contains("parse"));
104 }
105}