use std::collections::HashMap;
use super::{FilterChainConfig, FilterEntry, Listener};
pub struct ExpandedFilterChains<'chains> {
entries_by_name: HashMap<&'chains str, Vec<FilterEntry>>,
}
impl<'chains> ExpandedFilterChains<'chains> {
#[must_use]
pub fn new(chains: &'chains [FilterChainConfig]) -> Self {
let entries_by_name = chains
.iter()
.map(|chain| (chain.name.as_str(), chain.expanded_entries()))
.collect();
Self { entries_by_name }
}
#[must_use]
pub fn as_slices(&self) -> HashMap<&str, &[FilterEntry]> {
self.entries_by_name
.iter()
.map(|(name, entries)| (*name, entries.as_slice()))
.collect()
}
pub fn for_listener(&self, listener: &Listener) -> Result<Vec<FilterEntry>, String> {
listener
.filter_chains
.iter()
.try_fold(Vec::new(), |mut entries, chain_name| {
let chain_entries = self
.entries_by_name
.get(chain_name.as_str())
.ok_or_else(|| format!("unknown chain '{chain_name}' for listener '{}'", listener.name))?;
entries.extend_from_slice(chain_entries);
Ok(entries)
})
}
}
#[cfg(test)]
#[expect(
clippy::unwrap_used,
clippy::indexing_slicing,
reason = "tests use unwrap and indexing for brevity"
)]
mod tests {
use super::*;
use crate::config::{Condition, Config};
#[test]
fn listener_and_named_refs_share_expanded_conditions() {
let config = Config::from_yaml(CONDITIONED_CHAINS_YAML).unwrap();
let expanded = ExpandedFilterChains::new(&config.filter_chains);
let listener_entries = expanded.for_listener(&config.listeners[0]).unwrap();
let named_entries = expanded.as_slices();
let guarded = &named_entries.get("guarded").unwrap()[0];
assert_eq!(listener_entries.len(), 2, "listener preserves chain order");
assert_eq!(listener_entries[0].filter_type, "request_id");
assert_eq!(listener_entries[1].conditions.len(), 2);
assert_eq!(guarded.conditions.len(), 2, "named references inherit the same gate");
assert!(
matches!(&guarded.conditions[0], Condition::When(matcher) if matcher.path_prefix.as_deref() == Some("/api")),
"the inherited /api condition must precede the filter's own condition"
);
assert_eq!(
config.filter_chains[1].filters[0].conditions.len(),
1,
"source remains unchanged"
);
}
const CONDITIONED_CHAINS_YAML: &str = r#"
listeners:
- name: web
address: "127.0.0.1:8080"
filter_chains: [plain, guarded]
filter_chains:
- name: plain
filters:
- filter: request_id
- name: guarded
conditions:
- when:
path_prefix: "/api"
filters:
- filter: headers
conditions:
- when:
methods: ["POST"]
"#;
}