From a5196a7a6c13237b05314f825c90359811691496 Mon Sep 17 00:00:00 2001 From: Tushar Mathur Date: Thu, 11 Apr 2024 17:04:45 +0530 Subject: [PATCH] refactor: move color coding logic into the formatter --- src/cli/error.rs | 14 ++++++ src/cli/mod.rs | 14 +----- src/cli/telemetry.rs | 2 +- src/error.rs | 116 ++++++++++++++++++++++--------------------- src/main.rs | 2 +- 5 files changed, 77 insertions(+), 71 deletions(-) create mode 100644 src/cli/error.rs diff --git a/src/cli/error.rs b/src/cli/error.rs new file mode 100644 index 0000000000..4b1836f5f0 --- /dev/null +++ b/src/cli/error.rs @@ -0,0 +1,14 @@ +use std::fmt::Display; + +use crate::error::Error; +impl From for Error { + fn from(error: rustls::Error) -> Self { + Error::new("TLS Error").description(error.to_string()) + } +} + +impl Display for crate::error::Error { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + self.pretty_print(f, true) + } +} diff --git a/src/cli/mod.rs b/src/cli/mod.rs index 38ce70268a..bfc2e6d121 100644 --- a/src/cli/mod.rs +++ b/src/cli/mod.rs @@ -1,24 +1,14 @@ mod command; +pub mod error; mod fmt; #[cfg(feature = "js")] pub mod javascript; pub mod metrics; +pub mod runtime; pub mod server; mod tc; pub mod telemetry; - -pub mod runtime; pub(crate) mod update_checker; pub use tc::run; - -use crate::error::Error; -impl From for Error { - fn from(error: rustls::Error) -> Self { - let cli_error = Error::new("Failed to create TLS Acceptor"); - let message = error.to_string(); - - cli_error.description(message) - } -} diff --git a/src/cli/telemetry.rs b/src/cli/telemetry.rs index 0244ea01f1..20c975a619 100644 --- a/src/cli/telemetry.rs +++ b/src/cli/telemetry.rs @@ -210,7 +210,7 @@ pub fn init_opentelemetry(config: Telemetry, runtime: &TargetRuntime) -> anyhow: let cli = crate::error::Error::new("Open Telemetry Error") .caused_by(vec![Error::new(error.to_string().as_str())]) .trace(vec!["schema".to_string(), "@telemetry".to_string()]); - tracing::error!("{}", cli.color(true)); + tracing::error!("{}", cli); }); } })?; diff --git a/src/error.rs b/src/error.rs index 056c45bb92..1fab6bf350 100644 --- a/src/error.rs +++ b/src/error.rs @@ -1,4 +1,4 @@ -use std::fmt::{Debug, Display}; +use std::fmt::Debug; use colored::Colorize; use derive_setters::Setters; @@ -6,13 +6,12 @@ use thiserror::Error; use crate::valid::ValidationError; -// FIXME: Rename to Error /// A versatile error container that's optimized for CLI and Web. +/// NOTE: For each target you will need implement Display and then call +/// pretty_print with the required configurations. #[derive(Debug, Error, Setters, PartialEq, Clone)] pub struct Error { is_root: bool, - #[setters(skip)] - color: bool, message: String, #[setters(strip_option)] description: Option, @@ -26,7 +25,6 @@ impl Error { pub fn new(message: &str) -> Self { Error { is_root: true, - color: false, message: message.to_string(), description: Default::default(), trace: Default::default(), @@ -44,48 +42,13 @@ impl Error { self } - fn colored<'a>(&'a self, str: &'a str, color: colored::Color) -> String { - if self.color { - str.color(color).to_string() - } else { - str.to_string() - } - } - - fn dimmed<'a>(&'a self, str: &'a str) -> String { - if self.color { - str.dimmed().to_string() - } else { - str.to_string() - } - } - - pub fn color(mut self, color: bool) -> Self { - self.color = color; - for inner in self.caused_by.iter_mut() { - inner.color = color; - } - self - } -} - -fn margin(str: &str, margin: usize) -> String { - let mut result = String::new(); - for line in str.split_inclusive('\n') { - result.push_str(&format!("{}{}", " ".repeat(margin), line)); - } - result -} - -fn bullet(str: &str) -> String { - let mut chars = margin(str, 2).chars().collect::>(); - chars[0] = '•'; - chars[1] = ' '; - chars.into_iter().collect::() -} + pub fn pretty_print( + &self, + f: &mut std::fmt::Formatter<'_>, + use_color: bool, + ) -> std::fmt::Result { + let p = Printer { use_color }; -impl Display for Error { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let default_padding = 2; let message_color = if self.is_root { @@ -94,11 +57,11 @@ impl Display for Error { colored::Color::White }; - f.write_str(self.colored(&self.message, message_color).as_str())?; + f.write_str(p.colored(&self.message, message_color).as_str())?; if let Some(description) = &self.description { - f.write_str(&self.colored(": ", message_color))?; - f.write_str(&self.colored(description.to_string().as_str(), colored::Color::White))?; + f.write_str(&p.colored(": ", message_color))?; + f.write_str(&p.colored(description.to_string().as_str(), colored::Color::White))?; } if !self.trace.is_empty() { @@ -112,17 +75,17 @@ impl Display for Error { } } buf.push(']'); - f.write_str(&self.colored(&buf, colored::Color::Cyan))?; + f.write_str(&p.colored(&buf, colored::Color::Cyan))?; } if !self.caused_by.is_empty() { f.write_str("\n")?; - f.write_str(self.dimmed("Caused by:").as_str())?; + f.write_str(p.dimmed("Caused by:").as_str())?; f.write_str("\n")?; for (i, error) in self.caused_by.iter().enumerate() { let message = &error.to_string(); - f.write_str(&margin(bullet(message.as_str()).as_str(), default_padding))?; + f.write_str(&p.margin(p.bullet(message.as_str()).as_str(), default_padding))?; if i < self.caused_by.len() - 1 { f.write_str("\n")?; @@ -134,6 +97,43 @@ impl Display for Error { } } +struct Printer { + use_color: bool, +} + +impl Printer { + fn colored(&self, str: &str, color: colored::Color) -> String { + if self.use_color { + str.color(color).to_string() + } else { + str.to_string() + } + } + + fn dimmed(&self, str: &str) -> String { + if self.use_color { + str.dimmed().to_string() + } else { + str.to_string() + } + } + + fn margin(&self, str: &str, margin: usize) -> String { + let mut result = String::new(); + for line in str.split_inclusive('\n') { + result.push_str(&format!("{}{}", " ".repeat(margin), line)); + } + result + } + + fn bullet(&self, str: &str) -> String { + let mut chars = self.margin(str, 2).chars().collect::>(); + chars[0] = '•'; + chars[1] = ' '; + chars.into_iter().collect::() + } +} + impl From for Error { fn from(error: hyper::Error) -> Self { // TODO: add type-safety to CLIError conversion @@ -230,39 +230,41 @@ mod tests { use super::*; use crate::valid::Cause; + const P: Printer = Printer { use_color: false }; + #[test] fn test_no_newline() { let input = "Hello"; let expected = " Hello"; - assert_eq!(margin(input, 4), expected); + assert_eq!(P.margin(input, 4), expected); } #[test] fn test_with_newline() { let input = "Hello\nWorld"; let expected = " Hello\n World"; - assert_eq!(margin(input, 4), expected); + assert_eq!(P.margin(input, 4), expected); } #[test] fn test_empty_string() { let input = ""; let expected = ""; - assert_eq!(margin(input, 4), expected); + assert_eq!(P.margin(input, 4), expected); } #[test] fn test_zero_margin() { let input = "Hello"; let expected = "Hello"; - assert_eq!(margin(input, 0), expected); + assert_eq!(P.margin(input, 0), expected); } #[test] fn test_zero_margin_with_newline() { let input = "Hello\nWorld"; let expected = "Hello\nWorld"; - assert_eq!(margin(input, 0), expected); + assert_eq!(P.margin(input, 0), expected); } #[test] diff --git a/src/main.rs b/src/main.rs index 7a694120fd..d827b6801c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -47,7 +47,7 @@ fn main() -> anyhow::Result<()> { Err(error) => { // Ensure all errors are converted to CLIErrors before being printed. let cli_error: Error = error.into(); - tracing::error!("{}", cli_error.color(true)); + tracing::error!("{}", cli_error); std::process::exit(exitcode::CONFIG); } }