use serde::Serialize;
use crate::print::Print;
#[must_use]
pub fn error_json(err: &(dyn std::error::Error + 'static), error_type: &str) -> serde_json::Value {
let mut current: Option<&(dyn std::error::Error + 'static)> = Some(err);
while let Some(source) = current {
if let Some(object) = source.downcast_ref::<jsonrpsee_types::ErrorObjectOwned>() {
if let Ok(serde_json::Value::Object(mut map)) = serde_json::to_value(object) {
map.insert("type".to_string(), serde_json::json!(error_type));
return serde_json::json!({ "error": map });
}
}
current = source.source();
}
serde_json::json!({ "error": { "type": error_type, "message": err.to_string() } })
}
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
pub enum Format {
Readable,
Json,
JsonFormatted,
}
impl Format {
#[must_use]
pub fn is_json(self) -> bool {
matches!(self, Format::Json | Format::JsonFormatted)
}
}
#[derive(Clone)]
pub struct Output {
format: Format,
print: Print,
}
impl Output {
#[must_use]
pub fn new(format: Format, quiet: bool) -> Self {
Self {
format,
print: Print::new(quiet),
}
}
#[must_use]
pub fn is_json(&self) -> bool {
self.format.is_json()
}
#[must_use]
pub fn compact_json(&self) -> bool {
self.format == Format::Json
}
#[must_use]
pub fn print(&self) -> &Print {
&self.print
}
pub fn readable<F: FnOnce(&Print)>(&self, f: F) {
if !self.format.is_json() {
f(&self.print);
}
}
pub fn json<F: FnOnce(&Output)>(&self, f: F) {
if self.format.is_json() {
f(self);
}
}
pub fn json_value<T: Serialize>(&self, value: &T) -> Result<(), serde_json::Error> {
match self.format {
Format::Json => println!("{}", serde_json::to_string(value)?),
Format::JsonFormatted => println!("{}", serde_json::to_string_pretty(value)?),
Format::Readable => {}
}
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use std::cell::Cell;
#[test]
fn format_is_json() {
assert!(!Format::Readable.is_json());
assert!(Format::Json.is_json());
assert!(Format::JsonFormatted.is_json());
}
#[test]
fn readable_runs_only_in_readable_mode() {
for (format, expected) in [
(Format::Readable, true),
(Format::Json, false),
(Format::JsonFormatted, false),
] {
let output = Output::new(format, false);
let ran = Cell::new(false);
output.readable(|_| ran.set(true));
assert_eq!(ran.get(), expected, "format: {format:?}");
}
}
#[test]
fn json_runs_only_in_json_mode() {
for (format, expected) in [
(Format::Readable, false),
(Format::Json, true),
(Format::JsonFormatted, true),
] {
let output = Output::new(format, false);
let ran = Cell::new(false);
output.json(|_| ran.set(true));
assert_eq!(ran.get(), expected, "format: {format:?}");
}
}
#[test]
fn compact_json_only_for_compact_format() {
assert!(Output::new(Format::Json, false).compact_json());
assert!(!Output::new(Format::JsonFormatted, false).compact_json());
assert!(!Output::new(Format::Readable, false).compact_json());
}
#[test]
fn error_json_uses_structured_rpc_error_object() {
let obj = jsonrpsee_types::ErrorObject::owned(-32603, "DB is empty", None::<()>);
assert_eq!(
error_json(&obj, "unknown"),
serde_json::json!({ "error": { "type": "unknown", "code": -32603, "message": "DB is empty" } }),
);
}
#[test]
fn error_json_includes_specific_type() {
let err = std::io::Error::new(std::io::ErrorKind::NotFound, "no sac");
assert_eq!(
error_json(&err, "sac_not_deployed"),
serde_json::json!({ "error": { "type": "sac_not_deployed", "message": "no sac" } }),
);
}
#[test]
fn error_json_walks_the_source_chain() {
#[derive(Debug)]
struct Wrapper(jsonrpsee_types::ErrorObjectOwned);
impl std::fmt::Display for Wrapper {
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
write!(f, "wrapper")
}
}
impl std::error::Error for Wrapper {
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
Some(&self.0)
}
}
let obj = jsonrpsee_types::ErrorObject::owned(-32000, "boom", None::<()>);
assert_eq!(
error_json(&Wrapper(obj), "unknown"),
serde_json::json!({ "error": { "type": "unknown", "code": -32000, "message": "boom" } }),
);
}
#[test]
fn error_json_falls_back_to_message_for_other_errors() {
let err = std::io::Error::new(std::io::ErrorKind::NotFound, "nope");
assert_eq!(
error_json(&err, "unknown"),
serde_json::json!({ "error": { "type": "unknown", "message": "nope" } }),
);
}
}