diff --git a/benchmarks/src/routing.rs b/benchmarks/src/routing.rs index d5ab6328..89dd4e5c 100644 --- a/benchmarks/src/routing.rs +++ b/benchmarks/src/routing.rs @@ -2,7 +2,7 @@ use std::collections::hash_set::HashSet; use criterion::{criterion_group, Criterion}; -use rocket::{route, config::{self, CliColors}, Request, Data, Route, Config}; +use rocket::{route, config, Request, Data, Route, Config}; use rocket::http::{Method, RawStr, ContentType, Accept, Status}; use rocket::local::blocking::{Client, LocalRequest}; @@ -81,7 +81,7 @@ fn client(routes: Vec) -> Client { let config = Config { profile: Config::RELEASE_PROFILE, log_level: rocket::config::LogLevel::Off, - cli_colors: CliColors::Never, + cli_colors: config::CliColors::Never, shutdown: config::Shutdown { ctrlc: false, #[cfg(unix)] diff --git a/core/lib/fuzz/targets/collision-matching.rs b/core/lib/fuzz/targets/collision-matching.rs index a478bc42..2af226ac 100644 --- a/core/lib/fuzz/targets/collision-matching.rs +++ b/core/lib/fuzz/targets/collision-matching.rs @@ -6,7 +6,6 @@ use rocket::http::QMediaType; use rocket::local::blocking::{LocalRequest, Client}; use rocket::http::{Method, Accept, ContentType, MediaType, uri::Origin}; use rocket::route::{Route, RouteUri, dummy_handler}; -use rocket::config::CliColors; #[derive(Arbitrary)] struct ArbitraryRequestData<'a> { @@ -187,7 +186,7 @@ fn fuzz((route_a, route_b, req): TestData<'_>) { let rocket = rocket::custom(rocket::Config { workers: 2, log_level: rocket::log::LogLevel::Off, - cli_colors: CliColors::Never, + cli_colors: rocket::config::CliColors::Never, ..rocket::Config::debug_default() }); diff --git a/core/lib/src/config/cli_colors.rs b/core/lib/src/config/cli_colors.rs index 863104e2..94dc62e7 100644 --- a/core/lib/src/config/cli_colors.rs +++ b/core/lib/src/config/cli_colors.rs @@ -1,26 +1,43 @@ -use core::fmt; -use serde::{ - de::{self, Unexpected::{Signed, Str}}, - Deserialize, Serialize -}; +use std::fmt; -/// Configure color output for logging. -#[derive(Clone, Serialize, PartialEq, Debug, Default)] +use serde::{de, Deserialize, Serialize}; + +/// Enable or disable coloring when logging. +/// +/// Valid configuration values are: +/// +/// * `"always"` - [`CliColors::Always`] +/// * `"auto"`, `1`, or `true` - [`CliColors::Auto`] _(default)_ +/// * `"never"`, `0`, or `false` - [`CliColors::Never`] +#[derive(Debug, Copy, Clone, Default, Serialize, PartialEq, Eq, Hash)] pub enum CliColors { - /// Always use colors in logs. + /// Always enable colors, irrespective of `stdout` and `stderr`. + /// + /// Case-insensitive string values of `"always"` parse as this value. Always, - /// Use colors in logs if the terminal supports it. + /// Enable colors _only if_ `stdout` and `stderr` support coloring. + /// + /// Case-insensitive string values of `"auto"`, the boolean `true`, and the + /// integer `1` all parse as this value. + /// + /// Only Unix-like systems (Linux, macOS, BSD, etc.), this is equivalent to + /// checking if `stdout` and `stderr` are both TTYs. On Windows, the console + /// is queried for ANSI escape sequence based coloring support and enabled + /// if support is successfully enabled. #[default] Auto, - /// Never use colors in logs. - Never + /// Never enable colors, even if `stdout` and `stderr` support them. + /// + /// Case-insensitive string values of `"never"`, the boolean `false`, and + /// the integer `0` all parse as this value. + Never, } impl fmt::Display for CliColors { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - match *self { + match self { CliColors::Always => write!(f, "always"), CliColors::Auto => write!(f, "auto"), CliColors::Never => write!(f, "never") @@ -28,7 +45,6 @@ impl fmt::Display for CliColors { } } - impl<'de> Deserialize<'de> for CliColors { fn deserialize>(de: D) -> Result { struct Visitor; @@ -37,7 +53,7 @@ impl<'de> Deserialize<'de> for CliColors { type Value = CliColors; fn expecting(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - f.write_str("0, 1, false, true, always, auto, never") + f.write_str("0, 1, false, true, always, auto, or never") } fn visit_str(self, val: &str) -> Result { @@ -46,31 +62,33 @@ impl<'de> Deserialize<'de> for CliColors { "false" => Ok(CliColors::Never), "1" => Ok(CliColors::Auto), "0" => Ok(CliColors::Never), + "always" => Ok(CliColors::Always), "auto" => Ok(CliColors::Auto), "never" => Ok(CliColors::Never), - "always" => Ok(CliColors::Always), - _ => Err(E::invalid_value( - Str(val), - &"0, 1, false, true, always, auto, never", - )) + _ => Err(E::invalid_value(de::Unexpected::Str(val), &self)), } } fn visit_bool(self, val: bool) -> Result { match val { true => Ok(CliColors::Auto), - false => Ok(CliColors::Never) + false => Ok(CliColors::Never), } } fn visit_i64(self, val: i64) -> Result { match val { - 0 => Ok(CliColors::Never), 1 => Ok(CliColors::Auto), - _ => Err(E::invalid_value( - Signed(val), - &"0, 1, false, true, always, auto, never", - )) + 0 => Ok(CliColors::Never), + _ => Err(E::invalid_value(de::Unexpected::Signed(val), &self)), + } + } + + fn visit_u64(self, val: u64) -> Result { + match val { + 1 => Ok(CliColors::Auto), + 0 => Ok(CliColors::Never), + _ => Err(E::invalid_value(de::Unexpected::Unsigned(val), &self)), } } } diff --git a/core/lib/src/config/config.rs b/core/lib/src/config/config.rs index 1f86ad34..6f69b85d 100644 --- a/core/lib/src/config/config.rs +++ b/core/lib/src/config/config.rs @@ -116,7 +116,8 @@ pub struct Config { pub shutdown: Shutdown, /// Max level to log. **(default: _debug_ `normal` / _release_ `critical`)** pub log_level: LogLevel, - /// Whether to use colors and emoji when logging. **(default: `auto`)** + /// Whether to use colors and emoji when logging. **(default: + /// [`CliColors::Auto`])** pub cli_colors: CliColors, /// PRIVATE: This structure may grow (but never change otherwise) in a /// non-breaking release. As such, constructing this structure should diff --git a/core/lib/src/config/mod.rs b/core/lib/src/config/mod.rs index 9049ba86..07286fc1 100644 --- a/core/lib/src/config/mod.rs +++ b/core/lib/src/config/mod.rs @@ -268,118 +268,103 @@ mod tests { ..Config::default() }); - jail.set_env("ROCKET_CONFIG", "Other.toml"); - jail.create_file("Other.toml", r#" + Ok(()) + }); + } + + #[test] + fn test_cli_colors() { + figment::Jail::expect_with(|jail| { + jail.create_file("Rocket.toml", r#" [default] - address = "1.2.3.4" - port = 1234 - workers = 20 - keep_alive = 10 - log_level = "off" cli_colors = "never" "#)?; let config = Config::from(Config::figment()); - assert_eq!(config, Config { - address: Ipv4Addr::new(1, 2, 3, 4).into(), - port: 1234, - workers: 20, - keep_alive: 10, - log_level: LogLevel::Off, - cli_colors: CliColors::Never, - ..Config::default() - }); + assert_eq!(config.cli_colors, CliColors::Never); - jail.set_env("ROCKET_CONFIG", "Other.toml"); - jail.create_file("Other.toml", r#" + jail.create_file("Rocket.toml", r#" [default] - address = "1.2.3.4" - port = 1234 - workers = 20 - keep_alive = 10 - log_level = "off" cli_colors = "auto" "#)?; let config = Config::from(Config::figment()); - assert_eq!(config, Config { - address: Ipv4Addr::new(1, 2, 3, 4).into(), - port: 1234, - workers: 20, - keep_alive: 10, - log_level: LogLevel::Off, - cli_colors: CliColors::Auto, - ..Config::default() - }); + assert_eq!(config.cli_colors, CliColors::Auto); - jail.set_env("ROCKET_CONFIG", "Other.toml"); - jail.create_file("Other.toml", r#" + jail.create_file("Rocket.toml", r#" [default] - address = "1.2.3.4" - port = 1234 - workers = 20 - keep_alive = 10 - log_level = "off" cli_colors = "always" "#)?; let config = Config::from(Config::figment()); - assert_eq!(config, Config { - address: Ipv4Addr::new(1, 2, 3, 4).into(), - port: 1234, - workers: 20, - keep_alive: 10, - log_level: LogLevel::Off, - cli_colors: CliColors::Always, - ..Config::default() - }); + assert_eq!(config.cli_colors, CliColors::Always); - jail.set_env("ROCKET_CONFIG", "Other.toml"); - jail.create_file("Other.toml", r#" + jail.create_file("Rocket.toml", r#" [default] - address = "1.2.3.4" - port = 1234 - workers = 20 - keep_alive = 10 - log_level = "off" cli_colors = true "#)?; let config = Config::from(Config::figment()); - assert_eq!(config, Config { - address: Ipv4Addr::new(1, 2, 3, 4).into(), - port: 1234, - workers: 20, - keep_alive: 10, - log_level: LogLevel::Off, - cli_colors: CliColors::Auto, - ..Config::default() - }); + assert_eq!(config.cli_colors, CliColors::Auto); - jail.set_env("ROCKET_CONFIG", "Other.toml"); - jail.create_file("Other.toml", r#" + jail.create_file("Rocket.toml", r#" + [default] + cli_colors = false + "#)?; + + let config = Config::from(Config::figment()); + assert_eq!(config.cli_colors, CliColors::Never); + + jail.create_file("Rocket.toml", r#"[default]"#)?; + let config = Config::from(Config::figment()); + assert_eq!(config.cli_colors, CliColors::Auto); + + jail.create_file("Rocket.toml", r#" [default] - address = "1.2.3.4" - port = 1234 - workers = 20 - keep_alive = 10 - log_level = "off" cli_colors = 1 "#)?; let config = Config::from(Config::figment()); - assert_eq!(config, Config { - address: Ipv4Addr::new(1, 2, 3, 4).into(), - port: 1234, - workers: 20, - keep_alive: 10, - log_level: LogLevel::Off, - cli_colors: CliColors::Auto, - ..Config::default() - }); + assert_eq!(config.cli_colors, CliColors::Auto); + + jail.create_file("Rocket.toml", r#" + [default] + cli_colors = 0 + "#)?; + + let config = Config::from(Config::figment()); + assert_eq!(config.cli_colors, CliColors::Never); + + jail.set_env("ROCKET_CLI_COLORS", 1); + let config = Config::from(Config::figment()); + assert_eq!(config.cli_colors, CliColors::Auto); + + jail.set_env("ROCKET_CLI_COLORS", 0); + let config = Config::from(Config::figment()); + assert_eq!(config.cli_colors, CliColors::Never); + + jail.set_env("ROCKET_CLI_COLORS", true); + let config = Config::from(Config::figment()); + assert_eq!(config.cli_colors, CliColors::Auto); + + jail.set_env("ROCKET_CLI_COLORS", false); + let config = Config::from(Config::figment()); + assert_eq!(config.cli_colors, CliColors::Never); + + jail.set_env("ROCKET_CLI_COLORS", "always"); + let config = Config::from(Config::figment()); + assert_eq!(config.cli_colors, CliColors::Always); + + jail.set_env("ROCKET_CLI_COLORS", "NEveR"); + let config = Config::from(Config::figment()); + assert_eq!(config.cli_colors, CliColors::Never); + + jail.set_env("ROCKET_CLI_COLORS", "auTO"); + let config = Config::from(Config::figment()); + assert_eq!(config.cli_colors, CliColors::Auto); Ok(()) - }); + }) } #[test] diff --git a/core/lib/src/log.rs b/core/lib/src/log.rs index e1143924..cfd529a2 100644 --- a/core/lib/src/log.rs +++ b/core/lib/src/log.rs @@ -7,8 +7,6 @@ use std::sync::atomic::{AtomicBool, Ordering}; use serde::{de, Serialize, Serializer, Deserialize, Deserializer}; use yansi::{Paint, Painted, Condition}; -use crate::config::CliColors; - /// Reexport the `log` crate as `private`. pub use log as private; @@ -170,12 +168,12 @@ pub(crate) fn init(config: &crate::Config) { // Always disable colors if requested or if the stdout/err aren't TTYs. let should_color = match config.cli_colors { - CliColors::Always => true, - CliColors::Auto => Condition::stdouterr_are_tty(), - CliColors::Never => false + crate::config::CliColors::Always => Condition::ALWAYS, + crate::config::CliColors::Auto => Condition::DEFAULT, + crate::config::CliColors::Never => Condition::NEVER, }; - yansi::whenever(Condition::cached(should_color)); + yansi::whenever(should_color); // Set Rocket-logger specific settings only if Rocket's logger is set. if ROCKET_LOGGER_SET.load(Ordering::Acquire) { diff --git a/site/guide/9-configuration.md b/site/guide/9-configuration.md index 230d345b..76b14a9a 100644 --- a/site/guide/9-configuration.md +++ b/site/guide/9-configuration.md @@ -27,7 +27,7 @@ values: | `ip_header` | `string`, `false` | IP header to inspect to get [client's real IP]. | `"X-Real-IP"` | | `keep_alive` | `u32` | Keep-alive timeout seconds; disabled when `0`. | `5` | | `log_level` | [`LogLevel`] | Max level to log. (off/normal/debug/critical) | `normal`/`critical` | -| `cli_colors` | `bool` | Whether to use colors and emoji when logging. | `true` | +| `cli_colors` | [`CliColors`] | Whether to use colors and emoji when logging. | `"auto"` | | `secret_key` | [`SecretKey`] | Secret key for signing and encrypting values. | `None` | | `tls` | [`TlsConfig`] | TLS configuration, if any. | `None` | | `limits` | [`Limits`] | Streaming read size limits. | [`Limits::default()`] | @@ -68,6 +68,7 @@ profile supplant any values with the same name in any profile. [`Limits`]: @api/rocket/data/struct.Limits.html [`Limits::default()`]: @api/rocket/data/struct.Limits.html#impl-Default [`SecretKey`]: @api/rocket/config/struct.SecretKey.html +[`CliColors`]: @api/rocket/config/enum.CliColors.html [`TlsConfig`]: @api/rocket/config/struct.TlsConfig.html [`Shutdown`]: @api/rocket/config/struct.Shutdown.html [`Shutdown::default()`]: @api/rocket/config/struct.Shutdown.html#fields