use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum DurableBackend {
#[default]
Local,
Restate,
}
#[allow(clippy::struct_excessive_bools)] #[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct DurableConfig {
pub enabled: bool,
pub backend: DurableBackend,
pub encrypt_payload: bool,
pub agent_turns: bool,
pub orchestration: bool,
pub scheduler: bool,
pub subagent: bool,
pub journal_flush_interval_ms: u64,
pub journal_ack_timeout_ms: u64,
pub max_steps_per_execution: u32,
pub max_payload_bytes: u64,
pub promise_poll_interval_secs: u64,
pub max_parked_promises: u32,
pub retention: RetentionPolicy,
}
impl Default for DurableConfig {
fn default() -> Self {
Self {
enabled: false,
backend: DurableBackend::Local,
encrypt_payload: true,
agent_turns: true,
orchestration: true,
scheduler: true,
subagent: true,
journal_flush_interval_ms: 10,
journal_ack_timeout_ms: 5000,
max_steps_per_execution: 10_000,
max_payload_bytes: 1_048_576,
promise_poll_interval_secs: 2,
max_parked_promises: 1000,
retention: RetentionPolicy::default(),
}
}
}
#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
#[serde(default)]
pub struct RetentionPolicy {
pub ttl_completed_secs: u64,
pub ttl_failed_secs: u64,
pub max_executions: u64,
pub max_journal_bytes: u64,
pub prune_batch_size: u64,
pub prune_interval_secs: u64,
}
impl Default for RetentionPolicy {
fn default() -> Self {
Self {
ttl_completed_secs: 604_800,
ttl_failed_secs: 2_592_000,
max_executions: 10_000,
max_journal_bytes: 1_073_741_824,
prune_batch_size: 500,
prune_interval_secs: 3600,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn empty_table_yields_every_spec_default() {
let cfg: DurableConfig = toml::from_str("").unwrap();
assert!(!cfg.enabled);
assert_eq!(cfg.backend, DurableBackend::Local);
assert!(cfg.encrypt_payload);
assert!(cfg.agent_turns);
assert!(cfg.orchestration);
assert!(cfg.scheduler);
assert!(cfg.subagent);
assert_eq!(cfg.journal_flush_interval_ms, 10);
assert_eq!(cfg.journal_ack_timeout_ms, 5000);
assert_eq!(cfg.max_steps_per_execution, 10_000);
assert_eq!(cfg.max_payload_bytes, 1_048_576);
assert_eq!(cfg.promise_poll_interval_secs, 2);
assert_eq!(cfg.max_parked_promises, 1000);
}
#[test]
fn empty_table_yields_retention_defaults() {
let cfg: DurableConfig = toml::from_str("").unwrap();
assert_eq!(cfg.retention.ttl_completed_secs, 604_800);
assert_eq!(cfg.retention.ttl_failed_secs, 2_592_000);
assert_eq!(cfg.retention.max_executions, 10_000);
assert_eq!(cfg.retention.max_journal_bytes, 1_073_741_824);
assert_eq!(cfg.retention.prune_batch_size, 500);
assert_eq!(cfg.retention.prune_interval_secs, 3600);
}
#[test]
fn default_impl_matches_serde_default() {
let from_toml: DurableConfig = toml::from_str("").unwrap();
assert_eq!(from_toml, DurableConfig::default());
}
#[test]
fn partial_table_overrides_only_named_fields() {
let cfg: DurableConfig = toml::from_str(
r#"
enabled = true
backend = "restate"
[retention]
prune_batch_size = 999
"#,
)
.unwrap();
assert!(cfg.enabled);
assert_eq!(cfg.backend, DurableBackend::Restate);
assert_eq!(cfg.journal_ack_timeout_ms, 5000);
assert_eq!(cfg.retention.prune_batch_size, 999);
assert_eq!(cfg.retention.ttl_completed_secs, 604_800);
}
}