use std::{collections::HashMap, mem, sync::Arc};
use praxis_core::{config::FilterEntry, id::IdGenerator, time::SystemTimeSource};
use tracing::{debug, warn};
use super::{FilterPipeline, body::compute_body_capabilities, filter::PipelineFilter};
use crate::{FilterError, any_filter::AnyFilter, registry::FilterRegistry};
impl FilterPipeline {
#[expect(clippy::too_many_lines, reason = "pipeline construction is inherently sequential")]
pub fn build(entries: &mut [FilterEntry], registry: &FilterRegistry) -> Result<Self, FilterError> {
let mut filters = Vec::with_capacity(entries.len());
for (filter_id, entry) in entries.iter_mut().enumerate() {
let filter = registry.create(&entry.filter_type, &entry.config)?;
warn_tcp_unsupported_fields(&filter, entry);
let has_conditions = !entry.conditions.is_empty() || !entry.response_conditions.is_empty();
debug!(
filter = filter.name(),
conditions = has_conditions,
"filter added to pipeline"
);
let mut pf = PipelineFilter::new(
filter_id,
filter,
mem::take(&mut entry.conditions),
mem::take(&mut entry.response_conditions),
);
pf.failure_mode = entry.failure_mode;
pf.name = entry.name.as_ref().map(|n| Arc::from(n.as_str()));
filters.push(pf);
}
let body_capabilities = compute_body_capabilities(&filters);
let compression = extract_compression_config(&filters);
Ok(Self {
body_capabilities,
compression,
filters,
health_registry: None,
id_generator: Arc::new(IdGenerator::new()),
kv_stores: None,
pipeline_extensions: Vec::new(),
time_source: Arc::new(SystemTimeSource),
})
}
pub fn build_with_chains(
entries: &mut [FilterEntry],
registry: &FilterRegistry,
chains: &HashMap<&str, &[FilterEntry]>,
) -> Result<Self, FilterError> {
let filters = super::build_branch::resolve_chain_filters(entries, registry, chains, 0)?;
let body_capabilities = compute_body_capabilities(&filters);
let compression = extract_compression_config(&filters);
Ok(Self {
body_capabilities,
compression,
filters,
health_registry: None,
id_generator: Arc::new(IdGenerator::new()),
kv_stores: None,
pipeline_extensions: Vec::new(),
time_source: Arc::new(SystemTimeSource),
})
}
pub fn ordering_errors(&self, entries: &[FilterEntry], allow_open_security: bool) -> Vec<String> {
let names: Vec<&str> = self.filters.iter().map(|pf| pf.filter.name()).collect();
let mut errors = Vec::new();
super::checks::check_lb_without_cluster_selector(&names, &mut errors);
super::checks::check_unconditional_static_response(&names, &self.filters, &mut errors);
super::checks::check_conditional_security(&names, &self.filters, &mut errors);
super::checks::check_open_security_filters(&names, &self.filters, allow_open_security, &mut errors);
super::checks::check_duplicate_routers(&names, &mut errors);
super::checks::check_duplicate_load_balancers(&names, &mut errors);
super::checks::check_misaligned_clusters(entries, &mut errors);
super::checks::check_duplicate_rewrite_filters(&names, entries, &mut errors);
errors
}
pub fn ordering_warnings(&self) -> Vec<String> {
let names: Vec<&str> = self.filters.iter().map(|pf| pf.filter.name()).collect();
let mut warnings = Vec::new();
super::checks::check_router_without_lb(&names, &mut warnings);
super::checks::check_all_routers_conditional(&names, &self.filters, &mut warnings);
warnings
}
}
fn warn_tcp_unsupported_fields(filter: &AnyFilter, entry: &FilterEntry) {
if !matches!(filter, AnyFilter::Tcp(_)) {
return;
}
if !entry.conditions.is_empty() || !entry.response_conditions.is_empty() {
warn!(
filter = filter.name(),
"TCP filter has conditions that will be ignored; \
conditions are only evaluated for HTTP filters"
);
}
if entry.branch_chains.is_some() {
warn!(
filter = filter.name(),
"TCP filter has branch_chains that will be ignored; \
branching is only supported for HTTP filters"
);
}
}
fn extract_compression_config(
filters: &[PipelineFilter],
) -> Option<crate::builtins::http::payload_processing::compression_config::CompressionConfig> {
for pf in filters {
if let AnyFilter::Http(f) = &pf.filter
&& let Some(cfg) = f.compression_config()
{
return Some(cfg.clone());
}
}
None
}