Skip to content

Commit f58196a

Browse files
committed
feat(trogon-cron): harden job modeling and scheduling
1 parent a55df6b commit f58196a

8 files changed

Lines changed: 816 additions & 242 deletions

File tree

rsworkspace/crates/trogon-cron/Cargo.toml

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
name = "trogon-cron"
33
version = "0.1.0"
44
edition = "2024"
5+
autotests = false
56

67
[features]
78
test-support = []
@@ -35,3 +36,12 @@ uuid = { version = "1", features = ["v4"] }
3536
[[bin]]
3637
name = "trogon-cron"
3738
path = "src/main.rs"
39+
40+
[[test]]
41+
name = "cron_unit"
42+
path = "tests/cron_unit.rs"
43+
required-features = ["test-support"]
44+
45+
[[test]]
46+
name = "integration"
47+
path = "tests/integration.rs"

rsworkspace/crates/trogon-cron/src/client.rs

Lines changed: 7 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
11
use async_nats::jetstream;
22

33
use crate::{
4-
config::JobConfig, error::CronError, nats_impls::NatsConfigStore, traits::ConfigStore,
4+
config::JobConfig, domain::RegisteredJob, error::CronError, nats_impls::NatsConfigStore,
5+
traits::ConfigStore,
56
};
67

78
/// Client for registering and managing CRON job configs.
@@ -50,44 +51,12 @@ impl<C: ConfigStore> CronClient<C> {
5051

5152
/// Register or update a job. Existing jobs with the same `id` are overwritten.
5253
///
53-
/// Structural constraints on `Action::Spawn` configs are validated here so
54-
/// callers get an immediate error rather than a silent scheduler-side skip.
55-
/// Filesystem checks (file exists, is executable) are intentionally omitted
56-
/// because the client may run on a different host than the scheduler.
54+
/// Structural config constraints are validated here so callers get an
55+
/// immediate error rather than a silent scheduler-side skip. Filesystem
56+
/// checks for spawned binaries are intentionally omitted because the client
57+
/// may run on a different host than the scheduler.
5758
pub async fn register_job(&self, config: &JobConfig) -> Result<(), CronError> {
58-
if let crate::config::Action::Publish { subject } = &config.action {
59-
if !subject.starts_with("cron.") {
60-
return Err(CronError::InvalidJobConfig {
61-
reason: format!("publish subject must start with 'cron.', got: {subject}"),
62-
});
63-
}
64-
}
65-
if let crate::config::Action::Spawn {
66-
bin,
67-
args,
68-
timeout_sec,
69-
..
70-
} = &config.action
71-
{
72-
if !std::path::Path::new(bin).is_absolute() {
73-
return Err(CronError::InvalidJobConfig {
74-
reason: format!("bin must be an absolute path, got: {bin}"),
75-
});
76-
}
77-
for arg in args {
78-
if arg.contains('\0') {
79-
return Err(CronError::InvalidJobConfig {
80-
reason: format!("argument contains null byte: {arg:?}"),
81-
});
82-
}
83-
}
84-
if timeout_sec.is_some_and(|s| s == 0) {
85-
return Err(CronError::InvalidJobConfig {
86-
reason: "timeout_sec must be >= 1 when set".into(),
87-
});
88-
}
89-
}
90-
59+
let _ = RegisteredJob::try_from(config)?;
9160
self.store.put_job(config).await?;
9261
tracing::info!(job_id = %config.id, "Job registered");
9362
Ok(())

rsworkspace/crates/trogon-cron/src/config.rs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,9 @@ pub struct RetryConfig {
4747
/// Must be >= 1.
4848
#[serde(default = "default_retry_backoff_sec")]
4949
pub retry_backoff_sec: u64,
50-
/// Hard ceiling on the per-attempt delay in seconds. Overrides the implicit 32×
51-
/// cap when set to a lower value. Must be >= `retry_backoff_sec` and >= 1.
50+
/// Hard ceiling on the per-attempt delay in seconds. Overrides the implicit 32x
51+
/// cap when set. It may be lower than `retry_backoff_sec`, which clamps every
52+
/// retry delay to `max_backoff_sec`. Must be >= 1 when set.
5253
#[serde(default, skip_serializing_if = "Option::is_none")]
5354
pub max_backoff_sec: Option<u64>,
5455
/// Total time budget in seconds for all retry attempts combined.

0 commit comments

Comments
 (0)