use std::fmt;
use std::sync::Arc;
use std::time::Duration;
use super::Identity;
use crate::Result;
pub(super) type Generate = Arc<dyn Fn(u64) -> Result<Identity> + Send + Sync>;
#[derive(Clone)]
pub(super) enum Source {
Fixed(Vec<Identity>),
Generate(Generate),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum Choice {
InTurn,
Random,
StickyPerHost,
}
#[derive(Clone)]
pub struct Identities {
pub(super) source: Source,
pub(super) max_active: usize,
pub(super) choice: Choice,
pub(super) max_requests: Option<u64>,
pub(super) max_age: Option<Duration>,
pub(super) max_failures: u32,
pub(super) rest: Duration,
}
impl Identities {
pub const DEFAULT_MAX_ACTIVE: usize = 16;
#[must_use]
pub fn new(identities: impl IntoIterator<Item = Identity>) -> Self {
let identities: Vec<Identity> = identities.into_iter().collect();
assert!(
!identities.is_empty(),
"`Identities::new` needs an identity"
);
for (index, identity) in identities.iter().enumerate() {
assert!(
!identities[..index].contains(identity),
"two identities are named `{}`",
identity.name()
);
}
let max_active = identities.len();
Self::with_source(Source::Fixed(identities), max_active)
}
#[must_use]
pub fn from_fn<F>(generate: F) -> Self
where
F: Fn(u64) -> Result<Identity> + Send + Sync + 'static,
{
Self::with_source(
Source::Generate(Arc::new(generate)),
Self::DEFAULT_MAX_ACTIVE,
)
}
const fn with_source(source: Source, max_active: usize) -> Self {
Self {
source,
max_active,
choice: Choice::InTurn,
max_requests: None,
max_age: None,
max_failures: 3,
rest: Duration::from_mins(1),
}
}
#[must_use]
pub fn max_active(mut self, n: usize) -> Self {
assert!(n > 0, "`Identities::max_active` must be above zero");
if matches!(self.source, Source::Generate(_)) {
self.max_active = n;
}
self
}
#[must_use]
pub const fn in_turn(mut self) -> Self {
self.choice = Choice::InTurn;
self
}
#[must_use]
pub const fn at_random(mut self) -> Self {
self.choice = Choice::Random;
self
}
#[must_use]
pub const fn sticky_per_host(mut self) -> Self {
self.choice = Choice::StickyPerHost;
self
}
#[must_use]
pub const fn retire_after_requests(mut self, n: u64) -> Self {
self.max_requests = Some(n);
self
}
#[must_use]
pub const fn retire_after(mut self, age: Duration) -> Self {
self.max_age = Some(age);
self
}
#[must_use]
pub fn max_failures(mut self, n: u32) -> Self {
assert!(n > 0, "`Identities::max_failures` must be above zero");
self.max_failures = n;
self
}
#[must_use]
pub const fn rest(mut self, duration: Duration) -> Self {
self.rest = duration;
self
}
}
impl fmt::Debug for Identities {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let source = match &self.source {
Source::Fixed(identities) => format!("{} listed", identities.len()),
Source::Generate(_) => "generated".to_owned(),
};
f.debug_struct("Identities")
.field("source", &source)
.field("max_active", &self.max_active)
.field("choice", &self.choice)
.field("max_requests", &self.max_requests)
.field("max_age", &self.max_age)
.field("max_failures", &self.max_failures)
.field("rest", &self.rest)
.finish()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
#[should_panic(expected = "two identities are named `a`")]
fn listed_names_are_unique() {
let _ = Identities::new([Identity::new("a"), Identity::new("a")]);
}
}