From 549c6c3b291f76fc8f03303d136387e52bf524da Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Ma=C4=87kowski?= Date: Sun, 22 Mar 2026 17:22:45 +0100 Subject: [PATCH] feat: allow accessing extra config --- cot/src/config.rs | 62 ++++++++++++++++++++++++++++++++++++++++++++++- cot/src/lib.rs | 1 + 2 files changed, 62 insertions(+), 1 deletion(-) diff --git a/cot/src/config.rs b/cot/src/config.rs index 218245e9..fa13f5b7 100644 --- a/cot/src/config.rs +++ b/cot/src/config.rs @@ -34,7 +34,7 @@ use crate::utils::chrono::DateTimeWithOffsetAdapter; /// /// This is all the project-specific configuration data that can (and makes /// sense to) be expressed in a TOML configuration file. -#[derive(Debug, Clone, PartialEq, Eq, Builder, Serialize, Deserialize)] +#[derive(Debug, Clone, Builder, Serialize, Deserialize)] #[builder(build_fn(skip, error = std::convert::Infallible))] #[serde(default)] #[non_exhaustive] @@ -271,6 +271,40 @@ pub struct ProjectConfig { /// ``` #[cfg(feature = "email")] pub email: EmailConfig, + /// All the config that was not recognized. + /// + /// This is useful for parsing project-specific config that is not part of + /// any of the predefined fields in `ProjectConfig`. + /// + /// # Examples + /// + /// ``` + /// use cot::config::ProjectConfig; + /// use serde::Deserialize; + /// + /// let config = ProjectConfig::from_toml( + /// r#" + /// [my_cot_project] + /// foo = "bar" + /// "#, + /// )?; + /// + /// #[derive(Deserialize)] + /// struct MyCotProjectConfig { + /// foo: String, + /// } + /// + /// let my_config: MyCotProjectConfig = config + /// .extra + /// .get("my_cot_project") + /// .ok_or("could not find the project config")? + /// .clone() + /// .try_into()?; + /// assert_eq!(my_config.foo, "bar"); + /// # Ok::<(), Box>(()) + /// ``` + #[serde(flatten)] + pub extra: toml::Table, } const fn default_debug() -> bool { @@ -381,6 +415,7 @@ impl ProjectConfigBuilder { middlewares: self.middlewares.clone().unwrap_or_default(), #[cfg(feature = "email")] email: self.email.clone().unwrap_or_default(), + extra: Default::default(), } } } @@ -3116,4 +3151,29 @@ mod tests { assert_eq!(u1, u2); assert_eq!(u1.as_str(), s); } + + #[test] + fn config_extra_can_be_accessed() { + #[derive(Deserialize)] + struct CustomConfig { + foo: String, + } + + let config = ProjectConfig::from_toml( + r#" + [custom_config] + foo = "bar" + "#, + ) + .unwrap(); + + let my_config: CustomConfig = config + .extra + .get("custom_config") + .unwrap() + .clone() + .try_into() + .unwrap(); + assert_eq!(my_config.foo, "bar"); + } } diff --git a/cot/src/lib.rs b/cot/src/lib.rs index 22ea64d5..1479cf2d 100644 --- a/cot/src/lib.rs +++ b/cot/src/lib.rs @@ -185,6 +185,7 @@ pub use cot_macros::test; pub use http; #[cfg(feature = "openapi")] pub use schemars; +pub use toml; pub use crate::__private::askama::{Template, filter_fn}; pub use crate::project::{