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}