use std::cell::RefCell;
use std::collections::HashMap;
use serde_json::{json, Value};
#[derive(Debug, Clone)]
pub struct MiddlewareContext {
pub request: Value,
pub metadata: HashMap<String, Value>,
pub agent_id: String,
pub timestamp: String,
}
impl MiddlewareContext {
pub fn new(request: Value, agent_id: impl Into<String>, timestamp: impl Into<String>) -> Self {
Self {
request,
metadata: HashMap::new(),
agent_id: agent_id.into(),
timestamp: timestamp.into(),
}
}
pub fn get_metadata(&self, key: &str) -> Option<&Value> {
self.metadata.get(key)
}
pub fn set_metadata(&mut self, key: impl Into<String>, value: Value) {
self.metadata.insert(key.into(), value);
}
pub fn to_json(&self) -> Value {
json!({
"request": self.request,
"metadata": self.metadata,
"agent_id": self.agent_id,
"timestamp": self.timestamp,
})
}
}
#[derive(Debug, Clone)]
pub enum MiddlewareResult {
Continue(Value),
Halt(Value),
Error(String),
}
impl MiddlewareResult {
pub fn is_continue(&self) -> bool {
matches!(self, Self::Continue(_))
}
pub fn is_halt(&self) -> bool {
matches!(self, Self::Halt(_))
}
pub fn is_error(&self) -> bool {
matches!(self, Self::Error(_))
}
pub fn into_value(self) -> Value {
match self {
Self::Continue(v) | Self::Halt(v) => v,
Self::Error(e) => Value::String(e),
}
}
}
pub trait PipelineMiddleware {
fn name(&self) -> &str;
fn priority(&self) -> i32 {
0
}
fn process(&self, ctx: &mut MiddlewareContext) -> MiddlewareResult;
fn on_error(&self, _ctx: &MiddlewareContext, error: &str) -> MiddlewareResult {
MiddlewareResult::Error(error.to_string())
}
}
pub struct MiddlewarePipeline {
middlewares: Vec<Box<dyn PipelineMiddleware>>,
}
impl MiddlewarePipeline {
pub fn new() -> Self {
Self {
middlewares: Vec::new(),
}
}
pub fn add(&mut self, middleware: Box<dyn PipelineMiddleware>) {
self.middlewares.push(middleware);
}
pub fn execute(&self, ctx: &mut MiddlewareContext) -> MiddlewareResult {
if self.middlewares.is_empty() {
return MiddlewareResult::Continue(Value::Null);
}
let mut indices: Vec<usize> = (0..self.middlewares.len()).collect();
indices.sort_by_key(|&i| self.middlewares[i].priority());
let mut last_value = Value::Null;
let mut executed: Vec<usize> = Vec::new();
for &idx in &indices {
let mw = &self.middlewares[idx];
let result = mw.process(ctx);
match result {
MiddlewareResult::Continue(v) => {
last_value = v;
executed.push(idx);
}
MiddlewareResult::Halt(v) => {
return MiddlewareResult::Halt(v);
}
MiddlewareResult::Error(ref e) => {
let err_str = e.clone();
for &prev_idx in executed.iter().rev() {
let _ = self.middlewares[prev_idx].on_error(ctx, &err_str);
}
return MiddlewareResult::Error(err_str);
}
}
}
MiddlewareResult::Continue(last_value)
}
pub fn len(&self) -> usize {
self.middlewares.len()
}
pub fn is_empty(&self) -> bool {
self.middlewares.is_empty()
}
pub fn middleware_names(&self) -> Vec<&str> {
self.middlewares.iter().map(|m| m.name()).collect()
}
}
impl Default for MiddlewarePipeline {
fn default() -> Self {
Self::new()
}
}
pub struct LoggingMiddleware {
name: String,
logs: RefCell<Vec<String>>,
}
impl LoggingMiddleware {
pub fn new() -> Self {
Self {
name: "logging".to_string(),
logs: RefCell::new(Vec::new()),
}
}
pub fn logs(&self) -> Vec<String> {
self.logs.borrow().clone()
}
pub fn clear(&self) {
self.logs.borrow_mut().clear();
}
}
impl Default for LoggingMiddleware {
fn default() -> Self {
Self::new()
}
}
impl PipelineMiddleware for LoggingMiddleware {
fn name(&self) -> &str {
&self.name
}
fn process(&self, ctx: &mut MiddlewareContext) -> MiddlewareResult {
let entry = format!(
"[{}] agent={} request={}",
ctx.timestamp, ctx.agent_id, ctx.request
);
self.logs.borrow_mut().push(entry);
MiddlewareResult::Continue(ctx.request.clone())
}
fn on_error(&self, ctx: &MiddlewareContext, error: &str) -> MiddlewareResult {
let entry = format!(
"[{}] agent={} ERROR: {}",
ctx.timestamp, ctx.agent_id, error
);
self.logs.borrow_mut().push(entry);
MiddlewareResult::Error(error.to_string())
}
}
pub struct ValidationMiddleware {
name: String,
required_fields: Vec<String>,
}
impl ValidationMiddleware {
pub fn new() -> Self {
Self {
name: "validation".to_string(),
required_fields: Vec::new(),
}
}
pub fn require_field(mut self, name: impl Into<String>) -> Self {
self.required_fields.push(name.into());
self
}
}
impl Default for ValidationMiddleware {
fn default() -> Self {
Self::new()
}
}
impl PipelineMiddleware for ValidationMiddleware {
fn name(&self) -> &str {
&self.name
}
fn process(&self, ctx: &mut MiddlewareContext) -> MiddlewareResult {
if let Some(obj) = ctx.request.as_object() {
for field in &self.required_fields {
if !obj.contains_key(field) {
return MiddlewareResult::Error(format!("missing required field: '{}'", field));
}
}
MiddlewareResult::Continue(ctx.request.clone())
} else if self.required_fields.is_empty() {
MiddlewareResult::Continue(ctx.request.clone())
} else {
MiddlewareResult::Error(
"request is not a JSON object; cannot validate fields".to_string(),
)
}
}
}
pub struct TransformMiddleware {
name: String,
transform: Box<dyn Fn(Value) -> Value>,
}
impl TransformMiddleware {
pub fn new() -> Self {
Self {
name: "transform".to_string(),
transform: Box::new(|v| v),
}
}
pub fn with_transform(mut self, f: Box<dyn Fn(Value) -> Value>) -> Self {
self.transform = f;
self
}
}
impl Default for TransformMiddleware {
fn default() -> Self {
Self::new()
}
}
impl PipelineMiddleware for TransformMiddleware {
fn name(&self) -> &str {
&self.name
}
fn process(&self, ctx: &mut MiddlewareContext) -> MiddlewareResult {
let transformed = (self.transform)(ctx.request.clone());
ctx.request = transformed.clone();
MiddlewareResult::Continue(transformed)
}
}
pub struct RateLimitMiddleware {
name: String,
max_calls: usize,
call_count: RefCell<usize>,
}
impl RateLimitMiddleware {
pub fn new(max_calls: usize) -> Self {
Self {
name: "rate_limit".to_string(),
max_calls,
call_count: RefCell::new(0),
}
}
pub fn remaining(&self) -> usize {
let count = *self.call_count.borrow();
self.max_calls.saturating_sub(count)
}
pub fn reset(&self) {
*self.call_count.borrow_mut() = 0;
}
}
impl PipelineMiddleware for RateLimitMiddleware {
fn name(&self) -> &str {
&self.name
}
fn process(&self, ctx: &mut MiddlewareContext) -> MiddlewareResult {
let mut count = self.call_count.borrow_mut();
if *count >= self.max_calls {
return MiddlewareResult::Halt(json!({
"error": "rate limit exceeded",
"max_calls": self.max_calls,
}));
}
*count += 1;
MiddlewareResult::Continue(ctx.request.clone())
}
}
pub struct MiddlewareStack {
pipelines: HashMap<String, MiddlewarePipeline>,
}
impl MiddlewareStack {
pub fn new() -> Self {
Self {
pipelines: HashMap::new(),
}
}
pub fn register(&mut self, name: impl Into<String>, pipeline: MiddlewarePipeline) {
self.pipelines.insert(name.into(), pipeline);
}
pub fn get(&self, name: &str) -> Option<&MiddlewarePipeline> {
self.pipelines.get(name)
}
pub fn execute(&self, name: &str, ctx: &mut MiddlewareContext) -> MiddlewareResult {
match self.pipelines.get(name) {
Some(pipeline) => pipeline.execute(ctx),
None => MiddlewareResult::Error(format!("pipeline '{}' not found", name)),
}
}
pub fn pipeline_names(&self) -> Vec<&str> {
self.pipelines.keys().map(|s| s.as_str()).collect()
}
}
impl Default for MiddlewareStack {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
use serde_json::json;
struct PassThrough {
name: String,
priority: i32,
}
impl PassThrough {
fn new(name: &str, priority: i32) -> Self {
Self {
name: name.to_string(),
priority,
}
}
}
impl PipelineMiddleware for PassThrough {
fn name(&self) -> &str {
&self.name
}
fn priority(&self) -> i32 {
self.priority
}
fn process(&self, ctx: &mut MiddlewareContext) -> MiddlewareResult {
let mut order: Vec<String> = ctx
.get_metadata("order")
.and_then(|v| serde_json::from_value(v.clone()).ok())
.unwrap_or_default();
order.push(self.name.clone());
ctx.set_metadata("order", json!(order));
MiddlewareResult::Continue(ctx.request.clone())
}
}
struct AlwaysHalt {
name: String,
priority: i32,
}
impl AlwaysHalt {
fn new(name: &str, priority: i32) -> Self {
Self {
name: name.to_string(),
priority,
}
}
}
impl PipelineMiddleware for AlwaysHalt {
fn name(&self) -> &str {
&self.name
}
fn priority(&self) -> i32 {
self.priority
}
fn process(&self, _ctx: &mut MiddlewareContext) -> MiddlewareResult {
MiddlewareResult::Halt(json!({"halted_by": self.name}))
}
}
struct AlwaysError {
name: String,
priority: i32,
}
impl AlwaysError {
fn new(name: &str, priority: i32) -> Self {
Self {
name: name.to_string(),
priority,
}
}
}
impl PipelineMiddleware for AlwaysError {
fn name(&self) -> &str {
&self.name
}
fn priority(&self) -> i32 {
self.priority
}
fn process(&self, _ctx: &mut MiddlewareContext) -> MiddlewareResult {
MiddlewareResult::Error(format!("error from {}", self.name))
}
}
struct ErrorTracker {
name: String,
priority: i32,
error_calls: RefCell<Vec<String>>,
}
impl ErrorTracker {
fn new(name: &str, priority: i32) -> Self {
Self {
name: name.to_string(),
priority,
error_calls: RefCell::new(Vec::new()),
}
}
}
impl PipelineMiddleware for ErrorTracker {
fn name(&self) -> &str {
&self.name
}
fn priority(&self) -> i32 {
self.priority
}
fn process(&self, ctx: &mut MiddlewareContext) -> MiddlewareResult {
let mut order: Vec<String> = ctx
.get_metadata("order")
.and_then(|v| serde_json::from_value(v.clone()).ok())
.unwrap_or_default();
order.push(self.name.clone());
ctx.set_metadata("order", json!(order));
MiddlewareResult::Continue(ctx.request.clone())
}
fn on_error(&self, _ctx: &MiddlewareContext, error: &str) -> MiddlewareResult {
self.error_calls.borrow_mut().push(error.to_string());
MiddlewareResult::Error(error.to_string())
}
}
fn make_ctx() -> MiddlewareContext {
MiddlewareContext::new(json!({"key": "value"}), "agent-1", "2026-01-01T00:00:00Z")
}
#[test]
fn test_context_new() {
let ctx = make_ctx();
assert_eq!(ctx.agent_id, "agent-1");
assert_eq!(ctx.timestamp, "2026-01-01T00:00:00Z");
assert_eq!(ctx.request, json!({"key": "value"}));
assert!(ctx.metadata.is_empty());
}
#[test]
fn test_context_metadata_get_set() {
let mut ctx = make_ctx();
assert!(ctx.get_metadata("foo").is_none());
ctx.set_metadata("foo", json!(42));
assert_eq!(ctx.get_metadata("foo"), Some(&json!(42)));
}
#[test]
fn test_context_metadata_overwrite() {
let mut ctx = make_ctx();
ctx.set_metadata("k", json!("a"));
ctx.set_metadata("k", json!("b"));
assert_eq!(ctx.get_metadata("k"), Some(&json!("b")));
}
#[test]
fn test_context_to_json() {
let mut ctx = make_ctx();
ctx.set_metadata("m", json!(true));
let j = ctx.to_json();
assert_eq!(j["agent_id"], "agent-1");
assert_eq!(j["timestamp"], "2026-01-01T00:00:00Z");
assert_eq!(j["request"]["key"], "value");
assert_eq!(j["metadata"]["m"], true);
}
#[test]
fn test_context_clone() {
let mut ctx = make_ctx();
ctx.set_metadata("x", json!(1));
let ctx2 = ctx.clone();
assert_eq!(ctx2.agent_id, ctx.agent_id);
assert_eq!(ctx2.get_metadata("x"), Some(&json!(1)));
}
#[test]
fn test_result_continue() {
let r = MiddlewareResult::Continue(json!(1));
assert!(r.is_continue());
assert!(!r.is_halt());
assert!(!r.is_error());
}
#[test]
fn test_result_halt() {
let r = MiddlewareResult::Halt(json!(2));
assert!(!r.is_continue());
assert!(r.is_halt());
assert!(!r.is_error());
}
#[test]
fn test_result_error() {
let r = MiddlewareResult::Error("oops".into());
assert!(!r.is_continue());
assert!(!r.is_halt());
assert!(r.is_error());
}
#[test]
fn test_result_into_value_continue() {
let r = MiddlewareResult::Continue(json!({"a": 1}));
assert_eq!(r.into_value(), json!({"a": 1}));
}
#[test]
fn test_result_into_value_halt() {
let r = MiddlewareResult::Halt(json!("stopped"));
assert_eq!(r.into_value(), json!("stopped"));
}
#[test]
fn test_result_into_value_error() {
let r = MiddlewareResult::Error("bad".into());
assert_eq!(r.into_value(), json!("bad"));
}
#[test]
fn test_pipeline_new_is_empty() {
let p = MiddlewarePipeline::new();
assert!(p.is_empty());
assert_eq!(p.len(), 0);
}
#[test]
fn test_pipeline_default() {
let p = MiddlewarePipeline::default();
assert!(p.is_empty());
}
#[test]
fn test_pipeline_add_and_len() {
let mut p = MiddlewarePipeline::new();
p.add(Box::new(PassThrough::new("a", 0)));
p.add(Box::new(PassThrough::new("b", 0)));
assert_eq!(p.len(), 2);
assert!(!p.is_empty());
}
#[test]
fn test_pipeline_middleware_names() {
let mut p = MiddlewarePipeline::new();
p.add(Box::new(PassThrough::new("alpha", 0)));
p.add(Box::new(PassThrough::new("beta", 0)));
let names = p.middleware_names();
assert_eq!(names, vec!["alpha", "beta"]);
}
#[test]
fn test_pipeline_empty_execute() {
let p = MiddlewarePipeline::new();
let mut ctx = make_ctx();
let result = p.execute(&mut ctx);
assert!(result.is_continue());
assert_eq!(result.into_value(), Value::Null);
}
#[test]
fn test_pipeline_execution_order_by_priority() {
let mut p = MiddlewarePipeline::new();
p.add(Box::new(PassThrough::new("third", 30)));
p.add(Box::new(PassThrough::new("first", 10)));
p.add(Box::new(PassThrough::new("second", 20)));
let mut ctx = make_ctx();
let result = p.execute(&mut ctx);
assert!(result.is_continue());
let order: Vec<String> =
serde_json::from_value(ctx.get_metadata("order").unwrap().clone()).unwrap();
assert_eq!(order, vec!["first", "second", "third"]);
}
#[test]
fn test_pipeline_halt_stops_execution() {
let mut p = MiddlewarePipeline::new();
p.add(Box::new(PassThrough::new("before", 1)));
p.add(Box::new(AlwaysHalt::new("halter", 2)));
p.add(Box::new(PassThrough::new("after", 3)));
let mut ctx = make_ctx();
let result = p.execute(&mut ctx);
assert!(result.is_halt());
let order: Vec<String> =
serde_json::from_value(ctx.get_metadata("order").unwrap().clone()).unwrap();
assert_eq!(order, vec!["before"]);
}
#[test]
fn test_pipeline_error_stops_execution() {
let mut p = MiddlewarePipeline::new();
p.add(Box::new(PassThrough::new("before", 1)));
p.add(Box::new(AlwaysError::new("errorer", 2)));
p.add(Box::new(PassThrough::new("after", 3)));
let mut ctx = make_ctx();
let result = p.execute(&mut ctx);
assert!(result.is_error());
let order: Vec<String> =
serde_json::from_value(ctx.get_metadata("order").unwrap().clone()).unwrap();
assert_eq!(order, vec!["before"]);
}
#[test]
fn test_pipeline_all_halt() {
let mut p = MiddlewarePipeline::new();
p.add(Box::new(AlwaysHalt::new("h1", 1)));
p.add(Box::new(AlwaysHalt::new("h2", 2)));
let mut ctx = make_ctx();
let result = p.execute(&mut ctx);
assert!(result.is_halt());
assert_eq!(result.into_value()["halted_by"], "h1");
}
#[test]
fn test_pipeline_all_error() {
let mut p = MiddlewarePipeline::new();
p.add(Box::new(AlwaysError::new("e1", 1)));
p.add(Box::new(AlwaysError::new("e2", 2)));
let mut ctx = make_ctx();
let result = p.execute(&mut ctx);
assert!(result.is_error());
}
#[test]
fn test_pipeline_on_error_callback_invoked() {
let mut p = MiddlewarePipeline::new();
p.add(Box::new(ErrorTracker::new("tracker", 1)));
p.add(Box::new(AlwaysError::new("err", 2)));
let mut ctx = make_ctx();
let result = p.execute(&mut ctx);
assert!(result.is_error());
let order: Vec<String> =
serde_json::from_value(ctx.get_metadata("order").unwrap().clone()).unwrap();
assert_eq!(order, vec!["tracker"]);
}
#[test]
fn test_pipeline_single_middleware() {
let mut p = MiddlewarePipeline::new();
p.add(Box::new(PassThrough::new("only", 0)));
let mut ctx = make_ctx();
let result = p.execute(&mut ctx);
assert!(result.is_continue());
}
#[test]
fn test_logging_new() {
let lm = LoggingMiddleware::new();
assert_eq!(lm.name(), "logging");
assert!(lm.logs().is_empty());
}
#[test]
fn test_logging_records_request() {
let lm = LoggingMiddleware::new();
let mut ctx = make_ctx();
let result = lm.process(&mut ctx);
assert!(result.is_continue());
let logs = lm.logs();
assert_eq!(logs.len(), 1);
assert!(logs[0].contains("agent-1"));
assert!(logs[0].contains("2026-01-01T00:00:00Z"));
}
#[test]
fn test_logging_multiple_requests() {
let lm = LoggingMiddleware::new();
let mut ctx = make_ctx();
lm.process(&mut ctx);
lm.process(&mut ctx);
lm.process(&mut ctx);
assert_eq!(lm.logs().len(), 3);
}
#[test]
fn test_logging_clear() {
let lm = LoggingMiddleware::new();
let mut ctx = make_ctx();
lm.process(&mut ctx);
assert_eq!(lm.logs().len(), 1);
lm.clear();
assert!(lm.logs().is_empty());
}
#[test]
fn test_logging_on_error_records() {
let lm = LoggingMiddleware::new();
let ctx = make_ctx();
lm.on_error(&ctx, "something went wrong");
let logs = lm.logs();
assert_eq!(logs.len(), 1);
assert!(logs[0].contains("ERROR"));
assert!(logs[0].contains("something went wrong"));
}
#[test]
fn test_logging_in_pipeline() {
let mut p = MiddlewarePipeline::new();
let lm = LoggingMiddleware::new();
p.add(Box::new(lm));
let mut ctx = make_ctx();
let result = p.execute(&mut ctx);
assert!(result.is_continue());
}
#[test]
fn test_validation_no_fields_passes() {
let vm = ValidationMiddleware::new();
let mut ctx = make_ctx();
let result = vm.process(&mut ctx);
assert!(result.is_continue());
}
#[test]
fn test_validation_required_field_present() {
let vm = ValidationMiddleware::new().require_field("key");
let mut ctx = make_ctx();
let result = vm.process(&mut ctx);
assert!(result.is_continue());
}
#[test]
fn test_validation_required_field_missing() {
let vm = ValidationMiddleware::new().require_field("missing_field");
let mut ctx = make_ctx();
let result = vm.process(&mut ctx);
assert!(result.is_error());
if let MiddlewareResult::Error(e) = result {
assert!(e.contains("missing_field"));
}
}
#[test]
fn test_validation_multiple_required_fields() {
let vm = ValidationMiddleware::new()
.require_field("key")
.require_field("other");
let mut ctx = make_ctx();
let result = vm.process(&mut ctx);
assert!(result.is_error());
if let MiddlewareResult::Error(e) = result {
assert!(e.contains("other"));
}
}
#[test]
fn test_validation_non_object_with_fields() {
let vm = ValidationMiddleware::new().require_field("x");
let mut ctx = MiddlewareContext::new(json!("string_value"), "a", "t");
let result = vm.process(&mut ctx);
assert!(result.is_error());
}
#[test]
fn test_validation_non_object_no_fields() {
let vm = ValidationMiddleware::new();
let mut ctx = MiddlewareContext::new(json!(42), "a", "t");
let result = vm.process(&mut ctx);
assert!(result.is_continue());
}
#[test]
fn test_validation_all_fields_present() {
let vm = ValidationMiddleware::new()
.require_field("a")
.require_field("b");
let mut ctx = MiddlewareContext::new(json!({"a": 1, "b": 2, "c": 3}), "ag", "ts");
let result = vm.process(&mut ctx);
assert!(result.is_continue());
}
#[test]
fn test_transform_identity() {
let tm = TransformMiddleware::new();
let mut ctx = make_ctx();
let result = tm.process(&mut ctx);
assert!(result.is_continue());
assert_eq!(result.into_value(), json!({"key": "value"}));
}
#[test]
fn test_transform_custom_function() {
let tm = TransformMiddleware::new().with_transform(Box::new(|mut v| {
if let Some(obj) = v.as_object_mut() {
obj.insert("added".to_string(), json!(true));
}
v
}));
let mut ctx = make_ctx();
let result = tm.process(&mut ctx);
assert!(result.is_continue());
let val = result.into_value();
assert_eq!(val["added"], true);
assert_eq!(val["key"], "value");
}
#[test]
fn test_transform_modifies_context_request() {
let tm = TransformMiddleware::new().with_transform(Box::new(|_| json!({"new": "data"})));
let mut ctx = make_ctx();
tm.process(&mut ctx);
assert_eq!(ctx.request, json!({"new": "data"}));
}
#[test]
fn test_transform_chained_in_pipeline() {
let mut p = MiddlewarePipeline::new();
let t1 = TransformMiddleware {
name: "t1".to_string(),
transform: Box::new(|mut v| {
if let Some(obj) = v.as_object_mut() {
obj.insert("step1".to_string(), json!(true));
}
v
}),
};
let t2 = TransformMiddleware {
name: "t2".to_string(),
transform: Box::new(|mut v| {
if let Some(obj) = v.as_object_mut() {
obj.insert("step2".to_string(), json!(true));
}
v
}),
};
p.add(Box::new(t1));
p.add(Box::new(t2));
let mut ctx = make_ctx();
let result = p.execute(&mut ctx);
assert!(result.is_continue());
let val = result.into_value();
assert_eq!(val["step1"], true);
assert_eq!(val["step2"], true);
assert_eq!(val["key"], "value");
}
#[test]
fn test_rate_limit_allows_under_limit() {
let rl = RateLimitMiddleware::new(3);
let mut ctx = make_ctx();
assert!(rl.process(&mut ctx).is_continue());
assert!(rl.process(&mut ctx).is_continue());
assert!(rl.process(&mut ctx).is_continue());
assert_eq!(rl.remaining(), 0);
}
#[test]
fn test_rate_limit_halts_at_limit() {
let rl = RateLimitMiddleware::new(2);
let mut ctx = make_ctx();
rl.process(&mut ctx);
rl.process(&mut ctx);
let result = rl.process(&mut ctx);
assert!(result.is_halt());
}
#[test]
fn test_rate_limit_remaining() {
let rl = RateLimitMiddleware::new(5);
assert_eq!(rl.remaining(), 5);
let mut ctx = make_ctx();
rl.process(&mut ctx);
assert_eq!(rl.remaining(), 4);
rl.process(&mut ctx);
assert_eq!(rl.remaining(), 3);
}
#[test]
fn test_rate_limit_reset() {
let rl = RateLimitMiddleware::new(2);
let mut ctx = make_ctx();
rl.process(&mut ctx);
rl.process(&mut ctx);
assert_eq!(rl.remaining(), 0);
rl.reset();
assert_eq!(rl.remaining(), 2);
assert!(rl.process(&mut ctx).is_continue());
}
#[test]
fn test_rate_limit_zero_max() {
let rl = RateLimitMiddleware::new(0);
let mut ctx = make_ctx();
let result = rl.process(&mut ctx);
assert!(result.is_halt());
}
#[test]
fn test_rate_limit_halt_value_contains_info() {
let rl = RateLimitMiddleware::new(1);
let mut ctx = make_ctx();
rl.process(&mut ctx);
let result = rl.process(&mut ctx);
let val = result.into_value();
assert_eq!(val["max_calls"], 1);
}
#[test]
fn test_stack_new_is_empty() {
let s = MiddlewareStack::new();
assert!(s.pipeline_names().is_empty());
}
#[test]
fn test_stack_default() {
let s = MiddlewareStack::default();
assert!(s.pipeline_names().is_empty());
}
#[test]
fn test_stack_register_and_get() {
let mut s = MiddlewareStack::new();
let p = MiddlewarePipeline::new();
s.register("test", p);
assert!(s.get("test").is_some());
assert!(s.get("other").is_none());
}
#[test]
fn test_stack_pipeline_names() {
let mut s = MiddlewareStack::new();
s.register("alpha", MiddlewarePipeline::new());
s.register("beta", MiddlewarePipeline::new());
let mut names = s.pipeline_names();
names.sort();
assert_eq!(names, vec!["alpha", "beta"]);
}
#[test]
fn test_stack_execute_existing() {
let mut s = MiddlewareStack::new();
let mut p = MiddlewarePipeline::new();
p.add(Box::new(PassThrough::new("pt", 0)));
s.register("main", p);
let mut ctx = make_ctx();
let result = s.execute("main", &mut ctx);
assert!(result.is_continue());
}
#[test]
fn test_stack_execute_missing() {
let s = MiddlewareStack::new();
let mut ctx = make_ctx();
let result = s.execute("nonexistent", &mut ctx);
assert!(result.is_error());
}
#[test]
fn test_stack_replace_pipeline() {
let mut s = MiddlewareStack::new();
let p1 = MiddlewarePipeline::new();
s.register("x", p1);
assert_eq!(s.get("x").unwrap().len(), 0);
let mut p2 = MiddlewarePipeline::new();
p2.add(Box::new(PassThrough::new("new", 0)));
s.register("x", p2);
assert_eq!(s.get("x").unwrap().len(), 1);
}
#[test]
fn test_mixed_pipeline_validation_then_transform() {
let mut p = MiddlewarePipeline::new();
let vm = ValidationMiddleware::new().require_field("key");
let tm = TransformMiddleware::new().with_transform(Box::new(|mut v| {
if let Some(obj) = v.as_object_mut() {
obj.insert("validated".to_string(), json!(true));
}
v
}));
p.add(Box::new(vm));
p.add(Box::new(tm));
let mut ctx = make_ctx();
let result = p.execute(&mut ctx);
assert!(result.is_continue());
assert_eq!(result.into_value()["validated"], true);
}
#[test]
fn test_mixed_pipeline_validation_fails_before_transform() {
let mut p = MiddlewarePipeline::new();
let vm = ValidationMiddleware::new().require_field("nonexistent");
let tm = TransformMiddleware::new().with_transform(Box::new(|_| {
panic!("should not reach transform");
}));
p.add(Box::new(vm));
p.add(Box::new(tm));
let mut ctx = make_ctx();
let result = p.execute(&mut ctx);
assert!(result.is_error());
}
#[test]
fn test_rate_limit_in_pipeline() {
let mut p = MiddlewarePipeline::new();
p.add(Box::new(RateLimitMiddleware::new(2)));
p.add(Box::new(PassThrough::new("pt", 1)));
let mut ctx = make_ctx();
assert!(p.execute(&mut ctx).is_continue());
assert!(p.execute(&mut ctx).is_continue());
assert!(p.execute(&mut ctx).is_halt());
}
#[test]
fn test_on_error_default_returns_error() {
let pt = PassThrough::new("test", 0);
let ctx = make_ctx();
let result = pt.on_error(&ctx, "some error");
assert!(result.is_error());
if let MiddlewareResult::Error(e) = result {
assert_eq!(e, "some error");
}
}
#[test]
fn test_middleware_priority_default_is_zero() {
let vm = ValidationMiddleware::new();
assert_eq!(vm.priority(), 0);
}
#[test]
fn test_context_empty_request() {
let ctx = MiddlewareContext::new(json!(null), "a", "t");
assert_eq!(ctx.request, Value::Null);
}
#[test]
fn test_pipeline_same_priority_preserves_insertion_order() {
let mut p = MiddlewarePipeline::new();
p.add(Box::new(PassThrough::new("a", 0)));
p.add(Box::new(PassThrough::new("b", 0)));
p.add(Box::new(PassThrough::new("c", 0)));
let mut ctx = make_ctx();
p.execute(&mut ctx);
let order: Vec<String> =
serde_json::from_value(ctx.get_metadata("order").unwrap().clone()).unwrap();
assert_eq!(order, vec!["a", "b", "c"]);
}
#[test]
fn test_pipeline_negative_priority_runs_first() {
let mut p = MiddlewarePipeline::new();
p.add(Box::new(PassThrough::new("normal", 0)));
p.add(Box::new(PassThrough::new("early", -10)));
p.add(Box::new(PassThrough::new("late", 10)));
let mut ctx = make_ctx();
p.execute(&mut ctx);
let order: Vec<String> =
serde_json::from_value(ctx.get_metadata("order").unwrap().clone()).unwrap();
assert_eq!(order, vec!["early", "normal", "late"]);
}
#[test]
fn test_logging_default() {
let lm = LoggingMiddleware::default();
assert_eq!(lm.name(), "logging");
}
#[test]
fn test_validation_default() {
let vm = ValidationMiddleware::default();
assert_eq!(vm.name(), "validation");
}
#[test]
fn test_transform_default() {
let tm = TransformMiddleware::default();
assert_eq!(tm.name(), "transform");
}
}