#[macro_export]
macro_rules! register_filters {
( @register $registry:ident, http $name:expr => $factory:expr ) => {
$registry.register(
$name,
$crate::FilterFactory::Http(
::std::sync::Arc::new(move |config: &serde_yaml::Value| {
($factory)(config)
}),
),
).unwrap_or_else(|_| panic!("duplicate filter name: '{}'", $name));
};
( @register $registry:ident, tcp $name:expr => $factory:expr ) => {
$registry.register(
$name,
$crate::FilterFactory::Tcp(
::std::sync::Arc::new(move |config: &serde_yaml::Value| {
($factory)(config)
}),
),
).unwrap_or_else(|_| panic!("duplicate filter name: '{}'", $name));
};
( $( $kind:ident $name:expr => $factory:expr ),* $(,)? ) => {
pub fn custom_registry() -> $crate::FilterRegistry {
let mut registry = $crate::FilterRegistry::with_builtins();
$(
$crate::register_filters!(@register registry, $kind $name => $factory);
)*
registry
}
};
}
#[macro_export]
macro_rules! export_filters {
( $( $kind:ident $name:expr => $factory:expr ),* $(,)? ) => {
pub fn register_filters(registry: &mut $crate::FilterRegistry) {
$(
$crate::register_filters!(@register registry, $kind $name => $factory);
)*
}
};
}
#[cfg(test)]
#[expect(clippy::allow_attributes, reason = "blanket test suppressions")]
#[allow(
unreachable_pub,
clippy::unwrap_used,
clippy::expect_used,
clippy::panic,
clippy::unnecessary_wraps,
reason = "internal pub items re-exported selectively; test module"
)]
mod macro_tests {
use async_trait::async_trait;
use crate::{FilterAction, FilterError, HttpFilter, HttpFilterContext, TcpFilter};
#[test]
fn macro_registers_http_filter() {
let registry = custom_registry();
assert!(
registry.available_filters().contains(&"dummy_http"),
"registry should contain custom HTTP filter"
);
}
#[test]
fn macro_registers_tcp_filter() {
let registry = custom_registry();
assert!(
registry.available_filters().contains(&"dummy_tcp"),
"registry should contain custom TCP filter"
);
}
#[test]
fn macro_registers_http_filter_with_name_expression() {
let mut registry = crate::FilterRegistry::with_builtins();
let name = String::from("dummy_http_expr");
register_filters!(@register registry, http name.as_str() => DummyHttpFilter::from_config);
assert!(
registry.available_filters().contains(&"dummy_http_expr"),
"registry should contain custom HTTP filter registered with a name expression"
);
}
#[test]
fn macro_registers_tcp_filter_with_name_expression() {
let mut registry = crate::FilterRegistry::with_builtins();
let name = String::from("dummy_tcp_expr");
register_filters!(@register registry, tcp name.as_str() => DummyTcpFilter::from_config);
assert!(
registry.available_filters().contains(&"dummy_tcp_expr"),
"registry should contain custom TCP filter registered with a name expression"
);
}
#[test]
fn macro_preserves_builtins() {
let registry = custom_registry();
assert!(
registry.available_filters().contains(&"router"),
"registry should still contain built-in router"
);
assert!(
registry.available_filters().contains(&"load_balancer"),
"registry should still contain built-in load_balancer"
);
}
#[test]
fn macro_registered_http_filter_creates_successfully() {
let registry = custom_registry();
let result = registry.create("dummy_http", &serde_yaml::Value::Null);
assert!(result.is_ok(), "custom HTTP filter should instantiate without error");
}
#[test]
fn macro_registered_tcp_filter_creates_successfully() {
let registry = custom_registry();
let result = registry.create("dummy_tcp", &serde_yaml::Value::Null);
assert!(result.is_ok(), "custom TCP filter should instantiate without error");
}
#[test]
#[should_panic(expected = "duplicate filter name: 'router'")]
fn macro_panics_on_builtin_collision() {
let mut registry = crate::FilterRegistry::with_builtins();
register_filters!(@register registry, http "router" => DummyHttpFilter::from_config);
}
#[test]
fn export_filters_registers_http() {
let mut registry = crate::FilterRegistry::with_builtins();
export_test::register_filters(&mut registry);
assert!(
registry.available_filters().contains(&"exported_http"),
"exported HTTP filter should be registered"
);
}
#[test]
fn export_filters_registers_tcp() {
let mut registry = crate::FilterRegistry::with_builtins();
export_test::register_filters(&mut registry);
assert!(
registry.available_filters().contains(&"exported_tcp"),
"exported TCP filter should be registered"
);
}
#[test]
fn export_filters_preserves_builtins() {
let mut registry = crate::FilterRegistry::with_builtins();
export_test::register_filters(&mut registry);
assert!(
registry.available_filters().contains(&"router"),
"built-in router should still be registered"
);
}
#[test]
fn export_filters_creates_http_filter_successfully() {
let mut registry = crate::FilterRegistry::with_builtins();
export_test::register_filters(&mut registry);
let result = registry.create("exported_http", &serde_yaml::Value::Null);
assert!(result.is_ok(), "exported HTTP filter should instantiate without error");
}
#[test]
fn export_filters_creates_tcp_filter_successfully() {
let mut registry = crate::FilterRegistry::with_builtins();
export_test::register_filters(&mut registry);
let result = registry.create("exported_tcp", &serde_yaml::Value::Null);
assert!(result.is_ok(), "exported TCP filter should instantiate without error");
}
#[test]
#[should_panic(expected = "duplicate filter name: 'router'")]
fn export_filters_panics_on_builtin_collision() {
mod collision {
use super::*;
export_filters! {
http "router" => DummyHttpFilter::from_config,
}
}
let mut registry = crate::FilterRegistry::with_builtins();
collision::register_filters(&mut registry);
}
register_filters! {
http "dummy_http" => DummyHttpFilter::from_config,
tcp "dummy_tcp" => DummyTcpFilter::from_config,
}
mod export_test {
use super::*;
export_filters! {
http "exported_http" => DummyHttpFilter::from_config,
tcp "exported_tcp" => DummyTcpFilter::from_config,
}
}
struct DummyHttpFilter;
#[async_trait]
impl HttpFilter for DummyHttpFilter {
fn name(&self) -> &'static str {
"dummy_http"
}
async fn on_request(&self, _ctx: &mut HttpFilterContext<'_>) -> Result<FilterAction, FilterError> {
Ok(FilterAction::Continue)
}
}
impl DummyHttpFilter {
fn from_config(_: &serde_yaml::Value) -> Result<Box<dyn HttpFilter>, FilterError> {
Ok(Box::new(Self))
}
}
struct DummyTcpFilter;
#[async_trait]
impl TcpFilter for DummyTcpFilter {
fn name(&self) -> &'static str {
"dummy_tcp"
}
}
impl DummyTcpFilter {
fn from_config(_: &serde_yaml::Value) -> Result<Box<dyn TcpFilter>, FilterError> {
Ok(Box::new(Self))
}
}
}