use std::num::NonZeroUsize;
use crate::{ConfigError, core::validate_async_capacity};
const DEFAULT_QUEUE_CAPACITY: NonZeroUsize = NonZeroUsize::new(1024).unwrap();
const DEFAULT_MAX_SLOT_QUEUE: usize = 100;
#[derive(Clone, Debug)]
#[must_use]
pub struct ControllerConfig {
queue_capacity: NonZeroUsize,
admission_capacity: NonZeroUsize,
identity_operation_capacity: NonZeroUsize,
max_slot_queue: usize,
max_controller_slots: Option<NonZeroUsize>,
max_total_pending: Option<NonZeroUsize>,
}
impl ControllerConfig {
pub const fn new(queue_capacity: NonZeroUsize, max_slot_queue: usize) -> Self {
Self {
queue_capacity,
admission_capacity: queue_capacity,
identity_operation_capacity: queue_capacity,
max_slot_queue,
max_controller_slots: Some(queue_capacity),
max_total_pending: Some(queue_capacity),
}
}
pub fn try_new(queue_capacity: usize, max_slot_queue: usize) -> Result<Self, ConfigError> {
let queue_capacity = NonZeroUsize::new(queue_capacity).ok_or(ConfigError::Zero {
field: "controller_queue_capacity",
})?;
validate_async_capacity("controller_queue_capacity", queue_capacity)?;
Ok(Self::new(queue_capacity, max_slot_queue))
}
#[must_use]
pub const fn queue_capacity(&self) -> NonZeroUsize {
self.queue_capacity
}
#[must_use]
pub const fn admission_capacity(&self) -> NonZeroUsize {
self.admission_capacity
}
#[must_use]
pub const fn identity_operation_capacity(&self) -> NonZeroUsize {
self.identity_operation_capacity
}
#[must_use]
pub const fn max_slot_queue(&self) -> usize {
self.max_slot_queue
}
#[must_use]
pub const fn max_controller_slots(&self) -> Option<NonZeroUsize> {
self.max_controller_slots
}
#[must_use]
pub const fn max_total_pending(&self) -> Option<NonZeroUsize> {
self.max_total_pending
}
pub const fn with_queue_capacity(mut self, queue_capacity: NonZeroUsize) -> Self {
self.queue_capacity = queue_capacity;
self
}
pub fn try_with_queue_capacity(self, queue_capacity: usize) -> Result<Self, ConfigError> {
let queue_capacity = NonZeroUsize::new(queue_capacity).ok_or(ConfigError::Zero {
field: "controller_queue_capacity",
})?;
validate_async_capacity("controller_queue_capacity", queue_capacity)?;
Ok(self.with_queue_capacity(queue_capacity))
}
pub const fn with_admission_capacity(mut self, admission_capacity: NonZeroUsize) -> Self {
self.admission_capacity = admission_capacity;
self
}
pub fn try_with_admission_capacity(
self,
admission_capacity: usize,
) -> Result<Self, ConfigError> {
let admission_capacity =
NonZeroUsize::new(admission_capacity).ok_or(ConfigError::Zero {
field: "controller_admission_capacity",
})?;
Ok(self.with_admission_capacity(admission_capacity))
}
pub const fn with_identity_operation_capacity(
mut self,
identity_operation_capacity: NonZeroUsize,
) -> Self {
self.identity_operation_capacity = identity_operation_capacity;
self
}
pub fn try_with_identity_operation_capacity(
self,
identity_operation_capacity: usize,
) -> Result<Self, ConfigError> {
let identity_operation_capacity =
NonZeroUsize::new(identity_operation_capacity).ok_or(ConfigError::Zero {
field: "controller_identity_operation_capacity",
})?;
Ok(self.with_identity_operation_capacity(identity_operation_capacity))
}
pub const fn with_max_slot_queue(mut self, max_slot_queue: usize) -> Self {
self.max_slot_queue = max_slot_queue;
self
}
pub const fn with_max_controller_slots(
mut self,
max_controller_slots: Option<NonZeroUsize>,
) -> Self {
self.max_controller_slots = max_controller_slots;
self
}
pub fn try_with_max_controller_slots(
self,
max_controller_slots: usize,
) -> Result<Self, ConfigError> {
let value = NonZeroUsize::new(max_controller_slots).ok_or(ConfigError::Zero {
field: "max_controller_slots",
})?;
Ok(self.with_max_controller_slots(Some(value)))
}
pub const fn with_max_total_pending(mut self, max_total_pending: Option<NonZeroUsize>) -> Self {
self.max_total_pending = max_total_pending;
self
}
pub fn try_with_max_total_pending(self, max_total_pending: usize) -> Result<Self, ConfigError> {
let value = NonZeroUsize::new(max_total_pending).ok_or(ConfigError::Zero {
field: "max_total_pending",
})?;
Ok(self.with_max_total_pending(Some(value)))
}
pub(crate) fn validate(&self) -> Result<(), ConfigError> {
validate_async_capacity("controller_queue_capacity", self.queue_capacity)
}
}
impl Default for ControllerConfig {
fn default() -> Self {
Self::new(DEFAULT_QUEUE_CAPACITY, DEFAULT_MAX_SLOT_QUEUE)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn default_contract_is_explicit() {
let config = ControllerConfig::default();
assert_eq!(config.queue_capacity().get(), 1024);
assert_eq!(config.admission_capacity().get(), 1024);
assert_eq!(config.identity_operation_capacity().get(), 1024);
assert_eq!(config.max_slot_queue(), 100);
assert_eq!(
config.max_controller_slots().map(NonZeroUsize::get),
Some(1024)
);
assert_eq!(
config.max_total_pending().map(NonZeroUsize::get),
Some(1024)
);
}
#[test]
fn constructor_and_builders_preserve_invariants() {
let config = ControllerConfig::new(NonZeroUsize::new(8).unwrap(), 3)
.with_queue_capacity(NonZeroUsize::new(16).unwrap())
.with_admission_capacity(NonZeroUsize::new(4).unwrap())
.with_identity_operation_capacity(NonZeroUsize::new(2).unwrap())
.with_max_controller_slots(NonZeroUsize::new(32))
.with_max_total_pending(NonZeroUsize::new(64))
.with_max_slot_queue(0);
assert_eq!(config.queue_capacity().get(), 16);
assert_eq!(config.admission_capacity().get(), 4);
assert_eq!(config.identity_operation_capacity().get(), 2);
assert_eq!(config.max_slot_queue(), 0);
assert_eq!(
config.max_controller_slots().map(NonZeroUsize::get),
Some(32)
);
assert_eq!(config.max_total_pending().map(NonZeroUsize::get), Some(64));
}
#[test]
fn raw_zero_capacity_returns_a_clear_error() {
for result in [
ControllerConfig::try_new(0, 10),
ControllerConfig::default().try_with_queue_capacity(0),
] {
assert_eq!(
result.unwrap_err(),
ConfigError::Zero {
field: "controller_queue_capacity"
}
);
}
for (result, field) in [
(
ControllerConfig::default().try_with_admission_capacity(0),
"controller_admission_capacity",
),
(
ControllerConfig::default().try_with_identity_operation_capacity(0),
"controller_identity_operation_capacity",
),
(
ControllerConfig::default().try_with_max_controller_slots(0),
"max_controller_slots",
),
(
ControllerConfig::default().try_with_max_total_pending(0),
"max_total_pending",
),
] {
assert_eq!(result.unwrap_err(), ConfigError::Zero { field });
}
}
#[test]
fn command_capacity_rejects_values_above_tokio_structural_limit() {
let excessive = crate::core::MAX_ASYNC_CAPACITY + 1;
for result in [
ControllerConfig::try_new(excessive, 10),
ControllerConfig::default().try_with_queue_capacity(excessive),
] {
assert_eq!(
result.unwrap_err(),
ConfigError::TooLarge {
field: "controller_queue_capacity",
value: excessive,
max: crate::core::MAX_ASYNC_CAPACITY,
}
);
}
}
}