use std::collections::HashMap;
use crate::{
any_filter::AnyFilter,
factory::{FilterFactory, http_builtin, tcp_builtin},
filter::FilterError,
};
pub struct FilterRegistry {
factories: HashMap<String, FilterFactory>,
}
impl FilterRegistry {
#[must_use]
pub fn with_builtins() -> Self {
let mut factories = HashMap::new();
register_http_builtins(&mut factories);
register_tcp_builtins(&mut factories);
Self { factories }
}
pub fn register(&mut self, name: &str, factory: FilterFactory) -> Result<(), FilterError> {
if self.factories.contains_key(name) {
return Err(format!("duplicate filter name: '{name}'").into());
}
self.factories.insert(name.to_owned(), factory);
Ok(())
}
pub fn create(&self, name: &str, config: &serde_yaml::Value) -> Result<AnyFilter, FilterError> {
let factory = self
.factories
.get(name)
.ok_or_else(|| -> FilterError { format!("unknown filter type: '{name}'").into() })?;
factory.create(config)
}
pub fn available_filters(&self) -> Vec<&str> {
self.factories.keys().map(String::as_str).collect()
}
}
fn register_http_builtins(factories: &mut HashMap<String, FilterFactory>) {
use crate::builtins::{
AccessLogFilter, CompressionFilter, CorsFilter, ForwardedHeadersFilter, HeaderFilter, IpAclFilter,
JsonBodyFieldFilter, PathRewriteFilter, RateLimitFilter, RedirectFilter, RequestIdFilter, StaticResponseFilter,
TimeoutFilter, UrlRewriteFilter,
};
register_http(factories, "access_log", AccessLogFilter::from_config);
register_http(factories, "compression", CompressionFilter::from_config);
register_http(factories, "cors", CorsFilter::from_config);
register_http(factories, "headers", HeaderFilter::from_config);
register_http(factories, "forwarded_headers", ForwardedHeadersFilter::from_config);
register_http(factories, "guardrails", crate::GuardrailsFilter::from_config);
register_http(factories, "ip_acl", IpAclFilter::from_config);
register_http(factories, "load_balancer", crate::LoadBalancerFilter::from_config);
register_http(factories, "path_rewrite", PathRewriteFilter::from_config);
register_http(factories, "rate_limit", RateLimitFilter::from_config);
register_http(factories, "redirect", RedirectFilter::from_config);
register_http(factories, "request_id", RequestIdFilter::from_config);
register_http(factories, "router", crate::RouterFilter::from_config);
register_http(factories, "static_response", StaticResponseFilter::from_config);
register_http(factories, "timeout", TimeoutFilter::from_config);
register_http(factories, "url_rewrite", UrlRewriteFilter::from_config);
register_http(factories, "json_body_field", JsonBodyFieldFilter::from_config);
#[cfg(feature = "ai-inference")]
register_http(
factories,
"model_to_header",
crate::builtins::ModelToHeaderFilter::from_config,
);
}
#[allow(clippy::type_complexity, reason = "complex function pointer")]
fn register_http(
factories: &mut HashMap<String, FilterFactory>,
name: &str,
factory_fn: fn(&serde_yaml::Value) -> Result<Box<dyn crate::filter::HttpFilter>, FilterError>,
) {
factories.insert(name.to_owned(), http_builtin(factory_fn));
}
fn register_tcp_builtins(factories: &mut HashMap<String, FilterFactory>) {
factories.insert(
"tcp_access_log".to_owned(),
tcp_builtin(crate::builtins::TcpAccessLogFilter::from_config),
);
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn builtins_registered() {
let registry = FilterRegistry::with_builtins();
let mut names = registry.available_filters();
names.sort();
assert!(names.contains(&"access_log"), "access_log should be registered");
assert!(names.contains(&"compression"), "compression should be registered");
assert!(names.contains(&"cors"), "cors should be registered");
assert!(
names.contains(&"forwarded_headers"),
"forwarded_headers should be registered"
);
assert!(names.contains(&"guardrails"), "guardrails should be registered");
assert!(names.contains(&"headers"), "headers should be registered");
assert!(names.contains(&"ip_acl"), "ip_acl should be registered");
assert!(names.contains(&"load_balancer"), "load_balancer should be registered");
assert!(names.contains(&"path_rewrite"), "path_rewrite should be registered");
assert!(names.contains(&"rate_limit"), "rate_limit should be registered");
assert!(names.contains(&"redirect"), "redirect should be registered");
assert!(names.contains(&"request_id"), "request_id should be registered");
assert!(names.contains(&"router"), "router should be registered");
assert!(
names.contains(&"static_response"),
"static_response should be registered"
);
assert!(names.contains(&"tcp_access_log"), "tcp_access_log should be registered");
assert!(names.contains(&"timeout"), "timeout should be registered");
assert!(names.contains(&"url_rewrite"), "url_rewrite should be registered");
assert!(
names.contains(&"json_body_field"),
"json_body_field should be registered"
);
#[cfg(feature = "ai-inference")]
assert!(
names.contains(&"model_to_header"),
"model_to_header should be registered"
);
}
#[test]
fn unknown_filter_errors() {
let registry = FilterRegistry::with_builtins();
match registry.create("nonexistent", &serde_yaml::Value::Null) {
Err(e) => assert!(
e.to_string().contains("unknown filter type"),
"error should mention unknown filter type"
),
Ok(_) => panic!("expected error for unknown filter type"),
}
}
#[test]
fn register_custom_filter_succeeds() {
let mut registry = FilterRegistry::with_builtins();
let factory = FilterFactory::Http(std::sync::Arc::new(|_| Err("unused".into())));
assert!(
registry.register("my_custom", factory).is_ok(),
"registering a unique name should succeed"
);
assert!(
registry.available_filters().contains(&"my_custom"),
"custom filter should appear in available filters"
);
}
#[test]
fn register_duplicate_builtin_errors() {
let mut registry = FilterRegistry::with_builtins();
let factory = FilterFactory::Http(std::sync::Arc::new(|_| Err("unused".into())));
let err = registry.register("router", factory).unwrap_err();
assert!(
err.to_string().contains("duplicate filter name: 'router'"),
"error should name the duplicate: {err}"
);
}
#[test]
fn register_duplicate_custom_errors() {
let mut registry = FilterRegistry::with_builtins();
let factory_a = FilterFactory::Http(std::sync::Arc::new(|_| Err("a".into())));
let factory_b = FilterFactory::Http(std::sync::Arc::new(|_| Err("b".into())));
registry.register("my_filter", factory_a).unwrap();
let err = registry.register("my_filter", factory_b).unwrap_err();
assert!(
err.to_string().contains("duplicate filter name: 'my_filter'"),
"error should name the duplicate: {err}"
);
}
}