Skip to content

Commit 492593f

Browse files
committed
Fix config parsing
1 parent edff63b commit 492593f

19 files changed

+394
-291
lines changed

CHANGELOG.md

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,21 @@ All notable changes to this project will be documented in this file.
55
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
66
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
77

8+
## [1.13.1] - 2025-07-17
9+
10+
### Fixed
11+
- **🧪 Unit Test Compilation and TOML Serialization**
12+
- Fixed compilation errors in `key_management_example.rs` by adding required `encryption` feature flag
13+
- Resolved TOML serialization failures for configuration structs with proper serde attribute handling
14+
- Fixed missing optional field handling in TOML deserialization by adding `skip_serializing_if` and `default` attributes
15+
- Corrected `exposition_addr` field serialization in `MetricsConfig` to handle `None` values properly
16+
- Fixed `backoff_on_error` field serialization in `ThrottleConfig` for proper TOML compatibility
17+
- Updated `test_duration_serialization` test to use current configuration structure
18+
- Fixed enum deserialization for `RetryStrategy` and `JitterType` using flat TOML structure with `type` field
19+
- Resolved u128 serialization issues by converting `Duration.as_millis()` to u64 for TOML compatibility
20+
- Removed unused UUID serialization functions to eliminate compiler warnings
21+
- All 263 unit tests now pass successfully with no compilation errors or warnings
22+
823
## [1.13.0] - 2025-07-16
924

1025
### Fixed

