use crate::config::FlowControlConfig;
use crate::error::LimiteronError;
use crate::governor::Governor;
use crate::storage::{BanStorage, MemoryBanStorage, MemoryStorage, Storage};
use std::any::TypeId;
use std::future::Future;
use std::pin::Pin;
use std::sync::Arc;
use trait_kit::prelude::*;
#[derive(Default, Clone)]
pub struct LimiteronStorageConfig {
storage: Option<Arc<dyn Storage>>,
ban_storage: Option<Arc<dyn BanStorage>>,
}
impl LimiteronStorageConfig {
pub fn new() -> Self {
Self::default()
}
pub fn with_storage(mut self, storage: Arc<dyn Storage>) -> Self {
self.storage = Some(storage);
self
}
pub fn with_ban_storage(mut self, ban_storage: Arc<dyn BanStorage>) -> Self {
self.ban_storage = Some(ban_storage);
self
}
pub fn storage(&self) -> Option<Arc<dyn Storage>> {
self.storage.clone()
}
pub fn ban_storage(&self) -> Option<Arc<dyn BanStorage>> {
self.ban_storage.clone()
}
}
pub struct LimiteronModule;
impl ModuleMeta for LimiteronModule {
const NAME: &'static str = "limiteron";
fn dependencies() -> &'static [(&'static str, TypeId)] {
&[]
}
}
impl AsyncAutoBuilder for LimiteronModule {
type Capability = Arc<Governor>;
type Error = LimiteronError;
fn build<'a>(
kit: &'a AsyncKit,
) -> Pin<Box<dyn Future<Output = Result<Self::Capability, Self::Error>> + Send + 'a>> {
Box::pin(async move {
let config: FlowControlConfig = kit.config().map_err(|e| {
LimiteronError::ConfigError(format!("LimiteronModule: read config: {e}"))
})?;
let storage_override: Option<LimiteronStorageConfig> = kit.config().ok();
let storage: Arc<dyn Storage> = storage_override
.as_ref()
.and_then(|o| o.storage())
.unwrap_or_else(|| Arc::new(MemoryStorage::new()) as Arc<dyn Storage>);
let ban_storage: Arc<dyn BanStorage> = storage_override
.as_ref()
.and_then(|o| o.ban_storage())
.unwrap_or_else(|| Arc::new(MemoryBanStorage::new()) as Arc<dyn BanStorage>);
let governor = Governor::builder()
.with_config(config)
.with_storage(storage)
.with_ban_storage(ban_storage)
.build()
.await?;
Ok(Arc::new(governor))
})
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::config::FlowControlConfig;
#[cfg(feature = "ban-manager")]
use crate::error::StorageError;
use crate::governor::Governor;
#[cfg(feature = "ban-manager")]
use crate::matchers::Identifier;
#[cfg(feature = "ban-manager")]
use crate::storage::{BanHistory, BanRecord, BanTarget};
#[cfg(feature = "ban-manager")]
use std::any::Any;
use std::any::TypeId;
use std::sync::Arc;
#[cfg(feature = "ban-manager")]
use std::sync::atomic::{AtomicUsize, Ordering};
#[cfg(feature = "ban-manager")]
struct RecordingBanStorage {
inner: MemoryBanStorage,
saves: AtomicUsize,
}
#[cfg(feature = "ban-manager")]
impl RecordingBanStorage {
fn new() -> Self {
Self {
inner: MemoryBanStorage::new(),
saves: AtomicUsize::new(0),
}
}
fn save_count(&self) -> usize {
self.saves.load(Ordering::SeqCst)
}
}
#[cfg(feature = "ban-manager")]
#[async_trait::async_trait]
impl BanStorage for RecordingBanStorage {
async fn is_banned(&self, target: &BanTarget) -> Result<Option<BanRecord>, StorageError> {
self.inner.is_banned(target).await
}
async fn save(&self, record: &BanRecord) -> Result<(), StorageError> {
self.saves.fetch_add(1, Ordering::SeqCst);
self.inner.save(record).await
}
async fn get_history(
&self,
target: &BanTarget,
) -> Result<Option<BanHistory>, StorageError> {
self.inner.get_history(target).await
}
async fn increment_ban_times(&self, target: &BanTarget) -> Result<u64, StorageError> {
self.inner.increment_ban_times(target).await
}
async fn get_ban_times(&self, target: &BanTarget) -> Result<u64, StorageError> {
self.inner.get_ban_times(target).await
}
async fn remove_ban(&self, target: &BanTarget) -> Result<(), StorageError> {
self.inner.remove_ban(target).await
}
async fn cleanup_expired_bans(&self) -> Result<u64, StorageError> {
self.inner.cleanup_expired_bans().await
}
async fn list_bans(
&self,
active_only: bool,
offset: u64,
limit: u64,
) -> Result<Vec<BanRecord>, StorageError> {
self.inner.list_bans(active_only, offset, limit).await
}
fn as_any(&self) -> &dyn Any {
self
}
}
fn make_minimal_valid_config() -> FlowControlConfig {
use crate::config::{Action, ActionConfig, LimiterConfig, Matcher, Rule};
let mut config = FlowControlConfig::default();
config.rules.push(Rule {
id: "default".to_string(),
name: "default rule".to_string(),
priority: 0,
matchers: vec![Matcher::User {
user_ids: vec!["user1".to_string()],
}],
limiters: vec![LimiterConfig::TokenBucket {
capacity: 100,
refill_rate: 10,
}],
action: ActionConfig {
on_exceed: Action::Reject,
ban: None,
},
});
config
}
#[test]
fn limiteron_module_meta_name() {
assert_eq!(LimiteronModule::NAME, "limiteron");
}
#[test]
fn limiteron_module_meta_dependencies_empty() {
assert_eq!(
LimiteronModule::dependencies(),
&[] as &[(&'static str, TypeId)]
);
}
#[tokio::test]
async fn limiteron_module_build_returns_governor_capability() {
let mut kit = AsyncKit::new();
kit.set_config(make_minimal_valid_config());
kit.register::<LimiteronModule>()
.expect("register LimiteronModule");
let kit = kit.build().await.expect("AsyncKit::build");
let governor: Arc<Governor> = kit
.require::<LimiteronModule>()
.expect("require LimiteronModule");
let _ = governor;
}
#[tokio::test]
async fn limiteron_module_build_reads_config_from_kit() {
let mut kit = AsyncKit::new();
kit.set_config(make_minimal_valid_config());
kit.register::<LimiteronModule>()
.expect("register LimiteronModule");
let kit = kit.build().await.expect("AsyncKit::build");
let _governor: Arc<Governor> = kit
.require::<LimiteronModule>()
.expect("require LimiteronModule");
}
#[tokio::test]
async fn limiteron_module_build_is_async() {
let kit = AsyncKit::new();
kit.set_config(make_minimal_valid_config());
let fut = <LimiteronModule as AsyncAutoBuilder>::build(&kit);
let _governor: Arc<Governor> = fut.await.expect("build future resolves");
fn assert_send_sync<T: Send + Sync>() {}
assert_send_sync::<Arc<Governor>>();
}
#[cfg(feature = "ban-manager")]
#[tokio::test]
async fn limiteron_module_routes_to_injected_ban_storage() {
let recording = Arc::new(RecordingBanStorage::new());
let recording_for_assert = recording.clone();
let mut kit = AsyncKit::new();
kit.set_config(make_minimal_valid_config());
kit.set_config(
LimiteronStorageConfig::new()
.with_ban_storage(recording.clone() as Arc<dyn BanStorage>),
);
kit.register::<LimiteronModule>()
.expect("register LimiteronModule");
let kit = kit.build().await.expect("AsyncKit::build");
let governor: Arc<Governor> = kit
.require::<LimiteronModule>()
.expect("require LimiteronModule");
let identifier = Identifier::UserId("injected-route-user".to_string());
governor
.ban_identifier(&identifier, "test ban", None)
.await
.expect("ban via injected storage");
assert!(
recording_for_assert.save_count() > 0,
"注入的 BanStorage 必须被 Governor 路由调用(save)"
);
governor
.unban_identifier(&identifier)
.await
.expect("unban via injected storage");
}
#[cfg(feature = "ban-manager")]
#[tokio::test]
async fn limiteron_module_defaults_to_memory_storage() {
let mut kit = AsyncKit::new();
kit.set_config(make_minimal_valid_config());
kit.register::<LimiteronModule>()
.expect("register LimiteronModule");
let kit = kit.build().await.expect("AsyncKit::build");
let governor: Arc<Governor> = kit
.require::<LimiteronModule>()
.expect("require LimiteronModule");
let identifier = Identifier::UserId("default-memory-user".to_string());
governor
.ban_identifier(&identifier, "test ban", None)
.await
.expect("ban with default MemoryBanStorage");
}
#[test]
fn limiteron_storage_config_builders_roundtrip() {
let storage: Arc<dyn Storage> = Arc::new(MemoryStorage::new());
let ban_storage: Arc<dyn BanStorage> = Arc::new(MemoryBanStorage::new());
let config = LimiteronStorageConfig::new()
.with_storage(storage.clone())
.with_ban_storage(ban_storage.clone());
assert!(config.storage().is_some());
assert!(config.ban_storage().is_some());
let empty = LimiteronStorageConfig::new();
assert!(empty.storage().is_none());
assert!(empty.ban_storage().is_none());
}
}