1use serde::Serialize;
18
19use crate::print::Print;
20
21#[must_use]
35pub fn error_json(err: &(dyn std::error::Error + 'static), error_type: &str) -> serde_json::Value {
36 let mut current: Option<&(dyn std::error::Error + 'static)> = Some(err);
37 while let Some(source) = current {
38 if let Some(object) = source.downcast_ref::<jsonrpsee_types::ErrorObjectOwned>() {
39 if let Ok(serde_json::Value::Object(mut map)) = serde_json::to_value(object) {
40 map.insert("type".to_string(), serde_json::json!(error_type));
41 return serde_json::json!({ "error": map });
42 }
43 }
44 current = source.source();
45 }
46
47 serde_json::json!({ "error": { "type": error_type, "message": err.to_string() } })
48}
49
50#[derive(Clone, Copy, Debug, Eq, PartialEq)]
52pub enum Format {
53 Readable,
55 Json,
57 JsonFormatted,
59}
60
61impl Format {
62 #[must_use]
64 pub fn is_json(self) -> bool {
65 matches!(self, Format::Json | Format::JsonFormatted)
66 }
67}
68
69#[derive(Clone)]
72pub struct Output {
73 format: Format,
74 print: Print,
75}
76
77impl Output {
78 #[must_use]
81 pub fn new(format: Format, quiet: bool) -> Self {
82 Self {
83 format,
84 print: Print::new(quiet),
85 }
86 }
87
88 #[must_use]
90 pub fn is_json(&self) -> bool {
91 self.format.is_json()
92 }
93
94 #[must_use]
96 pub fn compact_json(&self) -> bool {
97 self.format == Format::Json
98 }
99
100 #[must_use]
106 pub fn print(&self) -> &Print {
107 &self.print
108 }
109
110 pub fn readable<F: FnOnce(&Print)>(&self, f: F) {
113 if !self.format.is_json() {
114 f(&self.print);
115 }
116 }
117
118 pub fn json<F: FnOnce(&Output)>(&self, f: F) {
122 if self.format.is_json() {
123 f(self);
124 }
125 }
126
127 pub fn json_value<T: Serialize>(&self, value: &T) -> Result<(), serde_json::Error> {
133 match self.format {
134 Format::Json => println!("{}", serde_json::to_string(value)?),
135 Format::JsonFormatted => println!("{}", serde_json::to_string_pretty(value)?),
136 Format::Readable => {}
137 }
138 Ok(())
139 }
140}
141
142#[cfg(test)]
143mod tests {
144 use super::*;
145 use std::cell::Cell;
146
147 #[test]
148 fn format_is_json() {
149 assert!(!Format::Readable.is_json());
150 assert!(Format::Json.is_json());
151 assert!(Format::JsonFormatted.is_json());
152 }
153
154 #[test]
155 fn readable_runs_only_in_readable_mode() {
156 for (format, expected) in [
157 (Format::Readable, true),
158 (Format::Json, false),
159 (Format::JsonFormatted, false),
160 ] {
161 let output = Output::new(format, false);
162 let ran = Cell::new(false);
163 output.readable(|_| ran.set(true));
164 assert_eq!(ran.get(), expected, "format: {format:?}");
165 }
166 }
167
168 #[test]
169 fn json_runs_only_in_json_mode() {
170 for (format, expected) in [
171 (Format::Readable, false),
172 (Format::Json, true),
173 (Format::JsonFormatted, true),
174 ] {
175 let output = Output::new(format, false);
176 let ran = Cell::new(false);
177 output.json(|_| ran.set(true));
178 assert_eq!(ran.get(), expected, "format: {format:?}");
179 }
180 }
181
182 #[test]
183 fn compact_json_only_for_compact_format() {
184 assert!(Output::new(Format::Json, false).compact_json());
185 assert!(!Output::new(Format::JsonFormatted, false).compact_json());
186 assert!(!Output::new(Format::Readable, false).compact_json());
187 }
188
189 #[test]
190 fn error_json_uses_structured_rpc_error_object() {
191 let obj = jsonrpsee_types::ErrorObject::owned(-32603, "DB is empty", None::<()>);
192 assert_eq!(
193 error_json(&obj, "unknown"),
194 serde_json::json!({ "error": { "type": "unknown", "code": -32603, "message": "DB is empty" } }),
195 );
196 }
197
198 #[test]
199 fn error_json_includes_specific_type() {
200 let err = std::io::Error::new(std::io::ErrorKind::NotFound, "no sac");
201 assert_eq!(
202 error_json(&err, "sac_not_deployed"),
203 serde_json::json!({ "error": { "type": "sac_not_deployed", "message": "no sac" } }),
204 );
205 }
206
207 #[test]
208 fn error_json_walks_the_source_chain() {
209 #[derive(Debug)]
210 struct Wrapper(jsonrpsee_types::ErrorObjectOwned);
211 impl std::fmt::Display for Wrapper {
212 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
213 write!(f, "wrapper")
214 }
215 }
216 impl std::error::Error for Wrapper {
217 fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
218 Some(&self.0)
219 }
220 }
221
222 let obj = jsonrpsee_types::ErrorObject::owned(-32000, "boom", None::<()>);
223 assert_eq!(
224 error_json(&Wrapper(obj), "unknown"),
225 serde_json::json!({ "error": { "type": "unknown", "code": -32000, "message": "boom" } }),
226 );
227 }
228
229 #[test]
230 fn error_json_falls_back_to_message_for_other_errors() {
231 let err = std::io::Error::new(std::io::ErrorKind::NotFound, "nope");
232 assert_eq!(
233 error_json(&err, "unknown"),
234 serde_json::json!({ "error": { "type": "unknown", "message": "nope" } }),
235 );
236 }
237}