Cargo.toml

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ members = [
99
resolver = "2"
1010

1111
[workspace.package]
12-
version = "1.13.0"
12+
version = "1.13.1"
1313
edition = "2024"
1414
license = "MIT"
1515
repository = "https://github.com/CodingAnarchy/hammerwork"
@@ -19,7 +19,7 @@ documentation = "https://docs.rs/hammerwork"
1919
rust-version = "1.86"
2020

2121
[workspace.dependencies]
22-
hammerwork = { version = "1.12.0", path = "." }
22+
hammerwork = { version = "1.13.1", path = "." }
2323
tokio = { version = "1.0", features = ["full"] }
2424
sqlx = { version = "0.8", features = ["runtime-tokio-rustls", "chrono", "uuid", "json"] }
2525
chrono = { version = "0.4", features = ["serde"] }
@@ -184,3 +184,11 @@ required-features = ["encryption"]
184184
name = "vault_kms_encryption_example"
185185
required-features = ["encryption"]
186186

187+
[[example]]
188+
name = "key_management_example"
189+
required-features = ["encryption"]
190+
191+
[[test]]
192+
name = "encryption_basic_tests"
193+
required-features = ["encryption"]
194+

examples/spawn_cli_example.rs

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,12 +10,8 @@
1010
//! cargo run --example spawn_cli_example --features mysql
1111
//! ```
1212
13-
use hammerwork::{Job, JobQueue, Result, queue::DatabaseQueue};
13+
use hammerwork::Result;
1414
use serde::{Deserialize, Serialize};
15-
use serde_json::json;
16-
use std::time::Duration;
17-
use tokio::time::sleep;
18-
use uuid::Uuid;
1915

2016
#[derive(Debug, Clone, Serialize, Deserialize)]
2117
struct DataProcessingJob {

src/config.rs

Lines changed: 146 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ use crate::{
1010
};
1111

1212
#[cfg(feature = "webhooks")]
13-
use crate::{streaming::StreamBackend, webhooks::WebhookConfig};
13+
use crate::webhooks::WebhookConfig;
1414

1515
#[cfg(feature = "alerting")]
1616
use crate::alerting::AlertingConfig;
@@ -86,29 +86,90 @@ mod duration_secs {
8686
}
8787
}
8888

89-
/// Module for serializing UUID as string for TOML compatibility
90-
mod uuid_string {
89+
/// Module for serializing chrono::Duration as human-readable strings (in days)
90+
mod chrono_duration_days {
91+
use chrono::Duration;
9192
use serde::{Deserialize, Deserializer, Serializer};
92-
use uuid::Uuid;
9393

94-
pub fn serialize<S>(uuid: &Uuid, serializer: S) -> Result<S::Ok, S::Error>
94+
pub fn serialize<S>(duration: &Duration, serializer: S) -> Result<S::Ok, S::Error>
9595
where
9696
S: Serializer,
9797
{
98-
serializer.serialize_str(&uuid.to_string())
98+
let days = duration.num_days();
99+
if days == 0 {
100+
serializer.serialize_str("0d")
101+
} else {
102+
serializer.serialize_str(&format!("{}d", days))
103+
}
99104
}
100105

101-
pub fn deserialize<'de, D>(deserializer: D) -> Result<Uuid, D::Error>
106+
pub fn deserialize<'de, D>(deserializer: D) -> Result<Duration, D::Error>
102107
where
103108
D: Deserializer<'de>,
104109
{
105110
use serde::de::Error;
111+
106112
let s = String::deserialize(deserializer)?;
107-
Uuid::parse_str(&s).map_err(D::Error::custom)
113+
parse_chrono_duration(&s).map_err(D::Error::custom)
114+
}
115+
116+
/// Parse a duration string like "30d", "7d", etc.
117+
pub fn parse_chrono_duration(s: &str) -> Result<Duration, String> {
118+
let s = s.trim();
119+
120+
// Handle just numbers (assume days)
121+
if let Ok(days) = s.parse::<i64>() {
122+
return Ok(Duration::days(days));
123+
}
124+
125+
// Handle suffixed durations
126+
if s.len() < 2 {
127+
return Err(format!("Invalid duration format: {}", s));
128+
}
129+
130+
let (num_str, suffix) = s.split_at(s.len() - 1);
131+
let num: i64 = num_str
132+
.parse()
133+
.map_err(|_| format!("Invalid number in duration: {}", num_str))?;
134+
135+
match suffix {
136+
"d" => Ok(Duration::days(num)),
137+
_ => Err(format!(
138+
"Invalid duration suffix: {}. Use d for days",
139+
suffix
140+
)),
141+
}
108142
}
109143
}
110144

111-
use uuid::Uuid;
145+
/// Module for serializing Option<chrono::Duration> as human-readable strings (in days)
146+
mod chrono_duration_days_option {
147+
use chrono::Duration;
148+
use serde::{Deserialize, Deserializer, Serializer};
149+
150+
pub fn serialize<S>(duration: &Option<Duration>, serializer: S) -> Result<S::Ok, S::Error>
151+
where
152+
S: Serializer,
153+
{
154+
match duration {
155+
Some(d) => super::chrono_duration_days::serialize(d, serializer),
156+
None => serializer.serialize_none(),
157+
}
158+
}
159+
160+
pub fn deserialize<'de, D>(deserializer: D) -> Result<Option<Duration>, D::Error>
161+
where
162+
D: Deserializer<'de>,
163+
{
164+
let opt: Option<String> = Option::deserialize(deserializer)?;
165+
match opt {
166+
Some(s) => super::chrono_duration_days::parse_chrono_duration(&s)
167+
.map(Some)
168+
.map_err(serde::de::Error::custom),
169+
None => Ok(None),
170+
}
171+
}
172+
}
112173

113174
/// Main configuration for the Hammerwork job queue system.
114175
///
@@ -443,9 +504,11 @@ pub struct ArchiveConfig {
443504
pub compression_level: u32,
444505

445506
/// Archive jobs older than this duration
507+
#[serde(with = "chrono_duration_days")]
446508
pub archive_after: Duration,
447509

448510
/// Delete archived files older than this duration
511+
#[serde(with = "chrono_duration_days_option")]
449512
pub delete_after: Option<Duration>,
450513

451514
/// Maximum archive file size in bytes
@@ -604,6 +667,7 @@ impl HammerworkConfig {
604667
#[cfg(test)]
605668
mod tests {
606669
use super::*;
670+
use crate::streaming::StreamBackend;
607671
use tempfile::tempdir;
608672

609673
#[test]
@@ -648,6 +712,10 @@ mod tests {
648712
.with_worker_pool_size(6);
649713

650714
// Save config
715+
println!("Testing TOML serialization...");
716+
let toml_result = toml::to_string_pretty(&config);
717+
println!("TOML result: {:?}", toml_result);
718+
651719
config.save_to_file(config_path.to_str().unwrap()).unwrap();
652720

653721
// Load config
@@ -724,6 +792,10 @@ mod tests {
724792
r#"
725793
[database]
726794
url = "postgresql://localhost/test"
795+
pool_size = 10
796+
connection_timeout_secs = 30
797+
auto_migrate = false
798+
create_tables = true
727799
728800
[worker]
729801
pool_size = 4
@@ -732,42 +804,89 @@ job_timeout = "5m"
732804
autoscaling_enabled = false
733805
min_workers = 1
734806
max_workers = 10
735-
scale_up_threshold = 0.8
736-
scale_down_threshold = 0.2
737-
scale_check_interval = "30s"
738807
739808
[worker.priority_weights]
740-
background = 1
741-
low = 2
742-
normal = 5
743-
high = 10
744-
critical = 20
809+
strict_priority = false
810+
fairness_factor = 0.1
811+
812+
[worker.priority_weights.weights]
813+
Background = 1
814+
Low = 2
815+
Normal = 5
816+
High = 10
817+
Critical = 20
745818
746819
[worker.retry_strategy]
747-
max_attempts = 3
748-
initial_delay = "1s"
749-
max_delay = "60s"
750-
backoff_multiplier = 2.0
820+
type = "Exponential"
821+
base_ms = 1000
822+
multiplier = 2.0
823+
max_delay_ms = 60000
751824
752825
[events]
753-
enabled = true
754-
buffer_size = 1000
826+
max_buffer_size = 1000
827+
include_payload_default = false
828+
max_payload_size_bytes = 65536
829+
log_events = false
830+
831+
[webhooks]
832+
webhooks = []
833+
834+
[webhooks.global_settings]
835+
max_concurrent_deliveries = 100
836+
max_response_body_size = 65536
837+
log_deliveries = true
838+
user_agent = "hammerwork-webhooks/1.13.0"
755839
756840
[streaming]
841+
streams = []
842+
843+
[streaming.global_settings]
844+
max_concurrent_processors = 50
845+
log_operations = true
846+
global_flush_interval_secs = 10
847+
848+
[alerting]
849+
targets = []
850+
enabled = true
851+
852+
[alerting.cooldown_period]
853+
secs = 300
854+
nanos = 0
855+
856+
[alerting.custom_thresholds]
857+
858+
[metrics]
859+
registry_name = "hammerwork"
860+
collect_histograms = true
861+
custom_gauges = []
862+
custom_histograms = []
863+
update_interval = 15
864+
865+
[metrics.custom_labels]
757866
758867
[archive]
759868
enabled = false
760-
retention_days = 30
761-
compression_enabled = false
869+
archive_directory = "./archives"
870+
compression_level = 6
871+
archive_after = "30d"
872+
delete_after = "365d"
873+
max_file_size_bytes = 104857600
874+
include_payloads = true
762875
763876
[rate_limiting]
764877
enabled = false
765-
requests_per_second = 100
766-
burst_size = 200
878+
879+
[rate_limiting.default_throttle]
880+
enabled = true
881+
882+
[rate_limiting.queue_throttles]
767883
768884
[logging]
769885
level = "info"
770886
json_format = false
887+
include_location = false
888+
enable_tracing = false
889+
service_name = "hammerwork"
771890
"#,
772891
duration_str
773892
);

src/encryption/key_manager.rs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -390,7 +390,7 @@ impl KeyManagerConfig {
390390
}
391391
}
392392

393-
impl<DB: Database> KeyManager<DB>
393+
impl<DB: Database> KeyManager<DB>
394394
where
395395
for<'c> &'c mut DB::Connection: sqlx::Executor<'c, Database = DB>,
396396
for<'q> <DB as sqlx::Database>::Arguments<'q>: sqlx::IntoArguments<'q, DB>,
@@ -683,8 +683,6 @@ where
683683
Ok(keys_due.contains(&key_id.to_string()))
684684
}
685685

686-
687-
688686
/// Start automated key rotation service that runs in the background
689687
/// Returns a future that should be spawned as a background task
690688
pub async fn start_rotation_service(
@@ -745,8 +743,6 @@ where
745743
Ok(rotation_service)
746744
}
747745

748-
749-
750746
/// Get current key management statistics
751747
pub async fn get_stats(&self) -> KeyManagerStats {
752748
self.stats
@@ -2556,8 +2552,7 @@ impl KeyManager<sqlx::MySql> {
25562552
from_time: DateTime<Utc>,
25572553
to_time: DateTime<Utc>,
25582554
) -> Result<Vec<(String, DateTime<Utc>)>, EncryptionError> {
2559-
self.get_scheduled_rotations_mysql(from_time, to_time)
2560-
.await
2555+
self.get_scheduled_rotations_mysql(from_time, to_time).await
25612556
}
25622557

25632558
/// Query statistics from MySQL

src/metrics.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,9 @@ pub struct MetricsConfig {
6363
/// HTTP server address for metrics exposition (as string)
6464
#[serde(
6565
serialize_with = "serialize_socket_addr",
66-
deserialize_with = "deserialize_socket_addr"
66+
deserialize_with = "deserialize_socket_addr",
67+
skip_serializing_if = "Option::is_none",
68+
default
6769
)]
6870
pub exposition_addr: Option<SocketAddr>,
6971
/// Custom metric labels to include

src/rate_limit.rs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,9 @@ pub struct ThrottleConfig {
8484
/// Backoff duration when errors occur (in seconds)
8585
#[serde(
8686
serialize_with = "serialize_duration",
87-
deserialize_with = "deserialize_duration"
87+
deserialize_with = "deserialize_duration",
88+
skip_serializing_if = "Option::is_none",
89+
default
8890
)]
8991
pub backoff_on_error: Option<Duration>,
9092
/// Enable/disable throttling

0 commit comments

Comments
 (0)