use crate::order::{MiddlewareKind, DEFAULT_ORDER, PHP_GLOBAL_ORDER};
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct MiddlewareChain {
order: Vec<MiddlewareKind>,
}
impl MiddlewareChain {
pub fn new() -> Self {
Self { order: Vec::new() }
}
pub fn default_chain() -> Self {
Self {
order: DEFAULT_ORDER.to_vec(),
}
}
pub fn php_global() -> Self {
Self {
order: PHP_GLOBAL_ORDER.to_vec(),
}
}
pub fn push(mut self, kind: MiddlewareKind) -> Self {
self.order.push(kind);
self
}
pub fn insert(mut self, index: usize, kind: MiddlewareKind) -> Result<Self, String> {
if index > self.order.len() {
return Err(format!(
"insert index {index} out of bounds (len={})",
self.order.len()
));
}
self.order.insert(index, kind);
Ok(self)
}
pub fn remove(&mut self, index: usize) -> Option<MiddlewareKind> {
if index >= self.order.len() {
return None;
}
Some(self.order.remove(index))
}
pub fn remove_kind(&mut self, kind: MiddlewareKind) -> usize {
let before = self.order.len();
self.order.retain(|k| *k != kind);
before - self.order.len()
}
pub fn remove_from(&mut self, kind: MiddlewareKind) -> usize {
if let Some(pos) = self.order.iter().position(|k| *k == kind) {
let removed = self.order.len() - pos;
self.order.truncate(pos);
removed
} else {
0
}
}
pub fn order(&self) -> &[MiddlewareKind] {
&self.order
}
pub fn service_builder_order(&self) -> Vec<MiddlewareKind> {
self.order.iter().copied().rev().collect()
}
pub fn len(&self) -> usize {
self.order.len()
}
pub fn is_empty(&self) -> bool {
self.order.is_empty()
}
pub fn contains(&self, kind: MiddlewareKind) -> bool {
self.order.contains(&kind)
}
pub fn position(&self, kind: MiddlewareKind) -> Option<usize> {
self.order.iter().position(|k| *k == kind)
}
#[inline]
pub fn has_duplicates(&self) -> bool {
let n = self.order.len();
for i in 0..n {
for j in (i + 1)..n {
if self.order[i] == self.order[j] {
return true;
}
}
}
false
}
}
impl Default for MiddlewareChain {
fn default() -> Self {
Self::default_chain()
}
}
impl std::fmt::Display for MiddlewareChain {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "MiddlewareChain[")?;
for (i, kind) in self.order.iter().enumerate() {
if i > 0 {
write!(f, " -> ")?;
}
write!(f, "{kind}")?;
}
write!(f, "]")
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_creates_empty_chain() {
let chain = MiddlewareChain::new();
assert!(chain.is_empty());
assert_eq!(chain.len(), 0);
assert_eq!(chain.order(), &[]);
}
#[test]
fn test_default_chain_uses_default_order() {
let chain = MiddlewareChain::default_chain();
assert_eq!(chain.order(), DEFAULT_ORDER);
assert_eq!(chain.len(), 5);
}
#[test]
fn test_default_trait_uses_default_chain() {
let chain = MiddlewareChain::default();
assert_eq!(chain.order(), DEFAULT_ORDER);
}
#[test]
fn test_php_global_uses_php_global_order() {
let chain = MiddlewareChain::php_global();
assert_eq!(chain.order(), PHP_GLOBAL_ORDER);
assert_eq!(chain.len(), 2);
}
#[test]
fn test_push_appends_to_end() {
let chain = MiddlewareChain::new()
.push(MiddlewareKind::Trace)
.push(MiddlewareKind::Cors);
assert_eq!(chain.order(), [MiddlewareKind::Trace, MiddlewareKind::Cors]);
}
#[test]
fn test_push_preserves_order() {
let chain = MiddlewareChain::new()
.push(MiddlewareKind::Auth)
.push(MiddlewareKind::Log)
.push(MiddlewareKind::Trace);
assert_eq!(
chain.order(),
[
MiddlewareKind::Auth,
MiddlewareKind::Log,
MiddlewareKind::Trace
]
);
}
#[test]
fn test_insert_at_beginning() {
let chain = MiddlewareChain::new()
.push(MiddlewareKind::Cors)
.push(MiddlewareKind::Log);
let chain = chain
.insert(0, MiddlewareKind::Trace)
.expect("insert at 0 should succeed");
assert_eq!(
chain.order(),
[
MiddlewareKind::Trace,
MiddlewareKind::Cors,
MiddlewareKind::Log
]
);
}
#[test]
fn test_insert_at_middle() {
let chain = MiddlewareChain::new()
.push(MiddlewareKind::Trace)
.push(MiddlewareKind::Log);
let chain = chain
.insert(1, MiddlewareKind::Cors)
.expect("insert at 1 should succeed");
assert_eq!(
chain.order(),
[
MiddlewareKind::Trace,
MiddlewareKind::Cors,
MiddlewareKind::Log
]
);
}
#[test]
fn test_insert_at_end() {
let chain = MiddlewareChain::new()
.push(MiddlewareKind::Trace)
.push(MiddlewareKind::Cors);
let chain = chain
.insert(2, MiddlewareKind::Log)
.expect("insert at 2 should succeed");
assert_eq!(
chain.order(),
[
MiddlewareKind::Trace,
MiddlewareKind::Cors,
MiddlewareKind::Log
]
);
}
#[test]
fn test_insert_out_of_bounds_returns_err() {
let chain = MiddlewareChain::new().push(MiddlewareKind::Trace);
let result = chain.insert(5, MiddlewareKind::Cors);
assert!(result.is_err());
let err = result.unwrap_err();
assert!(err.contains("out of bounds"));
}
#[test]
fn test_remove_by_index() {
let mut chain = MiddlewareChain::default_chain();
let removed = chain.remove(2); assert_eq!(removed, Some(MiddlewareKind::Log));
assert_eq!(
chain.order(),
[
MiddlewareKind::Trace,
MiddlewareKind::Cors,
MiddlewareKind::RateLimit,
MiddlewareKind::Auth,
]
);
}
#[test]
fn test_remove_out_of_bounds_returns_none() {
let mut chain = MiddlewareChain::default_chain();
assert_eq!(chain.remove(99), None);
assert_eq!(chain.len(), 5); }
#[test]
fn test_remove_kind_removes_all_occurrences() {
let mut chain = MiddlewareChain::new()
.push(MiddlewareKind::Trace)
.push(MiddlewareKind::Cors)
.push(MiddlewareKind::Trace); let removed = chain.remove_kind(MiddlewareKind::Trace);
assert_eq!(removed, 2);
assert_eq!(chain.order(), [MiddlewareKind::Cors]);
}
#[test]
fn test_remove_kind_not_present_returns_zero() {
let mut chain = MiddlewareChain::php_global();
let removed = chain.remove_kind(MiddlewareKind::Auth);
assert_eq!(removed, 0);
}
#[test]
fn test_remove_from_removes_kind_and_after() {
let mut chain = MiddlewareChain::default_chain();
let removed = chain.remove_from(MiddlewareKind::RateLimit);
assert_eq!(removed, 2);
assert_eq!(
chain.order(),
[
MiddlewareKind::Trace,
MiddlewareKind::Cors,
MiddlewareKind::Log
]
);
}
#[test]
fn test_remove_from_first_element_clears_all() {
let mut chain = MiddlewareChain::default_chain();
let removed = chain.remove_from(MiddlewareKind::Trace);
assert_eq!(removed, 5);
assert!(chain.is_empty());
}
#[test]
fn test_remove_from_not_present_returns_zero() {
let mut chain = MiddlewareChain::php_global(); let removed = chain.remove_from(MiddlewareKind::Auth);
assert_eq!(removed, 0);
assert_eq!(chain.len(), 2); }
#[test]
fn test_service_builder_order_reverses() {
let chain = MiddlewareChain::default_chain();
let sb_order = chain.service_builder_order();
assert_eq!(
sb_order,
[
MiddlewareKind::Auth,
MiddlewareKind::RateLimit,
MiddlewareKind::Log,
MiddlewareKind::Cors,
MiddlewareKind::Trace,
]
);
}
#[test]
fn test_service_builder_order_empty_chain() {
let chain = MiddlewareChain::new();
assert_eq!(chain.service_builder_order(), Vec::<MiddlewareKind>::new());
}
#[test]
fn test_service_builder_order_single_element() {
let chain = MiddlewareChain::new().push(MiddlewareKind::Cors);
assert_eq!(chain.service_builder_order(), [MiddlewareKind::Cors]);
}
#[test]
fn test_contains_true() {
let chain = MiddlewareChain::default_chain();
assert!(chain.contains(MiddlewareKind::Auth));
assert!(chain.contains(MiddlewareKind::Trace));
}
#[test]
fn test_contains_false() {
let chain = MiddlewareChain::php_global();
assert!(!chain.contains(MiddlewareKind::Auth));
}
#[test]
fn test_position_returns_index() {
let chain = MiddlewareChain::default_chain();
assert_eq!(chain.position(MiddlewareKind::Trace), Some(0));
assert_eq!(chain.position(MiddlewareKind::Auth), Some(4));
}
#[test]
fn test_position_not_present_returns_none() {
let chain = MiddlewareChain::php_global();
assert_eq!(chain.position(MiddlewareKind::Auth), None);
}
#[test]
fn test_has_duplicates_false_for_default() {
let chain = MiddlewareChain::default_chain();
assert!(!chain.has_duplicates());
}
#[test]
fn test_has_duplicates_true_when_repeated() {
let chain = MiddlewareChain::new()
.push(MiddlewareKind::Trace)
.push(MiddlewareKind::Cors)
.push(MiddlewareKind::Trace);
assert!(chain.has_duplicates());
}
#[test]
fn test_display_empty_chain() {
let chain = MiddlewareChain::new();
assert_eq!(chain.to_string(), "MiddlewareChain[]");
}
#[test]
fn test_display_single_element() {
let chain = MiddlewareChain::new().push(MiddlewareKind::Cors);
assert_eq!(chain.to_string(), "MiddlewareChain[cors]");
}
#[test]
fn test_display_multiple_elements() {
let chain = MiddlewareChain::php_global();
assert_eq!(chain.to_string(), "MiddlewareChain[trace -> cors]");
}
#[test]
fn test_display_full_default_chain() {
let chain = MiddlewareChain::default_chain();
assert_eq!(
chain.to_string(),
"MiddlewareChain[trace -> cors -> log -> rate_limit -> auth]"
);
}
#[test]
fn test_clone_produces_equal_chain() {
let chain = MiddlewareChain::default_chain();
let cloned = chain.clone();
assert_eq!(chain, cloned);
}
#[test]
fn test_eq_same_order() {
let a = MiddlewareChain::default_chain();
let b = MiddlewareChain::default_chain();
assert_eq!(a, b);
}
#[test]
fn test_ne_different_order() {
let a = MiddlewareChain::default_chain();
let b = MiddlewareChain::php_global();
assert_ne!(a, b);
}
#[test]
fn test_php_alignment_default_chain_includes_global() {
let chain = MiddlewareChain::default_chain();
let php_global = MiddlewareChain::php_global();
assert!(
chain.order().starts_with(php_global.order()),
"DEFAULT_ORDER must start with PHP global order"
);
}
#[test]
fn test_php_alignment_trace_first() {
let chain = MiddlewareChain::default_chain();
assert_eq!(chain.order().first(), Some(&MiddlewareKind::Trace));
}
#[test]
fn test_php_alignment_cors_second() {
let chain = MiddlewareChain::default_chain();
assert_eq!(chain.order().get(1), Some(&MiddlewareKind::Cors));
}
#[test]
fn test_php_alignment_auth_for_public_routes_can_be_removed() {
let mut chain = MiddlewareChain::default_chain();
let removed = chain.remove_kind(MiddlewareKind::Auth);
assert_eq!(removed, 1);
assert!(!chain.contains(MiddlewareKind::Auth));
assert!(chain.contains(MiddlewareKind::Trace));
assert!(chain.contains(MiddlewareKind::Cors));
assert!(chain.contains(MiddlewareKind::Log));
assert!(chain.contains(MiddlewareKind::RateLimit));
}
}