diff --git a/Cargo.lock b/Cargo.lock index 30a78fc5..9b7d2f5e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -311,14 +311,49 @@ version = "2.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ - "ansi_term", + "bitflags", + "textwrap 0.11.0", + "unicode-width", +] + +[[package]] +name = "clap" +version = "3.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2dbdf4bdacb33466e854ce889eee8dfd5729abf7ccd7664d0a2d60cd384440b" +dependencies = [ "atty", "bitflags", + "clap_derive", + "clap_lex", + "indexmap", + "lazy_static", "strsim", - "term_size", - "textwrap", - "unicode-width", - "vec_map", + "termcolor", + "terminal_size", + "textwrap 0.15.0", +] + +[[package]] +name = "clap_derive" +version = "3.1.18" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "25320346e922cffe59c0bbc5410c8d8784509efb321488971081313cb1e1a33c" +dependencies = [ + "heck", + "proc-macro-error", + "proc-macro2", + "quote", + "syn", +] + +[[package]] +name = "clap_lex" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a37c35f1112dad5e6e0b1adaff798507497a18fceeb30cceb3bae7d1427b9213" +dependencies = [ + "os_str_bytes", ] [[package]] @@ -390,7 +425,7 @@ checksum = "1604dafd25fba2fe2d5895a9da139f8dc9b319a5fe5354ca137cbbce4e178d10" dependencies = [ "atty", "cast", - "clap", + "clap 2.34.0", "criterion-plot", "csv", "itertools", @@ -715,12 +750,9 @@ dependencies = [ [[package]] name = "heck" -version = "0.3.3" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c" -dependencies = [ - "unicode-segmentation", -] +checksum = "2540771e65fc8cb83cd6e8a237f70c319bd5c29f78ed1084ba5d50eeac86f7f9" [[package]] name = "hermit-abi" @@ -744,7 +776,7 @@ dependencies = [ "bytefmt", "chrono", "chrono-tz", - "clap", + "clap 3.1.18", "closure", "collection_macros", "config", @@ -774,7 +806,6 @@ dependencies = [ "shellwords", "signal-hook", "stats_alloc", - "structopt", "thiserror", "wildmatch", "winapi", @@ -1019,6 +1050,12 @@ dependencies = [ "hashbrown 0.12.1", ] +[[package]] +name = "os_str_bytes" +version = "6.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "21326818e99cfe6ce1e524c2a805c189a99b5ae555a35d19f9a284b427d86afa" + [[package]] name = "parse-zoneinfo" version = "0.3.0" @@ -1585,50 +1622,35 @@ checksum = "5c0e04424e733e69714ca1bbb9204c1a57f09f5493439520f9f68c132ad25eec" [[package]] name = "strsim" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" - -[[package]] -name = "structopt" -version = "0.3.26" +version = "0.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10" -dependencies = [ - "clap", - "lazy_static", - "structopt-derive", -] +checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623" [[package]] -name = "structopt-derive" -version = "0.4.18" +name = "syn" +version = "1.0.96" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0" +checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" dependencies = [ - "heck", - "proc-macro-error", "proc-macro2", "quote", - "syn", + "unicode-ident", ] [[package]] -name = "syn" -version = "1.0.96" +name = "termcolor" +version = "1.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0748dd251e24453cb8717f0354206b91557e4ec8703673a4b30208f2abaf1ebf" +checksum = "bab24d30b911b2376f3a13cc2cd443142f0c81dda04c118693e35b3835757755" dependencies = [ - "proc-macro2", - "quote", - "unicode-ident", + "winapi-util", ] [[package]] -name = "term_size" -version = "0.3.2" +name = "terminal_size" +version = "0.1.17" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1e4129646ca0ed8f45d09b929036bafad5377103edd06e50bf574b353d2b08d9" +checksum = "633c1a546cee861a1a6d0dc69ebeca693bf4296661ba7852b9d21d159e0506df" dependencies = [ "libc", "winapi", @@ -1640,10 +1662,18 @@ version = "0.11.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" dependencies = [ - "term_size", "unicode-width", ] +[[package]] +name = "textwrap" +version = "0.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b1141d4d61095b28419e22cb0bbf02755f5e54e0526f97f1e3d1d160e60885fb" +dependencies = [ + "terminal_size", +] + [[package]] name = "thiserror" version = "1.0.31" @@ -1720,12 +1750,6 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d22af068fba1eb5edcb4aea19d382b2a3deb4c8f9d475c589b6ada9e0fd493ee" -[[package]] -name = "unicode-segmentation" -version = "1.9.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7e8820f5d777f6224dc4be3632222971ac30164d4a258d595640799554ebfd99" - [[package]] name = "unicode-width" version = "0.1.9" @@ -1738,12 +1762,6 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77439c1b53d2303b20d9459b1ade71a83c716e3f9c34f3228c00e6f185d6c002" -[[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" - [[package]] name = "version_check" version = "0.9.4" diff --git a/Cargo.toml b/Cargo.toml index c50a8adf..a86534a9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -42,7 +42,6 @@ serde_json = { version = "1", features = ["raw_value"] } serde_yaml = "0" shellwords = "1" signal-hook = "0" -structopt = "0" thiserror = "1" wildmatch = "2" @@ -51,8 +50,8 @@ version = "1" default-features = false [dependencies.clap] -version = "2" -features = ["wrap_help"] +version = "3" +features = ["wrap_help", "derive", "env"] [dependencies.winapi] version = "0" diff --git a/README.md b/README.md index 01a3c67a..1420e49c 100644 --- a/README.md +++ b/README.md @@ -297,43 +297,41 @@ Log viewer which translates JSON logs into pretty human-readable representation. ### Complete set of options and flags ``` -hl 0.10.3 +hl 0.11.0 JSON log converter to human readable representation USAGE: - hl [FLAGS] [OPTIONS] [--] [FILE]... - -FLAGS: - -c Handful alias for --color=always, overrides --color option - --help Prints help information - --list-themes List available themes and exit - -L, --local Use local time zone, overrides --time-zone option - -P Handful alias for --paging=never, overrides --paging option - -r, --raw-fields Disable unescaping and prettifying of field values - -V, --version Prints version information - -OPTIONS: - --buffer-size Buffer size [env: HL_BUFFER_SIZE=] [default: 2 MiB] - --color Color output options, one of { auto, always, never } [env: HL_COLOR=] [default: auto] - -C, --concurrency Number of processing threads [env: HL_CONCURRENCY=] - -f, --filter ... Filtering by field values in one of forms [=, ~=, ~~=, !=, !~=, !~~=] where ~ denotes substring match and ~~ denotes regular expression match - -h, --hide ... Hide fields with the specified keys - -e, --hide-empty-fields Hide empty fields, applies for null, string, object and array fields only [env: HL_HIDE_EMPTY_FIELDS=] - --interrupt-ignore-count Number of interrupts to ignore, i.e. Ctrl-C (SIGINT) [env: HL_INTERRUPT_IGNORE_COUNT=] [default: 3] - -l, --level Filtering by level, one of { d[ebug], i[nfo], w[arning], e[rror] } [env: HL_LEVEL=] - --max-message-size Maximum message size [env: HL_MAX_MESSAGE_SIZE=] [default: 64 MiB] - --paging Output paging options, one of { auto, always, never } [env: HL_PAGING=] [default: auto] - -H, --show ... Hide all fields except fields with the specified keys - -E, --show-empty-fields Show empty fields, overrides --hide-empty-fields option [env: HL_SHOW_EMPTY_FIELDS=] - --since Filtering by timestamp >= the value (--time-zone and --local options are honored) - --theme Color theme [env: HL_THEME=] [default: one-dark-green] - -t, --time-format Time format, see https://man7.org/linux/man-pages/man1/date.1.html [env: HL_TIME_FORMAT=] [default: %b %d %T.%3N] - -Z, --time-zone Time zone name, see column "TZ database name" at https://en.wikipedia.org/wiki/List_of_tz_database_time_zones [env: HL_TIME_ZONE=] [default: UTC] - -u, --unhide ... Unhide fields with the specified keys - --until Filtering by timestamp <= the value (--time-zone and --local options are honored) + hl [OPTIONS] [--] [FILE]... ARGS: ... Files to process + +OPTIONS: + --buffer-size Buffer size [env: HL_BUFFER_SIZE=] [default: "2 MiB"] + -c Handful alias for --color=always, overrides --color option + -C, --concurrency Number of processing threads [env: HL_CONCURRENCY=] + --color Color output options [env: HL_COLOR=] [default: auto] [possible values: auto, always, never] + -e, --hide-empty-fields Hide empty fields, applies for null, string, object and array fields only [env: HL_HIDE_EMPTY_FIELDS=] + -E, --show-empty-fields Show empty fields, overrides --hide-empty-fields option [env: HL_SHOW_EMPTY_FIELDS=] + -f, --filter Filtering by field values in one of forms [=, ~=, ~~=, !=, !~=, !~~=] where ~ denotes substring match and ~~ denotes regular expression match + -h, --hide Hide fields with the specified keys + -H, --show Hide all fields except fields with the specified keys + --help Print help information + --interrupt-ignore-count Number of interrupts to ignore, i.e. Ctrl-C (SIGINT) [env: HL_INTERRUPT_IGNORE_COUNT=] [default: 3] + -l, --level Filtering by level [env: HL_LEVEL=] [possible values: error, warning, info, debug] + -L, --local Use local time zone, overrides --time-zone option + --list-themes List available themes and exit + --max-message-size Maximum message size [env: HL_MAX_MESSAGE_SIZE=] [default: "64 MiB"] + -P Handful alias for --paging=never, overrides --paging option + --paging Output paging options [env: HL_PAGING=] [default: auto] [possible values: auto, always, never] + -r, --raw-fields Disable unescaping and prettifying of field values + --since Filtering by timestamp >= the value (--time-zone and --local options are honored) + -t, --time-format Time format, see https://man7.org/linux/man-pages/man1/date.1.html [env: HL_TIME_FORMAT=] [default: "%y-%m-%d %T.%3N"] + --theme Color theme [env: HL_THEME=] [default: one-dark-green] + -u, --unhide Unhide fields with the specified keys + --until Filtering by timestamp <= the value (--time-zone and --local options are honored) + -V, --version Print version information + -Z, --time-zone Time zone name, see column "TZ database name" at https://en.wikipedia.org/wiki/List_of_tz_database_time_zones [env: HL_TIME_ZONE=] [default: UTC] ``` ## Performance diff --git a/src/error.rs b/src/error.rs index 48d87e2a..84090b2e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -13,38 +13,21 @@ pub enum Error { #[error(transparent)] Io(#[from] io::Error), #[error(transparent)] - ParseIntError(#[from] ParseIntError), + SizeParseError(#[from] SizeParseError), #[error(transparent)] - TryFromIntError(#[from] TryFromIntError), + NonZeroSizeParseError(#[from] NonZeroSizeParseError), #[error("failed to load configuration: {0}")] Config(#[from] ConfigError), #[error(transparent)] Boxed(#[from] Box), #[error("file {filename:?} not found")] FileNotFoundError { filename: String }, - #[error("invalid level {value:?}, use any of {valid_values:?}")] - InvalidLevel { - value: String, - valid_values: Vec, - }, - #[error("invalid field kind {value:?}, use any of {valid_values:?}")] - InvalidFieldKind { - value: String, - valid_values: Vec, - }, - #[error( - "invalid size {0:?}, use {:?} or {:?} format for IEC units or {:?} format for SI units", - "64K", - "64KiB", - "64KB" - )] - InvalidSize(String), + #[error(transparent)] + InvalidLevel(#[from] InvalidLevelError), #[error("cannot recognize time {0:?}")] UnrecognizedTime(String), #[error("unknown theme {name:?}, use any of {known:?}")] UnknownTheme { name: String, known: Vec }, - #[error("zero size")] - ZeroSize, #[error("failed to parse utf-8 string: {0}")] Utf8Error(#[from] std::str::Utf8Error), #[error("failed to parse yaml: {0}")] @@ -55,5 +38,38 @@ pub enum Error { WrongRegularExpression(#[from] regex::Error), } +/// SizeParseError is an error which may occur when parsing size. +#[derive(Error, Debug)] +pub enum SizeParseError { + #[error(transparent)] + ParseIntError(#[from] ParseIntError), + #[error(transparent)] + TryFromIntError(#[from] TryFromIntError), + #[error( + "invalid size {0:?}, use {:?} or {:?} format for IEC units or {:?} format for SI units", + "64K", + "64KiB", + "64KB" + )] + InvalidSize(String), +} + +/// NonZeroSizeParseError is an error which may occur when parsing non-zero size. +#[derive(Error, Debug)] +pub enum NonZeroSizeParseError { + #[error(transparent)] + SizeParseError(#[from] SizeParseError), + #[error("zero size")] + ZeroSize, +} + +/// NonZeroSizeParseError is an error which may occur when parsing non-zero size. +#[derive(Error, Debug)] +#[error("invalid level {value:?}, use any of {valid_values:?}")] +pub struct InvalidLevelError { + pub value: String, + pub valid_values: Vec, +} + /// Result is an alias for standard result with bound Error type. pub type Result = std::result::Result; diff --git a/src/main.rs b/src/main.rs index 1c19ca9e..1c9b035c 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,13 +8,10 @@ use std::sync::Arc; use ansi_term::Colour; use chrono::{FixedOffset, Local, TimeZone}; use chrono_tz::{Tz, UTC}; +use clap::{ArgEnum, Parser}; use itertools::Itertools; use once_cell::sync::Lazy; use platform_dirs::AppDirs; -use structopt::{ - clap::{arg_enum, AppSettings::*}, - StructOpt, -}; // local imports use hl::datefmt::LinuxDateFormat; @@ -35,38 +32,40 @@ const APP_NAME: &str = "hl"; // --- /// JSON log converter to human readable representation. -#[derive(StructOpt)] -#[structopt(setting(ColorAuto), setting(ColoredHelp))] +#[derive(Parser)] +#[clap(version)] struct Opt { - /// Color output options, one of { auto, always, never }. - #[structopt( + /// Color output options. + #[clap( long, default_value = "auto", env = "HL_COLOR", overrides_with = "color" )] + #[clap(arg_enum)] color: ColorOption, // /// Handful alias for --color=always, overrides --color option. - #[structopt(short)] + #[clap(short)] color_always: bool, // - /// Output paging options, one of { auto, always, never }. - #[structopt( + /// Output paging options. + #[clap( long, default_value = "auto", env = "HL_PAGING", overrides_with = "paging" )] + #[clap(arg_enum)] paging: PagingOption, // /// Handful alias for --paging=never, overrides --paging option. - #[structopt(short = "P")] + #[clap(short = 'P')] paging_never: bool, // // /// Color theme. - #[structopt( + #[clap( long, default_value = &CONFIG.theme, env = "HL_THEME", @@ -75,11 +74,11 @@ struct Opt { theme: String, // /// Disable unescaping and prettifying of field values. - #[structopt(short, long)] + #[clap(short, long)] raw_fields: bool, // /// Number of interrupts to ignore, i.e. Ctrl-C (SIGINT). - #[structopt( + #[clap( long, default_value = "3", env = "HL_INTERRUPT_IGNORE_COUNT", @@ -88,52 +87,53 @@ struct Opt { interrupt_ignore_count: usize, // /// Buffer size. - #[structopt(long, default_value = "2 MiB", env="HL_BUFFER_SIZE", overrides_with = "buffer-size", parse(try_from_str = parse_non_zero_size))] + #[clap(long, default_value = "2 MiB", env="HL_BUFFER_SIZE", overrides_with = "buffer-size", parse(try_from_str = parse_non_zero_size))] buffer_size: usize, // /// Maximum message size. - #[structopt(long, default_value = "64 MiB", env="HL_MAX_MESSAGE_SIZE", overrides_with = "max-message-size", parse(try_from_str = parse_non_zero_size))] + #[clap(long, default_value = "64 MiB", env="HL_MAX_MESSAGE_SIZE", overrides_with = "max-message-size", parse(try_from_str = parse_non_zero_size))] max_message_size: usize, // /// Number of processing threads. - #[structopt( + #[clap( long, - short = "C", + short = 'C', env = "HL_CONCURRENCY", overrides_with = "concurrency" )] concurrency: Option, // /// Filtering by field values in one of forms [=, ~=, ~~=, !=, !~=, !~~=] where ~ denotes substring match and ~~ denotes regular expression match. - #[structopt(short, long, number_of_values = 1)] + #[clap(short, long, number_of_values = 1)] filter: Vec, // /// Hide fields with the specified keys. - #[structopt(long, short = "h", number_of_values = 1)] + #[clap(long, short = 'h', number_of_values = 1)] hide: Vec, // /// Hide all fields except fields with the specified keys. - #[structopt(long, short = "H", number_of_values = 1)] + #[structopt(long, short = 'H', number_of_values = 1)] show: Vec, // /// Unhide fields with the specified keys. - #[structopt(long, short = "u", number_of_values = 1)] + #[structopt(long, short = 'u', number_of_values = 1)] unhide: Vec, // - /// Filtering by level, one of { d[ebug], i[nfo], w[arning], e[rror] }. - #[structopt(short, long, env = "HL_LEVEL", overrides_with = "level")] + /// Filtering by level. + #[clap(short, long, env = "HL_LEVEL", overrides_with = "level")] + #[clap(arg_enum)] level: Option, // /// Filtering by timestamp >= the value (--time-zone and --local options are honored). - #[structopt(long, allow_hyphen_values = true)] + #[clap(long, allow_hyphen_values = true)] since: Option, // /// Filtering by timestamp <= the value (--time-zone and --local options are honored). - #[structopt(long, allow_hyphen_values = true)] + #[clap(long, allow_hyphen_values = true)] until: Option, // /// Time format, see https://man7.org/linux/man-pages/man1/date.1.html. - #[structopt( + #[clap( short, long, env="HL_TIME_FORMAT", @@ -143,46 +143,42 @@ struct Opt { time_format: String, // /// Time zone name, see column "TZ database name" at https://en.wikipedia.org/wiki/List_of_tz_database_time_zones. - #[structopt(long, short = "Z", env="HL_TIME_ZONE", default_value = &CONFIG.time_zone.name(), overrides_with = "time-zone")] + #[clap(long, short = 'Z', env="HL_TIME_ZONE", default_value = &CONFIG.time_zone.name(), overrides_with = "time-zone")] time_zone: Tz, // /// Use local time zone, overrides --time-zone option. - #[structopt(long, short = "L")] + #[clap(long, short = 'L')] local: bool, // /// Files to process - #[structopt(name = "FILE", parse(from_os_str))] + #[clap(name = "FILE", parse(from_os_str))] files: Vec, // /// Hide empty fields, applies for null, string, object and array fields only. - #[structopt(long, short = "e", env = "HL_HIDE_EMPTY_FIELDS")] + #[clap(long, short = 'e', env = "HL_HIDE_EMPTY_FIELDS")] hide_empty_fields: bool, // /// Show empty fields, overrides --hide-empty-fields option. - #[structopt(long, short = "E", env = "HL_SHOW_EMPTY_FIELDS")] + #[clap(long, short = 'E', env = "HL_SHOW_EMPTY_FIELDS")] show_empty_fields: bool, // /// List available themes and exit. - #[structopt(long)] + #[clap(long)] list_themes: bool, } -arg_enum! { - #[derive(Debug)] - enum ColorOption { - Auto, - Always, - Never, - } +#[derive(ArgEnum, Debug, Clone, Copy)] +enum ColorOption { + Auto, + Always, + Never, } -arg_enum! { - #[derive(Debug)] - enum PagingOption { - Auto, - Always, - Never, - } +#[derive(ArgEnum, Debug, Clone, Copy)] +enum PagingOption { + Auto, + Always, + Never, } // --- @@ -196,22 +192,22 @@ fn load_config() -> Settings { Settings::load(&app_dirs).unwrap() } -fn parse_size(s: &str) -> Result { +fn parse_size(s: &str) -> std::result::Result { match bytefmt::parse(s) { Ok(value) => Ok(usize::try_from(value)?), Err(_) => { if let Ok(value) = bytefmt::parse(s.to_owned() + "ib") { return Ok(usize::try_from(value)?); } - Err(Error::InvalidSize(s.into())) + Err(SizeParseError::InvalidSize(s.into())) } } } -fn parse_non_zero_size(s: &str) -> Result { +fn parse_non_zero_size(s: &str) -> std::result::Result { let value = parse_size(s)?; if value == 0 { - Err(Error::ZeroSize) + Err(NonZeroSizeParseError::ZeroSize) } else { Ok(value) } @@ -222,7 +218,7 @@ fn parse_non_zero_size(s: &str) -> Result { fn run() -> Result<()> { let app_dirs = AppDirs::new(Some("hl"), true).unwrap(); let settings = Settings::load(&app_dirs)?; - let opt = Opt::from_args(); + let opt = Opt::parse(); let stdout_is_atty = || atty::is(atty::Stream::Stdout); let color_supported = if stdout_is_atty() { if let Err(err) = hl::enable_ansi_support() { diff --git a/src/types.rs b/src/types.rs index 75d63cd0..1bf3710f 100644 --- a/src/types.rs +++ b/src/types.rs @@ -1,17 +1,19 @@ // std imports use std::cmp::Ord; +use std::result::Result; use std::str::FromStr; // third-party imports +use clap::ArgEnum; use enum_map::Enum; use serde::Deserialize; // local imports -use crate::error::{Error, Result}; +use crate::error::InvalidLevelError; // --- -#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Enum)] +#[derive(ArgEnum, Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd, Enum)] #[serde(rename_all = "kebab-case")] pub enum Level { Error, @@ -21,9 +23,9 @@ pub enum Level { } impl FromStr for Level { - type Err = Error; + type Err = InvalidLevelError; - fn from_str(s: &str) -> Result { + fn from_str(s: &str) -> Result { let matches = |value| s.eq_ignore_ascii_case(value); if matches("e") || matches("error") { Ok(Level::Error) @@ -34,7 +36,7 @@ impl FromStr for Level { } else if matches("d") || matches("debug") { Ok(Level::Debug) } else { - Err(Error::InvalidLevel { + Err(InvalidLevelError { value: s.into(), valid_values: vec![ "error".into(), @@ -50,7 +52,6 @@ impl FromStr for Level { // --- #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, Ord, PartialEq, PartialOrd)] -#[serde(rename_all = "kebab-case")] pub enum FieldKind { Time, Level,