Skip to main content

toon/
options.rs

1use std::sync::Arc;
2
3use crate::JsonValue;
4use crate::shared::constants::DEFAULT_DELIMITER;
5
6pub type EncodeReplacer =
7    Arc<dyn Fn(&str, &JsonValue, &[PathSegment]) -> Option<JsonValue> + Send + Sync>;
8
9#[derive(Clone)]
10pub struct EncodeOptions {
11    pub indent: Option<usize>,
12    pub delimiter: Option<char>,
13    pub key_folding: Option<KeyFoldingMode>,
14    pub flatten_depth: Option<usize>,
15    pub replacer: Option<EncodeReplacer>,
16}
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq)]
19pub enum KeyFoldingMode {
20    Off,
21    Safe,
22}
23
24#[derive(Debug, Clone)]
25pub struct DecodeOptions {
26    pub indent: Option<usize>,
27    pub strict: Option<bool>,
28    pub expand_paths: Option<ExpandPathsMode>,
29}
30
31#[derive(Debug, Clone, Copy, PartialEq, Eq)]
32pub enum ExpandPathsMode {
33    Off,
34    Safe,
35}
36
37#[derive(Debug, Clone, Default)]
38pub struct DecodeStreamOptions {
39    pub indent: Option<usize>,
40    pub strict: Option<bool>,
41}
42
43#[derive(Clone)]
44pub struct ResolvedEncodeOptions {
45    pub indent: usize,
46    pub delimiter: char,
47    pub key_folding: KeyFoldingMode,
48    pub flatten_depth: usize,
49    pub replacer: Option<EncodeReplacer>,
50}
51
52#[derive(Debug, Clone)]
53pub struct ResolvedDecodeOptions {
54    pub indent: usize,
55    pub strict: bool,
56    pub expand_paths: ExpandPathsMode,
57}
58
59#[derive(Debug, Clone, PartialEq, Eq)]
60pub enum PathSegment {
61    Key(String),
62    Index(usize),
63}
64
65#[must_use]
66pub fn resolve_encode_options(options: Option<EncodeOptions>) -> ResolvedEncodeOptions {
67    let options = options.unwrap_or(EncodeOptions {
68        indent: None,
69        delimiter: None,
70        key_folding: None,
71        flatten_depth: None,
72        replacer: None,
73    });
74
75    ResolvedEncodeOptions {
76        indent: options.indent.unwrap_or(2),
77        delimiter: options.delimiter.unwrap_or(DEFAULT_DELIMITER),
78        key_folding: options.key_folding.unwrap_or(KeyFoldingMode::Off),
79        flatten_depth: options.flatten_depth.unwrap_or(usize::MAX),
80        replacer: options.replacer,
81    }
82}
83
84#[must_use]
85pub fn resolve_decode_options(options: Option<DecodeOptions>) -> ResolvedDecodeOptions {
86    let options = options.unwrap_or(DecodeOptions {
87        indent: None,
88        strict: None,
89        expand_paths: None,
90    });
91
92    ResolvedDecodeOptions {
93        indent: options.indent.unwrap_or(2),
94        strict: options.strict.unwrap_or(true),
95        expand_paths: options.expand_paths.unwrap_or(ExpandPathsMode::Off),
96    }
97}
98
99#[cfg(test)]
100mod tests {
101    use super::*;
102
103    #[test]
104    fn resolve_encode_defaults_when_none() {
105        let r = resolve_encode_options(None);
106        assert_eq!(r.indent, 2);
107        assert_eq!(r.delimiter, DEFAULT_DELIMITER);
108        assert_eq!(r.key_folding, KeyFoldingMode::Off);
109        assert_eq!(r.flatten_depth, usize::MAX);
110        assert!(r.replacer.is_none());
111    }
112
113    #[test]
114    fn resolve_encode_uses_overrides() {
115        let r = resolve_encode_options(Some(EncodeOptions {
116            indent: Some(4),
117            delimiter: Some('|'),
118            key_folding: Some(KeyFoldingMode::Safe),
119            flatten_depth: Some(3),
120            replacer: None,
121        }));
122        assert_eq!(r.indent, 4);
123        assert_eq!(r.delimiter, '|');
124        assert_eq!(r.key_folding, KeyFoldingMode::Safe);
125        assert_eq!(r.flatten_depth, 3);
126    }
127
128    #[test]
129    fn resolve_encode_partial_overrides() {
130        let r = resolve_encode_options(Some(EncodeOptions {
131            indent: Some(0),
132            delimiter: None,
133            key_folding: None,
134            flatten_depth: None,
135            replacer: None,
136        }));
137        assert_eq!(r.indent, 0);
138        assert_eq!(r.delimiter, DEFAULT_DELIMITER);
139        assert_eq!(r.flatten_depth, usize::MAX);
140    }
141
142    #[test]
143    fn resolve_decode_defaults_when_none() {
144        let r = resolve_decode_options(None);
145        assert_eq!(r.indent, 2);
146        assert!(r.strict);
147        assert_eq!(r.expand_paths, ExpandPathsMode::Off);
148    }
149
150    #[test]
151    fn resolve_decode_uses_overrides() {
152        let r = resolve_decode_options(Some(DecodeOptions {
153            indent: Some(4),
154            strict: Some(false),
155            expand_paths: Some(ExpandPathsMode::Safe),
156        }));
157        assert_eq!(r.indent, 4);
158        assert!(!r.strict);
159        assert_eq!(r.expand_paths, ExpandPathsMode::Safe);
160    }
161
162    #[test]
163    fn resolve_decode_explicit_true_strict() {
164        let r = resolve_decode_options(Some(DecodeOptions {
165            indent: None,
166            strict: Some(true),
167            expand_paths: None,
168        }));
169        assert!(r.strict);
170    }
171
172    #[test]
173    fn key_folding_equality_and_copy() {
174        let a = KeyFoldingMode::Off;
175        let b = a;
176        assert_eq!(a, b);
177        assert_ne!(KeyFoldingMode::Off, KeyFoldingMode::Safe);
178    }
179
180    #[test]
181    fn expand_paths_equality_and_copy() {
182        let a = ExpandPathsMode::Safe;
183        let b = a;
184        assert_eq!(a, b);
185        assert_ne!(ExpandPathsMode::Off, ExpandPathsMode::Safe);
186    }
187
188    #[test]
189    fn path_segment_equality() {
190        assert_eq!(
191            PathSegment::Key("a".to_string()),
192            PathSegment::Key("a".to_string())
193        );
194        assert_eq!(PathSegment::Index(3), PathSegment::Index(3));
195        assert_ne!(PathSegment::Index(1), PathSegment::Index(2));
196        assert_ne!(PathSegment::Key("a".into()), PathSegment::Key("b".into()));
197    }
198
199    #[test]
200    fn decode_stream_options_default() {
201        let d = DecodeStreamOptions::default();
202        assert!(d.indent.is_none());
203        assert!(d.strict.is_none());
204    }
205
206    #[test]
207    fn resolve_encode_replacer_threads_through() {
208        use crate::JsonValue;
209        use std::sync::Arc;
210        let replacer: EncodeReplacer = Arc::new(|_key, value, _path| Some(value.clone()));
211        let r = resolve_encode_options(Some(EncodeOptions {
212            indent: None,
213            delimiter: None,
214            key_folding: None,
215            flatten_depth: None,
216            replacer: Some(replacer.clone()),
217        }));
218        assert!(r.replacer.is_some());
219        // Calling the replacer on a primitive returns a value.
220        let out = (r.replacer.as_ref().unwrap())("k", &JsonValue::from(1i64), &[]);
221        assert!(out.is_some());
222    }
223}