1use std::io::{self, Write as _};
4
5use anstream::stream::{AsLockedWrite, RawStream};
6use anstyle::{AnsiColor, Color, Style};
7use smart_config::value::{StrValue, Value, WithOrigin};
8
9use crate::Printer;
10
11pub(crate) const STRING: Style = Style::new().fg_color(Some(Color::Ansi(AnsiColor::Cyan)));
12pub(crate) const NULL: Style = Style::new().bold();
13const BOOL: Style = Style::new().fg_color(Some(Color::Ansi(AnsiColor::Yellow)));
14const NUMBER: Style = Style::new().fg_color(Some(Color::Ansi(AnsiColor::Green)));
15const SECRET: Style = Style::new()
16 .bg_color(Some(Color::Ansi(AnsiColor::Cyan)))
17 .fg_color(None);
18const OBJECT_KEY: Style = Style::new().bold();
19
20impl<W: RawStream + AsLockedWrite> Printer<W> {
21 pub fn print_json(&mut self, json: &serde_json::Value) -> io::Result<()> {
27 write_json_value(&mut self.writer, json, 0)?;
28 writeln!(&mut self.writer)
29 }
30
31 pub fn print_yaml(&mut self, json: &serde_json::Value) -> io::Result<()> {
37 write_yaml_value(&mut self.writer, json, 0, false)?;
38 writeln!(&mut self.writer)
39 }
40}
41
42pub(crate) fn write_json_value(
43 writer: &mut impl io::Write,
44 value: &serde_json::Value,
45 ident: usize,
46) -> io::Result<()> {
47 match value {
48 serde_json::Value::Null => write!(writer, "{NULL}null{NULL:#}"),
49 serde_json::Value::Bool(val) => write!(writer, "{BOOL}{val:?}{BOOL:#}"),
50 serde_json::Value::Number(val) => write!(writer, "{NUMBER}{val}{NUMBER:#}"),
51 serde_json::Value::String(val) => write!(writer, "{STRING}{val:?}{STRING:#}"),
52 serde_json::Value::Array(val) => {
53 if val.is_empty() {
54 write!(writer, "[]")
55 } else {
56 writeln!(writer, "[")?;
57 for (i, item) in val.iter().enumerate() {
58 write!(writer, "{:ident$} ", "")?;
59 write_json_value(writer, item, ident + 2)?;
60 if i + 1 < val.len() {
61 writeln!(writer, ",")?;
62 } else {
63 writeln!(writer)?;
64 }
65 }
66 write!(writer, "{:ident$}]", "")
67 }
68 }
69 serde_json::Value::Object(val) => {
70 if val.is_empty() {
71 write!(writer, "{{}}")
72 } else {
73 writeln!(writer, "{{")?;
74 for (i, (key, value)) in val.iter().enumerate() {
75 write!(writer, "{:ident$} {OBJECT_KEY}{key:?}{OBJECT_KEY:#}: ", "")?;
76 write_json_value(writer, value, ident + 2)?;
77 if i + 1 < val.len() {
78 writeln!(writer, ",")?;
79 } else {
80 writeln!(writer)?;
81 }
82 }
83 write!(writer, "{:ident$}}}", "")
84 }
85 }
86 }
87}
88
89fn yaml_string(val: &str) -> String {
90 let mut yaml = serde_yaml::to_string(val).unwrap();
92 if yaml.ends_with('\n') {
93 yaml.pop();
94 }
95 yaml
96}
97
98fn write_yaml_value(
99 writer: &mut impl io::Write,
100 value: &serde_json::Value,
101 ident: usize,
102 is_array_item: bool,
103) -> io::Result<()> {
104 match value {
105 serde_json::Value::Null => write!(writer, "{NULL}null{NULL:#}"),
106 serde_json::Value::Bool(val) => write!(writer, "{BOOL}{val:?}{BOOL:#}"),
107 serde_json::Value::Number(val) => write!(writer, "{NUMBER}{val}{NUMBER:#}"),
108 serde_json::Value::String(val) => {
109 let yaml_val = yaml_string(val);
110 write!(writer, "{STRING}{yaml_val}{STRING:#}")
111 }
112 serde_json::Value::Array(val) => {
113 if val.is_empty() {
114 if ident > 0 {
115 write!(writer, " ")?; }
117 write!(writer, "[]")
118 } else {
119 if ident > 0 {
120 writeln!(writer)?;
121 }
122 for (i, item) in val.iter().enumerate() {
123 write!(writer, "{:ident$}-", "")?;
124 if !item.is_array() {
125 write!(writer, " ")?; }
127
128 write_yaml_value(writer, item, ident + 2, true)?;
129 if i + 1 < val.len() {
130 writeln!(writer)?;
131 }
132 }
133 Ok(())
134 }
135 }
136 serde_json::Value::Object(val) => {
137 if val.is_empty() {
138 if ident > 0 {
139 write!(writer, " ")?; }
141 write!(writer, "{{}}")
142 } else {
143 if ident > 0 && !is_array_item {
144 writeln!(writer)?;
145 }
146 for (i, (key, value)) in val.iter().enumerate() {
147 let yaml_key = yaml_string(key);
148 if is_array_item && i == 0 {
149 } else {
151 write!(writer, "{:ident$}", "")?;
152 }
153 write!(writer, "{OBJECT_KEY}{yaml_key}{OBJECT_KEY:#}:")?;
154 if !value.is_object() && !value.is_array() {
155 write!(writer, " ")?; }
157
158 write_yaml_value(writer, value, ident + 2, false)?;
159 if i + 1 < val.len() {
160 writeln!(writer)?;
161 }
162 }
163 Ok(())
164 }
165 }
166 }
167}
168
169pub(crate) fn write_value(
170 writer: &mut impl io::Write,
171 value: &WithOrigin,
172 ident: usize,
173) -> io::Result<()> {
174 match &value.inner {
175 Value::Null => write!(writer, "{NULL}null{NULL:#}"),
176 Value::Bool(val) => write!(writer, "{BOOL}{val:?}{BOOL:#}"),
177 Value::Number(val) => write!(writer, "{NUMBER}{val}{NUMBER:#}"),
178 Value::String(StrValue::Plain(val)) => write!(writer, "{STRING}{val:?}{STRING:#}"),
179 Value::String(StrValue::Secret(_)) => write!(writer, "{SECRET}[REDACTED]{SECRET:#}"),
180 Value::Array(val) => {
181 writeln!(writer, "[")?;
182 for item in val {
183 write!(writer, "{:ident$} ", "")?;
184 write_value(writer, item, ident + 2)?;
185 writeln!(writer, ",")?;
186 }
187 write!(writer, "{:ident$}]", "")
188 }
189 Value::Object(val) => {
190 writeln!(writer, "{{")?;
191 for (key, value) in val {
192 write!(writer, "{:ident$} {OBJECT_KEY}{key:?}{OBJECT_KEY:#}: ", "")?;
193 write_value(writer, value, ident + 2)?;
194 writeln!(writer, ",")?;
195 }
196 write!(writer, "{:ident$}}}", "")
197 }
198 }
199}
200
201#[cfg(test)]
202mod tests {
203 use anstream::AutoStream;
204
205 use crate::utils::write_yaml_value;
206
207 #[test]
208 fn writing_yaml() {
209 let original_yaml = "\
210test:
211 app_name: test
212 cache_size: 128 MiB
213 dir_paths:
214 - /usr/local/bin
215 - /usr/bin
216 funding:
217 address: '0x0000000000000000000000000000000000001234'
218 api_key: correct horse battery staple
219 balance: '0x123456'
220 secret_key: 0x000102030405060708090a0b0c0d0e0f000102030405060708090a0b0c0d0e0f
221 nested:
222 complex:
223 array:
224 - 1
225 - 2
226 map:
227 value: 25
228 exit_on_error: false
229 method_limits:
230 - method: eth_blockNumber
231 rps: 3
232 - method: eth_getLogs
233 rps: 100
234 more_timeouts: []
235 object_store:
236 bucket_name: test-bucket
237 type: gcs
238 poll_latency: 300ms
239 port: 3000
240 required: 123
241 scaling_factor: 4.199999809265137
242 temp_dir: /var/folders/mw/lhb7m9dj3jbdm3w994t0_c8h0000gn/T/
243 timeout_sec: 60";
244 let json: serde_json::Value = serde_yaml::from_str(original_yaml).unwrap();
245 assert!(json["test"].as_object().unwrap().len() > 10, "{json:?}");
246
247 let mut buffer = vec![];
248 write_yaml_value(&mut AutoStream::never(&mut buffer), &json, 0, false).unwrap();
249 let produced_yaml = String::from_utf8(buffer).unwrap();
250 assert_eq!(produced_yaml, original_yaml);
251 }
252}