From 7156ba955cff919e5ad3e0cac1bd3cdca676f74f Mon Sep 17 00:00:00 2001 From: Vlad Zagvozdkin Date: Tue, 14 Apr 2026 12:32:20 +0500 Subject: [PATCH 1/4] fix: Make BaseUrl schema inlined --- src/base_url.rs | 16 +++++++++++++++- src/duration.rs | 24 ++++++++++++------------ 2 files changed, 27 insertions(+), 13 deletions(-) diff --git a/src/base_url.rs b/src/base_url.rs index dd86bee..c0e8cd2 100644 --- a/src/base_url.rs +++ b/src/base_url.rs @@ -5,6 +5,8 @@ //! Workaround on [`Url::join`] [behavior](https://github.com/servo/rust-url/issues/333) use std::ops::Deref; +#[cfg(feature = "schemars")] +use schemars::{JsonSchema, Schema, SchemaGenerator}; #[cfg(feature = "serde")] use serde::{de::Error, Deserialize, Deserializer, Serialize}; use thiserror::Error; @@ -24,7 +26,6 @@ use crate::GenericCombinators; /// let foo: Foo = serde_json::from_value(serde_json::json!({"base_url": "http://example.com"})).unwrap(); /// assert_eq!(foo.base_url.as_str(), "http://example.com/"); /// ``` -#[cfg_attr(feature = "schemars", derive(schemars::JsonSchema))] #[derive(Debug, PartialEq, Eq, Clone, Hash)] #[cfg_attr(feature = "serde", derive(Deserialize, Serialize))] #[repr(transparent)] @@ -125,6 +126,19 @@ impl Deref for BaseUrl { } } +#[cfg(feature = "schemars")] +impl JsonSchema for BaseUrl { + fn schema_name() -> std::borrow::Cow<'static, str> { + "BaseUrl".into() + } + fn json_schema(_generator: &mut SchemaGenerator) -> Schema { + schemars::json_schema!({"type": "string", "format": "uri"}) + } + fn inline_schema() -> bool { + true + } +} + /// Add trailing slash to [`Url`] fn add_trailing_slash(url: &mut Url) { if !url.path().ends_with('/') { diff --git a/src/duration.rs b/src/duration.rs index d6954cd..aaa67ba 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -62,20 +62,20 @@ macro_rules! define_generic_wrapper { } } - $( - #[cfg(feature = "schemars")] - impl JsonSchema for $name<$t> { - fn schema_name() -> std::borrow::Cow<'static, str> { - concat!("DurationIn", stringify!($name)).into() - } - fn json_schema(_generator: &mut SchemaGenerator) -> Schema { - schemars::json_schema!({"type": "integer"}) - } - fn inline_schema() -> bool { - true - } + #[cfg(feature = "schemars")] + impl JsonSchema for $name { + fn schema_name() -> std::borrow::Cow<'static, str> { + concat!("DurationIn", stringify!($name)).into() } + fn json_schema(_generator: &mut SchemaGenerator) -> Schema { + schemars::json_schema!({"type": "integer"}) + } + fn inline_schema() -> bool { + true + } + } + $( $( #[cfg(feature = $feat)] )? impl<'de> Deserialize<'de> for $name<$t> { fn deserialize(deserializer: D) -> Result From 5a882778529756bc712ebb66c62b4bbdb104ca21 Mon Sep 17 00:00:00 2001 From: Vlad Zagvozdkin Date: Tue, 14 Apr 2026 13:17:47 +0500 Subject: [PATCH 2/4] fix: Fix some docs --- Cargo.toml | 2 +- src/config.rs | 2 +- src/duration.rs | 2 +- src/lib.rs | 4 ++-- 4 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index f8237ef..e32e3ef 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,6 +16,7 @@ rust-version = "1.78.0" [package.metadata.docs.rs] all-features = true +rustdoc-args = ["--cfg", "docsrs"] [dependencies] figment = { version = "0.10.0", features = [ @@ -117,7 +118,6 @@ same_functions_in_if_condition = "warn" same_name_method = "warn" semicolon_if_nothing_returned = "warn" str_to_string = "warn" -string_to_string = "warn" suboptimal_flops = "warn" suspicious_operation_groupings = "warn" too_many_lines = "warn" diff --git a/src/config.rs b/src/config.rs index 148d8c8..ac72bc5 100644 --- a/src/config.rs +++ b/src/config.rs @@ -78,7 +78,7 @@ fn print_parse_config_errors(env_prefix: &str, error: Box) { /// configuration, which can be confusing if a config file cannot be /// read or is misnamed. /// -/// See [`print_parse_config_errors`] for further edge cases. +/// See internal `print_parse_config_errors` function for further edge cases. #[allow(clippy::print_stderr)] pub fn try_parse_config(env_prefix: &str) -> Result> { if let Some(config_path) = std::env::var_os(format!("{env_prefix}CONFIG")) { diff --git a/src/duration.rs b/src/duration.rs index aaa67ba..c4d040c 100644 --- a/src/duration.rs +++ b/src/duration.rs @@ -128,7 +128,7 @@ macro_rules! define_generic_wrapper { use time::Duration as TimeDuration; define_generic_wrapper! { - "`Duration` wrapper with [`Deserialize`] impl", + "Helper wrapper to use in configs to deserialize durations from seconds", Seconds: { diff --git a/src/lib.rs b/src/lib.rs index de59fb3..9207bbf 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ // // SPDX-License-Identifier: Apache-2.0 -#![cfg_attr(all(doc, not(doctest)), feature(doc_cfg))] +#![cfg_attr(all(docsrs, not(doctest)), feature(doc_cfg))] //! This crate consists of incohesive generic types and functions that are //! needed in almost every crate but are so small that making a separate crate //! for them is too much. @@ -208,7 +208,7 @@ impl IteratorExt for I { } } -/// Helper wrapper to serialize types as strings using [`FromStr`] and +/// Helper wrapper to serialize types as strings using [`std::str::FromStr`] and /// [`ToString`]. #[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Default)] pub struct AsString(pub X); From 4153800f0bf03920f72cd2645a008129fa0a8319 Mon Sep 17 00:00:00 2001 From: Vlad Zagvozdkin Date: Tue, 14 Apr 2026 13:35:55 +0500 Subject: [PATCH 3/4] feat: Add RFC3339 Timestamp --- Cargo.lock | 13 ++++++++ Cargo.toml | 2 +- src/lib.rs | 2 ++ src/timestamp.rs | 81 ++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 97 insertions(+), 1 deletion(-) create mode 100644 src/timestamp.rs diff --git a/Cargo.lock b/Cargo.lock index 3402479..74e707f 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -162,6 +162,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ececcb659e7ba858fb4f10388c250a7252eb0a27373f1a72b8748afdd248e587" dependencies = [ "powerfmt", + "serde_core", ] [[package]] @@ -1429,10 +1430,12 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "91e7d9e3bb61134e77bde20dd4825b97c010155709965fedf0f49bb138e52a9d" dependencies = [ "deranged", + "itoa", "num-conv", "powerfmt", "serde", "time-core", + "time-macros", ] [[package]] @@ -1441,6 +1444,16 @@ version = "0.1.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "40868e7c1d2f0b8d73e4a8c7f0ff63af4f6d19be117e90bd73eb1d62cf831c6b" +[[package]] +name = "time-macros" +version = "0.2.24" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "30cfb0125f12d9c277f35663a0a33f8c30190f4e4574868a330595412d34ebf3" +dependencies = [ + "num-conv", + "time-core", +] + [[package]] name = "tinystr" version = "0.8.2" diff --git a/Cargo.toml b/Cargo.toml index e32e3ef..4cc9dff 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -41,7 +41,7 @@ serde_json = "1.0.127" config = ["dep:figment", "dep:serde"] level_filter = ["dep:tracing", "dep:serde"] reqwest = ["dep:reqwest", "dep:thiserror"] -time = ["dep:time"] +time = ["dep:time", "time/serde", "time/parsing", "time/formatting"] schemars = ["dep:schemars", "schemars/url2"] serde = ["dep:serde", "dep:paste"] base_url = ["dep:url", "dep:thiserror", "dep:serde"] diff --git a/src/lib.rs b/src/lib.rs index 9207bbf..57ba573 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,6 +37,8 @@ pub mod duration; mod level_filter; #[cfg(feature = "reqwest")] pub mod reqwest; +#[cfg(all(feature = "time", feature = "serde"))] +pub mod timestamp; #[cfg(feature = "base_url")] pub use base_url::{BaseUrl, BaseUrlParseError}; diff --git a/src/timestamp.rs b/src/timestamp.rs new file mode 100644 index 0000000..bc9936f --- /dev/null +++ b/src/timestamp.rs @@ -0,0 +1,81 @@ +// SPDX-FileCopyrightText: 2026 Famedly GmbH (info@famedly.com) +// +// SPDX-License-Identifier: Apache-2.0 + +//! Wrapper over [`OffsetDateTime`] with RFC3339 JSON representation + +#![allow(missing_docs, unused_qualifications)] + +use core::{fmt, str}; + +#[cfg(feature = "schemars")] +use schemars::{JsonSchema, Schema, SchemaGenerator}; +use serde::{Deserialize, Serialize}; +use time::OffsetDateTime; + +/// Timestamp with RFC3339 JSON representation +#[derive(Debug, PartialEq, Eq, PartialOrd, Ord, Clone, Copy, Serialize, Deserialize)] +#[serde(transparent)] +#[repr(transparent)] +pub struct Timestamp { + #[serde(with = "time::serde::rfc3339")] + ts: OffsetDateTime, +} + +impl AsRef for Timestamp { + fn as_ref(&self) -> &OffsetDateTime { + &self.ts + } +} + +impl std::ops::Deref for Timestamp { + type Target = OffsetDateTime; + fn deref(&self) -> &Self::Target { + &self.ts + } +} + +impl fmt::Display for Timestamp { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { + use time::format_description::well_known::Rfc3339; + let ts = self.ts.format(&Rfc3339).map_err(|_| fmt::Error)?; + write!(f, "{}", ts) + } +} + +impl str::FromStr for Timestamp { + type Err = time::error::Parse; + fn from_str(s: &str) -> Result { + use time::format_description::well_known::Rfc3339; + let ts = time::OffsetDateTime::parse(s, &Rfc3339)?; + Ok(Self { ts }) + } +} + +#[cfg(feature = "schemars")] +impl JsonSchema for Timestamp { + fn schema_name() -> std::borrow::Cow<'static, str> { + "Timestamp".into() + } + fn json_schema(_generator: &mut SchemaGenerator) -> Schema { + schemars::json_schema!({ + "type": "string", + "format": "date-time", + }) + } + fn inline_schema() -> bool { + true + } +} + +impl From for Timestamp { + fn from(ts: OffsetDateTime) -> Self { + Self { ts } + } +} + +impl From for OffsetDateTime { + fn from(ts: Timestamp) -> Self { + ts.ts + } +} From 1086625db210dafa5a4a91941787d67b099db062 Mon Sep 17 00:00:00 2001 From: Vlad Zagvozdkin Date: Tue, 14 Apr 2026 13:46:10 +0500 Subject: [PATCH 4/4] release: v1.1.1 --- CHANGELOG.md | 15 +++++++++++++++ Cargo.lock | 2 +- Cargo.toml | 2 +- 3 files changed, 17 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50d24a4..67a9ef4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,21 @@ SPDX-License-Identifier: Apache-2.0 All notable changes to this project will be documented in this file. +## [1.1.1] - 2026-04-14 + +### Features + +- Add RFC3339 Timestamp + +### Bug Fixes + +- Make BaseUrl schema inlined +- Fix some docs + +### Documentation + +- Replace doc_auto_cfg with doc_cfg + ## [1.0.2] - 2025-08-01 ### Features diff --git a/Cargo.lock b/Cargo.lock index 74e707f..4e59c2a 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -215,7 +215,7 @@ dependencies = [ [[package]] name = "famedly_rust_utils" -version = "1.1.0" +version = "1.1.1" dependencies = [ "dedent", "figment", diff --git a/Cargo.toml b/Cargo.toml index 4cc9dff..5b6a984 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ [package] name = "famedly_rust_utils" description = "Various rust utility functions and types" -version = "1.1.0" +version = "1.1.1" authors = ["Famedly Workflows Team "] edition = "2021" resolver = "2"