Skip to content

Commit

Permalink
refactor: move color coding logic into the formatter
Browse files Browse the repository at this point in the history
  • Loading branch information
tusharmath committed Apr 11, 2024
1 parent 010e96c commit a5196a7
Show file tree
Hide file tree
Showing 5 changed files with 77 additions and 71 deletions.
14 changes: 14 additions & 0 deletions src/cli/error.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
use std::fmt::Display;

use crate::error::Error;
impl From<rustls::Error> 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)
}
}
14 changes: 2 additions & 12 deletions src/cli/mod.rs
Original file line number Diff line number Diff line change
@@ -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<rustls::Error> 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)
}
}
2 changes: 1 addition & 1 deletion src/cli/telemetry.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
});
}
})?;
Expand Down
116 changes: 59 additions & 57 deletions src/error.rs
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
use std::fmt::{Debug, Display};
use std::fmt::Debug;

use colored::Colorize;
use derive_setters::Setters;
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<String>,
Expand All @@ -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(),
Expand All @@ -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::<Vec<char>>();
chars[0] = '•';
chars[1] = ' ';
chars.into_iter().collect::<String>()
}
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 {
Expand All @@ -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() {
Expand All @@ -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")?;
Expand All @@ -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::<Vec<char>>();
chars[0] = '•';
chars[1] = ' ';
chars.into_iter().collect::<String>()
}
}

impl From<hyper::Error> for Error {
fn from(error: hyper::Error) -> Self {
// TODO: add type-safety to CLIError conversion
Expand Down Expand Up @@ -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]
Expand Down
2 changes: 1 addition & 1 deletion src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand Down

0 comments on commit a5196a7

Please sign in to comment.