From 970dfa1d13c4917152524fd3eb3df6d3efb14a5e Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Sun, 11 Jan 2026 13:01:22 +0900 Subject: [PATCH 1/2] Add garde to dependencies --- Cargo.lock | 42 +++++++++++++++++++++++++++++++++++++++++- Cargo.toml | 1 + 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/Cargo.lock b/Cargo.lock index 771e78f..7b8bb3a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -443,6 +443,20 @@ version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0b6a852b24ab71dffc585bcb46eaf7959d175cb865a7152e35b348d1b2960422" +[[package]] +name = "compact_str" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b79c4069c6cad78e2e0cdfcbd26275770669fb39fd308a752dc110e83b9af32" +dependencies = [ + "castaway", + "cfg-if", + "itoa", + "rustversion", + "ryu", + "static_assertions", +] + [[package]] name = "compact_str" version = "0.9.0" @@ -988,6 +1002,31 @@ dependencies = [ "thread_local", ] +[[package]] +name = "garde" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3a74b56a4039a46e8c91cc9d84e8a7df4e1f8b24239ca57d1304b3263cb599b9" +dependencies = [ + "compact_str 0.8.1", + "garde_derive", + "once_cell", + "regex", + "smallvec", +] + +[[package]] +name = "garde_derive" +version = "0.22.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7224c08ec489e2840af29ed882b47f7f6ac8f4ce15c275d9fc0d6d1b94578ae6" +dependencies = [ + "proc-macro2", + "quote", + "regex", + "syn 2.0.93", +] + [[package]] name = "generic-array" version = "0.14.7" @@ -2022,7 +2061,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5ef8dea09a92caaf73bff7adb70b76162e5937524058a7e5bff37869cbbec293" dependencies = [ "bitflags 2.10.0", - "compact_str", + "compact_str 0.9.0", "hashbrown", "indoc", "itertools 0.14.0", @@ -2415,6 +2454,7 @@ dependencies = [ "console", "dircpy", "fuzzy-matcher", + "garde", "image", "laurier", "once_cell", diff --git a/Cargo.toml b/Cargo.toml index a853f38..f28d657 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,6 +22,7 @@ chrono = "0.4.42" clap = { version = "4.5.53", features = ["derive"] } console = "0.16.2" fuzzy-matcher = "0.3.7" +garde = { version = "0.22.1", features = ["derive", "regex"] } image = { version = "0.25.9", default-features = false, features = [ "rayon", "png", From ed28821328041e14cc7bab18ac0172f4b8d3a5cb Mon Sep 17 00:00:00 2001 From: Kyosuke Fujimoto Date: Sun, 11 Jan 2026 13:06:52 +0900 Subject: [PATCH 2/2] Add garde validation to config --- src/config.rs | 64 ++++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 51 insertions(+), 13 deletions(-) diff --git a/src/config.rs b/src/config.rs index 8e63c0c..d36a84c 100644 --- a/src/config.rs +++ b/src/config.rs @@ -4,6 +4,7 @@ use std::{ path::{Path, PathBuf}, }; +use garde::Validate; use serde::Deserialize; use smart_default::SmartDefault; use umbra::optional; @@ -50,6 +51,9 @@ pub fn load() -> Result<( } } }?; + + config.validate()?; + Ok(( config.core, config.ui, @@ -78,29 +82,38 @@ fn read_config_from_path(path: &Path) -> Result { } #[optional(derives = [Deserialize])] -#[derive(Debug, Default, Clone, PartialEq, Eq)] +#[derive(Debug, Default, Clone, PartialEq, Eq, Validate)] struct Config { + #[garde(dive)] #[nested] core: CoreConfig, + #[garde(dive)] #[nested] ui: UiConfig, + #[garde(dive)] #[nested] graph: GraphConfig, + #[garde(skip)] #[nested] color: ColorTheme, // The user customed keybinds, please ref `assets/default-keybind.toml` + #[garde(skip)] keybind: Option, } #[optional(derives = [Deserialize])] -#[derive(Debug, Default, Clone, PartialEq, Eq)] +#[derive(Debug, Default, Clone, PartialEq, Eq, Validate)] pub struct CoreConfig { + #[garde(skip)] #[nested] pub option: CoreOptionConfig, + #[garde(skip)] #[nested] pub search: CoreSearchConfig, + #[garde(dive)] #[nested] pub user_command: CoreUserCommandConfig, + #[garde(dive)] #[nested] pub external: CoreExternalConfig, } @@ -125,8 +138,9 @@ pub struct CoreSearchConfig { } #[optional] -#[derive(Debug, Clone, PartialEq, Eq, SmartDefault)] +#[derive(Debug, Clone, PartialEq, Eq, SmartDefault, Validate)] pub struct CoreUserCommandConfig { + #[garde(dive)] #[default(HashMap::from([("1".into(), UserCommand { name: "git diff".into(), commands: vec![ @@ -139,6 +153,7 @@ pub struct CoreUserCommandConfig { ], })]))] pub commands: HashMap, + #[garde(range(min = 0))] #[default = 4] pub tab_width: u16, } @@ -205,23 +220,30 @@ impl<'de> Deserialize<'de> for OptionalCoreUserCommandConfig { } } -#[derive(Debug, Clone, PartialEq, Eq, Deserialize)] +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Validate)] pub struct UserCommand { + #[garde(length(min = 1))] pub name: String, + #[garde(length(min = 1), inner(length(min = 1)))] pub commands: Vec, } #[optional(derives = [Deserialize])] -#[derive(Debug, Default, Clone, PartialEq, Eq)] +#[derive(Debug, Default, Clone, PartialEq, Eq, Validate)] pub struct UiConfig { + #[garde(skip)] #[nested] pub common: UiCommonConfig, + #[garde(dive)] #[nested] pub list: UiListConfig, + #[garde(dive)] #[nested] pub detail: UiDetailConfig, + #[garde(dive)] #[nested] pub user_command: UiUserCommandConfig, + #[garde(dive)] #[nested] pub refs: UiRefsConfig, } @@ -239,72 +261,86 @@ pub enum CursorType { Virtual(String), } -#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Default)] +#[derive(Debug, Clone, PartialEq, Eq, Deserialize, Default, Validate)] pub enum ClipboardConfig { #[default] Auto, Custom { + #[garde(length(min = 1), inner(length(min = 1)))] commands: Vec, }, } #[optional(derives = [Deserialize])] -#[derive(Debug, Clone, PartialEq, Eq, SmartDefault)] +#[derive(Debug, Clone, PartialEq, Eq, SmartDefault, Validate)] pub struct CoreExternalConfig { + #[garde(dive)] #[default(ClipboardConfig::Auto)] pub clipboard: ClipboardConfig, } #[optional(derives = [Deserialize])] -#[derive(Debug, Clone, PartialEq, Eq, SmartDefault)] +#[derive(Debug, Clone, PartialEq, Eq, SmartDefault, Validate)] pub struct UiListConfig { + #[garde(range(min = 1))] #[default = 20] pub subject_min_width: u16, + #[garde(length(min = 1))] #[default = "%Y-%m-%d"] pub date_format: String, + #[garde(range(min = 0))] #[default = 10] pub date_width: u16, + #[garde(skip)] #[default = true] pub date_local: bool, + #[garde(range(min = 0))] #[default = 20] pub name_width: u16, } #[optional(derives = [Deserialize])] -#[derive(Debug, Clone, PartialEq, Eq, SmartDefault)] +#[derive(Debug, Clone, PartialEq, Eq, SmartDefault, Validate)] pub struct UiDetailConfig { + #[garde(range(min = 1))] #[default = 20] pub height: u16, + #[garde(length(min = 1))] #[default = "%Y-%m-%d %H:%M:%S %z"] pub date_format: String, + #[garde(skip)] #[default = true] pub date_local: bool, } #[optional(derives = [Deserialize])] -#[derive(Debug, Clone, PartialEq, Eq, SmartDefault)] +#[derive(Debug, Clone, PartialEq, Eq, SmartDefault, Validate)] pub struct UiUserCommandConfig { + #[garde(range(min = 1))] #[default = 20] pub height: u16, } #[optional(derives = [Deserialize])] -#[derive(Debug, Clone, PartialEq, Eq, SmartDefault)] +#[derive(Debug, Clone, PartialEq, Eq, SmartDefault, Validate)] pub struct UiRefsConfig { + #[garde(range(min = 1))] #[default = 26] pub width: u16, } #[optional(derives = [Deserialize])] -#[derive(Debug, Default, Clone, PartialEq, Eq)] +#[derive(Debug, Default, Clone, PartialEq, Eq, Validate)] pub struct GraphConfig { + #[garde(dive)] #[nested] pub color: GraphColorConfig, } #[optional(derives = [Deserialize])] -#[derive(Debug, Clone, PartialEq, Eq, SmartDefault)] +#[derive(Debug, Clone, PartialEq, Eq, SmartDefault, Validate)] pub struct GraphColorConfig { + #[garde(length(min = 1), inner(pattern(r"^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$")))] #[default(vec![ "#E06C76".into(), "#98C379".into(), @@ -314,8 +350,10 @@ pub struct GraphColorConfig { "#56B6C2".into(), ])] pub branches: Vec, + #[garde(pattern(r"^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$"))] #[default = "#00000000"] pub edge: String, + #[garde(pattern(r"^#([0-9a-fA-F]{6}|[0-9a-fA-F]{8})$"))] #[default = "#00000000"] pub background: String, }