From 24f7cb7c3682bdcf8ac039950afbd492cdef9aeb Mon Sep 17 00:00:00 2001 From: rina Date: Fri, 7 Nov 2025 15:26:44 +1000 Subject: [PATCH 1/5] fix: list accepted strings in error message of Verbosity deserialise --- lychee-bin/src/verbosity.rs | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/lychee-bin/src/verbosity.rs b/lychee-bin/src/verbosity.rs index e526e3de96..50531c7992 100644 --- a/lychee-bin/src/verbosity.rs +++ b/lychee-bin/src/verbosity.rs @@ -98,7 +98,15 @@ impl<'de> Deserialize<'de> for Verbosity { where D: serde::Deserializer<'de>, { - let s = String::deserialize(deserializer)?; + let append_expected = |err| { + D::Error::custom(format!( + r#"{err}expected one of "error", "warn", "info", "debug", or "trace""# + )) + }; + + use serde::de::Error; + + let s = String::deserialize(deserializer).map_err(append_expected)?; let level = match s.to_lowercase().as_str() { "error" => Level::Error, "warn" | "warning" => Level::Warn, @@ -106,9 +114,9 @@ impl<'de> Deserialize<'de> for Verbosity { "debug" => Level::Debug, "trace" => Level::Trace, level => { - return Err(serde::de::Error::custom(format!( - "invalid log level `{level}`" - ))); + return Err(append_expected(serde::de::Error::custom(format!( + r#"invalid log level "{level}""# + )))); } }; Ok(Verbosity { From 051488333966fc807e0211329678ba4a0be51a66 Mon Sep 17 00:00:00 2001 From: rina Date: Fri, 7 Nov 2025 15:54:41 +1000 Subject: [PATCH 2/5] refactor: use deserialiser from log::Level, and mention example TOML use the serde functionality of the log crate. this will make the error message print the accepted strings if an incorrect string is provided. unknown variant `asd`, expected one of `ERROR`, `WARN`, `INFO`, `DEBUG`, `TRACE` however, if you use a wrong type (like bool), it will just say "wanted string or table". to try and direct to docs, we link to the example toml in github. alternatively, this could link to https://lychee.cli.rs/guides/config/ instead? the table format from log::Level is a bit mysterious and could be a source for confusion, but idk how to prevent that - can you somehow assert that what is being deserialiesd is a string? related to https://www.github.com/lycheeverse/lychee/issues/1903 --- Cargo.lock | 1 + lychee-bin/Cargo.toml | 2 +- lychee-bin/src/main.rs | 9 +++++++-- lychee-bin/src/verbosity.rs | 22 +--------------------- lychee-lib/Cargo.toml | 2 +- 5 files changed, 11 insertions(+), 25 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 64ed30ff3e..d7e3ce67dd 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2485,6 +2485,7 @@ version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" dependencies = [ + "serde", "value-bag", ] diff --git a/lychee-bin/Cargo.toml b/lychee-bin/Cargo.toml index cf0539d3f9..29b40a72dd 100644 --- a/lychee-bin/Cargo.toml +++ b/lychee-bin/Cargo.toml @@ -34,7 +34,7 @@ humantime = "2.3.0" humantime-serde = "1.1.1" numeric-sort = "0.1.5" indicatif = "0.18.1" -log = "0.4.28" +log = { version = "0.4.28", features = ["serde"] } openssl-sys = { version = "0.9.110", optional = true } pad = "0.1.6" regex = "1.12.2" diff --git a/lychee-bin/src/main.rs b/lychee-bin/src/main.rs index e3264a7268..686cc65c5c 100644 --- a/lychee-bin/src/main.rs +++ b/lychee-bin/src/main.rs @@ -64,7 +64,7 @@ use std::path::PathBuf; use std::sync::Arc; use anyhow::{Context, Error, Result, bail}; -use clap::Parser; +use clap::{Parser, crate_version}; use commands::{CommandParams, generate}; use formatters::{get_stats_formatter, log::init_logging}; use http::HeaderMap; @@ -266,7 +266,12 @@ fn run_main() -> Result { let opts = match load_config() { Ok(opts) => opts, Err(e) => { - error!("Error while loading config: {e}"); + error!( + "Error while loading config: {}\n\ + See: https://github.com/lycheeverse/lychee/blob/lychee-v{}/lychee.example.toml", + e, + crate_version!() + ); exit(ExitCode::ConfigFile as i32); } }; diff --git a/lychee-bin/src/verbosity.rs b/lychee-bin/src/verbosity.rs index 50531c7992..ea3dfaf8f9 100644 --- a/lychee-bin/src/verbosity.rs +++ b/lychee-bin/src/verbosity.rs @@ -98,27 +98,7 @@ impl<'de> Deserialize<'de> for Verbosity { where D: serde::Deserializer<'de>, { - let append_expected = |err| { - D::Error::custom(format!( - r#"{err}expected one of "error", "warn", "info", "debug", or "trace""# - )) - }; - - use serde::de::Error; - - let s = String::deserialize(deserializer).map_err(append_expected)?; - let level = match s.to_lowercase().as_str() { - "error" => Level::Error, - "warn" | "warning" => Level::Warn, - "info" => Level::Info, - "debug" => Level::Debug, - "trace" => Level::Trace, - level => { - return Err(append_expected(serde::de::Error::custom(format!( - r#"invalid log level "{level}""# - )))); - } - }; + let level = log::Level::deserialize(deserializer)?; Ok(Verbosity { verbose: level_value(level) as u8, quiet: 0, diff --git a/lychee-lib/Cargo.toml b/lychee-lib/Cargo.toml index 89cd685bb5..251c657dd4 100644 --- a/lychee-lib/Cargo.toml +++ b/lychee-lib/Cargo.toml @@ -30,7 +30,7 @@ hyper = "1.6.0" ignore = "0.4.24" ip_network = "0.4.1" linkify = "0.10.0" -log = "0.4.28" +log = { version = "0.4.28", features = ["serde"] } octocrab = "0.47.0" openssl-sys = { version = "0.9.110", optional = true } path-clean = "1.0.1" From 7b5cf1dc2d85b21270067a1757c516302837414a Mon Sep 17 00:00:00 2001 From: rina Date: Tue, 11 Nov 2025 11:41:10 +1000 Subject: [PATCH 3/5] use String::deserialize then FromStr to accept only strings and show the list of acceptable values even when an invalid type value is given. TODO: should we implement TryFrom or FromStr for our Verbosity type? then, we would just call that from Deserialize. however, we'd need to choose an error type and there's nothing in ErrorKind that's suitable --- lychee-bin/src/verbosity.rs | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/lychee-bin/src/verbosity.rs b/lychee-bin/src/verbosity.rs index ea3dfaf8f9..e7a30df101 100644 --- a/lychee-bin/src/verbosity.rs +++ b/lychee-bin/src/verbosity.rs @@ -1,6 +1,7 @@ use log::Level; use log::LevelFilter; use serde::Deserialize; +use std::str::FromStr; /// Control the verbosity of the CLI output /// @@ -98,7 +99,21 @@ impl<'de> Deserialize<'de> for Verbosity { where D: serde::Deserializer<'de>, { - let level = log::Level::deserialize(deserializer)?; + let custom_error = || { + use serde::de::Error; + D::Error::custom( + r#"invalid verbosity value, expected one of "error", "warn", "info", "debug", or "trace""#, + ) + }; + + let level = <&str>::deserialize(deserializer).map_err(|_| custom_error())?; + let level = if level.eq_ignore_ascii_case("warning") { + "warn" + } else { + level + }; + let level = log::Level::from_str(level).map_err(|_| custom_error())?; + Ok(Verbosity { verbose: level_value(level) as u8, quiet: 0, From 19d6e04001660031f62205c87cbb65485cb9961d Mon Sep 17 00:00:00 2001 From: rina Date: Tue, 11 Nov 2025 12:00:18 +1000 Subject: [PATCH 4/5] weird anyway, looks like this now ``` [ERROR] Error while loading config: Cannot load default configuration file `lychee.toml`: Failed to parse configuration file Caused by: TOML parse error at line 5, column 11 | 5 | verbose = {} | ^^ invalid verbosity value, expected one of "error", "warn", "info", "debug", or "trace" See: https://github.com/lycheeverse/lychee/blob/lychee-v0.21.0/lychee.example.toml ``` --- lychee-bin/src/verbosity.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lychee-bin/src/verbosity.rs b/lychee-bin/src/verbosity.rs index e7a30df101..893d1d7889 100644 --- a/lychee-bin/src/verbosity.rs +++ b/lychee-bin/src/verbosity.rs @@ -106,11 +106,11 @@ impl<'de> Deserialize<'de> for Verbosity { ) }; - let level = <&str>::deserialize(deserializer).map_err(|_| custom_error())?; + let level = String::deserialize(deserializer).map_err(|_| custom_error())?; let level = if level.eq_ignore_ascii_case("warning") { "warn" } else { - level + &level }; let level = log::Level::from_str(level).map_err(|_| custom_error())?; From 3948ca58d0ee83e309cbeffa6a2420baf1199ec5 Mon Sep 17 00:00:00 2001 From: rina Date: Tue, 11 Nov 2025 21:04:50 +1000 Subject: [PATCH 5/5] so true!!! (serde feature in log crate no longer needed) --- Cargo.lock | 1 - lychee-bin/Cargo.toml | 2 +- lychee-lib/Cargo.toml | 2 +- 3 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index d7e3ce67dd..64ed30ff3e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2485,7 +2485,6 @@ version = "0.4.28" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34080505efa8e45a4b816c349525ebe327ceaa8559756f0356cba97ef3bf7432" dependencies = [ - "serde", "value-bag", ] diff --git a/lychee-bin/Cargo.toml b/lychee-bin/Cargo.toml index 29b40a72dd..cf0539d3f9 100644 --- a/lychee-bin/Cargo.toml +++ b/lychee-bin/Cargo.toml @@ -34,7 +34,7 @@ humantime = "2.3.0" humantime-serde = "1.1.1" numeric-sort = "0.1.5" indicatif = "0.18.1" -log = { version = "0.4.28", features = ["serde"] } +log = "0.4.28" openssl-sys = { version = "0.9.110", optional = true } pad = "0.1.6" regex = "1.12.2" diff --git a/lychee-lib/Cargo.toml b/lychee-lib/Cargo.toml index 251c657dd4..89cd685bb5 100644 --- a/lychee-lib/Cargo.toml +++ b/lychee-lib/Cargo.toml @@ -30,7 +30,7 @@ hyper = "1.6.0" ignore = "0.4.24" ip_network = "0.4.1" linkify = "0.10.0" -log = { version = "0.4.28", features = ["serde"] } +log = "0.4.28" octocrab = "0.47.0" openssl-sys = { version = "0.9.110", optional = true } path-clean = "1.0.1"