use crate::backoff::Backoff;
use std::{
num::{NonZeroU32, NonZeroUsize},
time::Duration,
};
#[derive(Debug, Default, Clone, Copy)]
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize),
serde(default, deny_unknown_fields)
)]
pub struct ActorConfig {
pub mailbox_capacity: MailboxCapacity,
pub supervision_strategy: SupervisionStrategy,
}
impl ActorConfig {
pub fn with_mailbox_capacity(self, mailbox_capacity: MailboxCapacity) -> Self {
Self {
mailbox_capacity,
..self
}
}
pub fn with_supervision_strategy(self, supervision_strategy: SupervisionStrategy) -> Self {
Self {
supervision_strategy,
..self
}
}
}
#[derive(Debug, Default, Clone, Copy)]
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize),
serde(rename_all = "snake_case")
)]
pub enum MailboxCapacity {
#[default]
Unbounded,
Bounded(NonZeroUsize),
}
#[derive(Debug, Default, Clone, Copy)]
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize),
serde(rename_all = "snake_case")
)]
pub enum SupervisionStrategy {
#[default]
Stop,
Restart(RestartPolicy),
}
#[derive(Debug, Clone, Copy)]
#[cfg_attr(
feature = "serde",
derive(serde::Deserialize),
serde(deny_unknown_fields)
)]
pub struct RestartPolicy {
pub max_restarts: NonZeroU32,
#[cfg_attr(feature = "serde", serde(default))]
pub backoff: Backoff,
#[cfg_attr(
feature = "serde",
serde(default = "default_reset_after", with = "humantime_serde")
)]
pub reset_after: Duration,
}
impl RestartPolicy {
pub const DEFAULT_RESET_AFTER: Duration = Duration::from_secs(30);
pub fn new(max_restarts: NonZeroU32) -> Self {
Self {
max_restarts,
backoff: Backoff::default(),
reset_after: Self::DEFAULT_RESET_AFTER,
}
}
pub fn with_backoff(self, backoff: Backoff) -> Self {
Self { backoff, ..self }
}
pub fn with_reset_after(self, reset_after: Duration) -> Self {
Self {
reset_after,
..self
}
}
}
#[cfg(feature = "serde")]
fn default_reset_after() -> Duration {
RestartPolicy::DEFAULT_RESET_AFTER
}
#[cfg(test)]
mod tests {
use crate::{ActorConfig, Backoff, MailboxCapacity, RestartPolicy, SupervisionStrategy};
use std::{
num::{NonZeroU32, NonZeroUsize},
time::Duration,
};
#[test]
fn a_new_policy_is_paced_by_the_defaults() {
let policy = RestartPolicy::new(NonZeroU32::MIN);
assert_eq!(policy.backoff.min(), Backoff::DEFAULT_MIN);
assert_eq!(policy.backoff.max(), Backoff::DEFAULT_MAX);
assert_eq!(policy.reset_after, RestartPolicy::DEFAULT_RESET_AFTER);
}
#[test]
fn a_config_setter_only_overwrites_its_own_field() {
let config = ActorConfig::default()
.with_mailbox_capacity(MailboxCapacity::Bounded(NonZeroUsize::MIN))
.with_supervision_strategy(SupervisionStrategy::Restart(RestartPolicy::new(
NonZeroU32::MIN,
)));
assert!(matches!(
config.mailbox_capacity,
MailboxCapacity::Bounded(capacity) if capacity == NonZeroUsize::MIN
));
assert!(matches!(
config.supervision_strategy,
SupervisionStrategy::Restart(_)
));
}
#[test]
fn a_policy_setter_only_overwrites_its_own_field() {
let backoff = Backoff::new(Duration::ZERO, Duration::ZERO).expect("the bounds are ordered");
let policy = RestartPolicy::new(NonZeroU32::MIN)
.with_backoff(backoff)
.with_reset_after(Duration::ZERO);
assert_eq!(policy.max_restarts, NonZeroU32::MIN);
assert_eq!(policy.backoff.min(), Duration::ZERO);
assert_eq!(policy.backoff.max(), Duration::ZERO);
assert_eq!(policy.reset_after, Duration::ZERO);
}
#[cfg(feature = "serde")]
#[test]
fn a_config_deserializes_from_its_documented_form() {
let config = serde_json::from_str::<ActorConfig>(
r#"{
"mailbox_capacity": { "bounded": 42 },
"supervision_strategy": { "restart": { "max_restarts": 3, "reset_after": "1m" } }
}"#,
)
.expect("the documented config form deserializes");
assert!(matches!(
config.mailbox_capacity,
MailboxCapacity::Bounded(capacity) if capacity.get() == 42
));
let SupervisionStrategy::Restart(policy) = config.supervision_strategy else {
panic!("expected a restart strategy")
};
assert_eq!(policy.max_restarts.get(), 3);
assert_eq!(policy.reset_after, Duration::from_secs(60));
assert_eq!(policy.backoff.min(), Backoff::DEFAULT_MIN);
assert_eq!(policy.backoff.max(), Backoff::DEFAULT_MAX);
}
#[cfg(feature = "serde")]
#[test]
fn deserializing_rejects_unknown_fields() {
let config =
serde_json::from_str::<ActorConfig>(r#"{ "mailbox_capacty": { "bounded": 42 } }"#);
assert!(config.is_err());
let config = serde_json::from_str::<ActorConfig>(
r#"{
"supervision_strategy": {
"restart": { "max_restarts": 3, "reset_atfer": "1m" }
}
}"#,
);
assert!(config.is_err());
}
}