Skip to main content

optionstratlib_core/utils/
json_format.rs

1//! JSON-backed `Debug` and `Display` implementations.
2//!
3//! Many result and strategy types format themselves as their own JSON:
4//! `Display` writes the compact form (`{"a":1}`) and `Debug` the indented
5//! one. The two writers below are the only implementation of that
6//! convention, and [`impl_json_display!`](crate::impl_json_display),
7//! [`impl_json_debug!`](crate::impl_json_debug) and
8//! [`impl_json_debug_pretty!`](crate::impl_json_debug_pretty) wire a type to
9//! them. They replace the `pretty-simple-display` derive macros, so no crate
10//! of the workspace depends on a formatting crate to print its types
11//! (M6-05, #546).
12//!
13//! A value that fails to serialize does not abort formatting: the writers
14//! print `Error serializing to JSON: <reason>` in place of the JSON, so
15//! `to_string()` and `{:?}` never panic on such a value.
16//!
17//! ```rust
18//! use optionstratlib_core::{impl_json_debug_pretty, impl_json_display};
19//! use serde::Serialize;
20//!
21//! #[derive(Serialize)]
22//! struct Quote {
23//!     bid: u32,
24//!     ask: u32,
25//! }
26//! impl_json_display!(Quote);
27//! impl_json_debug_pretty!(Quote);
28//!
29//! let quote = Quote { bid: 1, ask: 2 };
30//! assert_eq!(quote.to_string(), r#"{"bid":1,"ask":2}"#);
31//! assert_eq!(format!("{quote:?}"), "{\n  \"bid\": 1,\n  \"ask\": 2\n}");
32//! ```
33
34use serde::Serialize;
35use std::fmt;
36
37/// Writes `value` to `f` as compact JSON.
38///
39/// When `value` cannot be serialized, writes
40/// `Error serializing to JSON: <reason>` instead.
41///
42/// # Errors
43///
44/// Returns [`fmt::Error`] only when the formatter itself fails to write.
45#[inline]
46pub fn write_json<T: Serialize + ?Sized>(value: &T, f: &mut fmt::Formatter<'_>) -> fmt::Result {
47    match serde_json::to_string(value) {
48        Ok(json) => f.write_str(&json),
49        Err(e) => write!(f, "Error serializing to JSON: {e}"),
50    }
51}
52
53/// Writes `value` to `f` as indented JSON (two spaces per level).
54///
55/// When `value` cannot be serialized, writes
56/// `Error serializing to JSON: <reason>` instead.
57///
58/// # Errors
59///
60/// Returns [`fmt::Error`] only when the formatter itself fails to write.
61#[inline]
62pub fn write_json_pretty<T: Serialize + ?Sized>(
63    value: &T,
64    f: &mut fmt::Formatter<'_>,
65) -> fmt::Result {
66    match serde_json::to_string_pretty(value) {
67        Ok(json) => f.write_str(&json),
68        Err(e) => write!(f, "Error serializing to JSON: {e}"),
69    }
70}
71
72/// Implements `Display` as compact JSON for one or more `Serialize` types.
73///
74/// Expands to an `impl std::fmt::Display` per type that delegates to
75/// [`write_json`](crate::utils::json_format::write_json).
76///
77/// ```rust
78/// use optionstratlib_core::impl_json_display;
79/// use serde::Serialize;
80///
81/// #[derive(Serialize)]
82/// struct Fill {
83///     qty: u32,
84/// }
85/// impl_json_display!(Fill);
86///
87/// assert_eq!(Fill { qty: 3 }.to_string(), r#"{"qty":3}"#);
88/// ```
89#[macro_export]
90macro_rules! impl_json_display {
91    ($($ty:ty),+ $(,)?) => {
92        $(
93            impl ::std::fmt::Display for $ty {
94                fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
95                    $crate::utils::json_format::write_json(self, f)
96                }
97            }
98        )+
99    };
100}
101
102/// Implements `Debug` as compact JSON for one or more `Serialize` types.
103///
104/// Expands to an `impl std::fmt::Debug` per type that delegates to
105/// [`write_json`](crate::utils::json_format::write_json).
106///
107/// ```rust
108/// use optionstratlib_core::impl_json_debug;
109/// use serde::Serialize;
110///
111/// #[derive(Serialize)]
112/// struct Fill {
113///     qty: u32,
114/// }
115/// impl_json_debug!(Fill);
116///
117/// assert_eq!(format!("{:?}", Fill { qty: 3 }), r#"{"qty":3}"#);
118/// ```
119#[macro_export]
120macro_rules! impl_json_debug {
121    ($($ty:ty),+ $(,)?) => {
122        $(
123            impl ::std::fmt::Debug for $ty {
124                fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
125                    $crate::utils::json_format::write_json(self, f)
126                }
127            }
128        )+
129    };
130}
131
132/// Implements `Debug` as indented JSON for one or more `Serialize` types.
133///
134/// Expands to an `impl std::fmt::Debug` per type that delegates to
135/// [`write_json_pretty`](crate::utils::json_format::write_json_pretty).
136///
137/// ```rust
138/// use optionstratlib_core::impl_json_debug_pretty;
139/// use serde::Serialize;
140///
141/// #[derive(Serialize)]
142/// struct Fill {
143///     qty: u32,
144/// }
145/// impl_json_debug_pretty!(Fill);
146///
147/// assert_eq!(format!("{:?}", Fill { qty: 3 }), "{\n  \"qty\": 3\n}");
148/// ```
149#[macro_export]
150macro_rules! impl_json_debug_pretty {
151    ($($ty:ty),+ $(,)?) => {
152        $(
153            impl ::std::fmt::Debug for $ty {
154                fn fmt(&self, f: &mut ::std::fmt::Formatter<'_>) -> ::std::fmt::Result {
155                    $crate::utils::json_format::write_json_pretty(self, f)
156                }
157            }
158        )+
159    };
160}
161
162#[cfg(test)]
163mod tests {
164    use serde::ser::Error as _;
165    use serde::{Serialize, Serializer};
166
167    #[derive(Serialize)]
168    struct Sample {
169        name: &'static str,
170        value: u32,
171    }
172    crate::impl_json_display!(Sample);
173    crate::impl_json_debug_pretty!(Sample);
174
175    #[derive(Serialize)]
176    struct Compact {
177        flag: bool,
178    }
179    crate::impl_json_debug!(Compact);
180
181    /// A value whose serialization always fails.
182    struct Unserializable;
183
184    impl Serialize for Unserializable {
185        fn serialize<S: Serializer>(&self, _serializer: S) -> Result<S::Ok, S::Error> {
186            Err(S::Error::custom("refused"))
187        }
188    }
189    crate::impl_json_display!(Unserializable);
190    crate::impl_json_debug!(Unserializable);
191
192    #[test]
193    fn test_json_display_sample_writes_compact_json() {
194        let sample = Sample {
195            name: "a",
196            value: 1,
197        };
198        assert_eq!(sample.to_string(), r#"{"name":"a","value":1}"#);
199    }
200
201    #[test]
202    fn test_json_debug_pretty_sample_writes_indented_json() {
203        let sample = Sample {
204            name: "a",
205            value: 1,
206        };
207        assert_eq!(
208            format!("{sample:?}"),
209            "{\n  \"name\": \"a\",\n  \"value\": 1\n}"
210        );
211    }
212
213    #[test]
214    fn test_json_debug_compact_writes_compact_json() {
215        assert_eq!(format!("{:?}", Compact { flag: true }), r#"{"flag":true}"#);
216    }
217
218    #[test]
219    fn test_json_display_serialization_failure_writes_the_reason() {
220        assert_eq!(
221            Unserializable.to_string(),
222            "Error serializing to JSON: refused"
223        );
224        assert_eq!(
225            format!("{Unserializable:?}"),
226            "Error serializing to JSON: refused"
227        );
228    }
229}