Skip to main content

mockforge_core/protocol_abstraction/
mod.rs

1//! Protocol-agnostic abstractions for unified mocking across HTTP, GraphQL, gRPC, and WebSocket
2//!
3//! This module provides traits and types that abstract common patterns across different
4//! protocols, enabling code reuse for spec-driven mocking, middleware, and request matching.
5
6pub mod auth;
7pub mod base_registry;
8pub mod matcher;
9pub mod middleware;
10pub mod protocol_registry;
11pub mod streaming;
12
13use crate::Result;
14use serde::{Deserialize, Serialize};
15use std::collections::HashMap;
16use std::fmt;
17use std::sync::Arc;
18
19// Re-export middleware types
20pub use auth::{AuthMiddleware, AuthResult, Claims};
21pub use base_registry::{BaseSpecRegistry, ProtocolFixture};
22pub use matcher::{FuzzyRequestMatcher, RequestFingerprint, SimpleRequestMatcher};
23pub use middleware::{LatencyMiddleware, LoggingMiddleware, MetricsMiddleware};
24pub use protocol_registry::{ProtocolHandler, ProtocolRegistry};
25pub use streaming::{
26    MessageBuilder, MessageStream, ProtocolMessage, StreamingMetadata, StreamingProtocol,
27    StreamingProtocolRegistry,
28};
29
30/// Protocol type enumeration for multi-protocol support
31#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
32#[cfg_attr(feature = "schema", derive(schemars::JsonSchema))]
33pub enum Protocol {
34    /// HTTP/REST protocol for RESTful APIs
35    Http,
36    /// GraphQL protocol for GraphQL APIs
37    GraphQL,
38    /// gRPC protocol for gRPC services
39    Grpc,
40    /// WebSocket protocol for real-time bidirectional communication
41    WebSocket,
42    /// SMTP/Email protocol for email communication
43    Smtp,
44    /// MQTT protocol for IoT messaging and pub/sub
45    Mqtt,
46    /// FTP protocol for file transfer operations
47    Ftp,
48    /// Kafka protocol for distributed event streaming
49    Kafka,
50    /// RabbitMQ/AMQP protocol for message queuing
51    RabbitMq,
52    /// AMQP protocol for advanced message queuing scenarios
53    Amqp,
54    /// TCP protocol for raw TCP connections
55    Tcp,
56}
57
58impl fmt::Display for Protocol {
59    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
60        match self {
61            Protocol::Http => write!(f, "HTTP"),
62            Protocol::GraphQL => write!(f, "GraphQL"),
63            Protocol::Grpc => write!(f, "gRPC"),
64            Protocol::WebSocket => write!(f, "WebSocket"),
65            Protocol::Smtp => write!(f, "SMTP"),
66            Protocol::Mqtt => write!(f, "MQTT"),
67            Protocol::Ftp => write!(f, "FTP"),
68            Protocol::Kafka => write!(f, "Kafka"),
69            Protocol::RabbitMq => write!(f, "RabbitMQ"),
70            Protocol::Amqp => write!(f, "AMQP"),
71            Protocol::Tcp => write!(f, "TCP"),
72        }
73    }
74}
75
76/// Message pattern enumeration for different communication patterns
77#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
78pub enum MessagePattern {
79    /// Request-Response pattern (HTTP, gRPC unary)
80    RequestResponse,
81    /// One-way/fire-and-forget pattern (MQTT publish, email)
82    OneWay,
83    /// Publish-Subscribe pattern (Kafka, RabbitMQ, MQTT)
84    PubSub,
85    /// Streaming pattern (gRPC streaming, WebSocket)
86    Streaming,
87}
88
89impl fmt::Display for MessagePattern {
90    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
91        match self {
92            MessagePattern::RequestResponse => write!(f, "Request-Response"),
93            MessagePattern::OneWay => write!(f, "One-Way"),
94            MessagePattern::PubSub => write!(f, "Pub-Sub"),
95            MessagePattern::Streaming => write!(f, "Streaming"),
96        }
97    }
98}
99
100/// A protocol-agnostic request representation
101#[derive(Debug, Clone)]
102pub struct ProtocolRequest {
103    /// The protocol this request uses
104    pub protocol: Protocol,
105    /// Message pattern for this request
106    pub pattern: MessagePattern,
107    /// Method or operation (e.g., "GET", "Query.users", "greeter.SayHello")
108    pub operation: String,
109    /// Path, query name, or service/method name
110    pub path: String,
111    /// Topic for pub/sub protocols (MQTT, Kafka)
112    pub topic: Option<String>,
113    /// Routing key for message queuing protocols (AMQP, RabbitMQ)
114    pub routing_key: Option<String>,
115    /// Partition for partitioned protocols (Kafka)
116    pub partition: Option<i32>,
117    /// Quality of Service level (MQTT: 0, 1, 2)
118    pub qos: Option<u8>,
119    /// Request metadata (headers, metadata, etc.)
120    pub metadata: HashMap<String, String>,
121    /// Request body/payload as bytes
122    pub body: Option<Vec<u8>>,
123    /// Client IP address if available
124    pub client_ip: Option<String>,
125}
126
127impl Default for ProtocolRequest {
128    fn default() -> Self {
129        Self {
130            protocol: Protocol::Http,
131            pattern: MessagePattern::RequestResponse,
132            operation: String::new(),
133            path: String::new(),
134            topic: None,
135            routing_key: None,
136            partition: None,
137            qos: None,
138            metadata: HashMap::new(),
139            body: None,
140            client_ip: None,
141        }
142    }
143}
144
145/// A protocol-agnostic response representation
146#[derive(Debug, Clone)]
147pub struct ProtocolResponse {
148    /// Status code or success indicator (HTTP: 200, gRPC: OK, GraphQL: no errors)
149    pub status: ResponseStatus,
150    /// Response metadata (headers, metadata, etc.)
151    pub metadata: HashMap<String, String>,
152    /// Response body/payload
153    pub body: Vec<u8>,
154    /// Content type or serialization format
155    pub content_type: String,
156}
157
158/// Response status abstraction across protocols
159#[derive(Debug, Clone, PartialEq, Eq)]
160pub enum ResponseStatus {
161    /// HTTP status code
162    HttpStatus(u16),
163    /// gRPC status code
164    GrpcStatus(i32),
165    /// GraphQL success (true) or error (false)
166    GraphQLStatus(bool),
167    /// WebSocket status
168    WebSocketStatus(bool),
169    /// SMTP status code (2xx = success, 4xx/5xx = error)
170    SmtpStatus(u16),
171    /// MQTT status (true = success, false = error)
172    MqttStatus(bool),
173    /// Kafka status code (0 = success, non-zero = error)
174    KafkaStatus(i16),
175    /// AMQP/RabbitMQ status code
176    AmqpStatus(u16),
177    /// FTP status code
178    FtpStatus(u16),
179    /// TCP status (true = success, false = error/close)
180    TcpStatus(bool),
181}
182
183impl ResponseStatus {
184    /// Check if the response is successful
185    pub fn is_success(&self) -> bool {
186        match self {
187            ResponseStatus::HttpStatus(code) => (200..300).contains(code),
188            ResponseStatus::GrpcStatus(code) => *code == 0, // gRPC OK = 0
189            ResponseStatus::GraphQLStatus(success) => *success,
190            ResponseStatus::WebSocketStatus(success) => *success,
191            ResponseStatus::SmtpStatus(code) => (200..300).contains(code), // 2xx codes are success
192            ResponseStatus::MqttStatus(success) => *success,
193            ResponseStatus::KafkaStatus(code) => *code == 0, // Kafka OK = 0
194            ResponseStatus::AmqpStatus(code) => (200..300).contains(code), // AMQP success codes
195            ResponseStatus::FtpStatus(code) => (200..300).contains(code), // FTP success codes
196            ResponseStatus::TcpStatus(success) => *success,  // TCP success flag
197        }
198    }
199
200    /// Get numeric representation if applicable
201    pub fn as_code(&self) -> Option<i32> {
202        match self {
203            ResponseStatus::HttpStatus(code) => Some(*code as i32),
204            ResponseStatus::GrpcStatus(code) => Some(*code),
205            ResponseStatus::SmtpStatus(code) => Some(*code as i32),
206            ResponseStatus::KafkaStatus(code) => Some(*code as i32),
207            ResponseStatus::AmqpStatus(code) => Some(*code as i32),
208            ResponseStatus::FtpStatus(code) => Some(*code as i32),
209            ResponseStatus::TcpStatus(_) => None, // TCP uses boolean status
210            ResponseStatus::GraphQLStatus(_)
211            | ResponseStatus::WebSocketStatus(_)
212            | ResponseStatus::MqttStatus(_) => None,
213        }
214    }
215}
216
217/// Trait for spec-driven mocking registries (OpenAPI, GraphQL schema, Proto files)
218pub trait SpecRegistry: Send + Sync {
219    /// Get the protocol this registry handles
220    fn protocol(&self) -> Protocol;
221
222    /// Get all available operations/routes in this spec
223    fn operations(&self) -> Vec<SpecOperation>;
224
225    /// Find an operation by path/name
226    fn find_operation(&self, operation: &str, path: &str) -> Option<SpecOperation>;
227
228    /// Validate a request against the spec
229    fn validate_request(&self, request: &ProtocolRequest) -> Result<ValidationResult>;
230
231    /// Generate a mock response for a request
232    fn generate_mock_response(&self, request: &ProtocolRequest) -> Result<ProtocolResponse>;
233}
234
235/// Represents a single operation in a spec (endpoint, query, RPC method)
236#[derive(Debug, Clone)]
237pub struct SpecOperation {
238    /// Operation name or identifier
239    pub name: String,
240    /// Path or fully qualified name
241    pub path: String,
242    /// Operation type (GET, POST, Query, Mutation, RPC)
243    pub operation_type: String,
244    /// Input schema/type information
245    pub input_schema: Option<String>,
246    /// Output schema/type information
247    pub output_schema: Option<String>,
248    /// Metadata from spec
249    pub metadata: HashMap<String, String>,
250}
251
252/// Result of request validation
253#[derive(Debug, Clone)]
254pub struct ValidationResult {
255    /// Whether validation passed
256    pub valid: bool,
257    /// Validation errors if any
258    pub errors: Vec<ValidationError>,
259    /// Validation warnings
260    pub warnings: Vec<String>,
261}
262
263/// A validation error
264#[derive(Debug, Clone)]
265pub struct ValidationError {
266    /// Error message
267    pub message: String,
268    /// Path to the error (e.g., "body.user.email")
269    pub path: Option<String>,
270    /// Error code
271    pub code: Option<String>,
272}
273
274impl ValidationResult {
275    /// Create a successful validation result
276    pub fn success() -> Self {
277        Self {
278            valid: true,
279            errors: Vec::new(),
280            warnings: Vec::new(),
281        }
282    }
283
284    /// Create a failed validation result with errors
285    pub fn failure(errors: Vec<ValidationError>) -> Self {
286        Self {
287            valid: false,
288            errors,
289            warnings: Vec::new(),
290        }
291    }
292
293    /// Add a warning to the result
294    pub fn with_warning(mut self, warning: String) -> Self {
295        self.warnings.push(warning);
296        self
297    }
298}
299
300/// Result of middleware request processing
301///
302/// Allows middleware to either continue the chain or short-circuit with a response
303/// (e.g., auth middleware rejecting an unauthenticated request with a 401).
304#[derive(Debug)]
305pub enum MiddlewareAction {
306    /// Continue processing the next middleware in the chain
307    Continue,
308    /// Stop the chain and return this response immediately
309    ShortCircuit(ProtocolResponse),
310}
311
312/// Trait for protocol-agnostic middleware
313#[async_trait::async_trait]
314pub trait ProtocolMiddleware: Send + Sync {
315    /// Get the name of this middleware
316    fn name(&self) -> &str;
317
318    /// Process a request before it reaches the handler
319    ///
320    /// Returns `MiddlewareAction::Continue` to pass the request to the next middleware,
321    /// or `MiddlewareAction::ShortCircuit(response)` to stop the chain and return early.
322    async fn process_request(&self, request: &mut ProtocolRequest) -> Result<MiddlewareAction>;
323
324    /// Process a response before it's returned to the client
325    async fn process_response(
326        &self,
327        request: &ProtocolRequest,
328        response: &mut ProtocolResponse,
329    ) -> Result<()>;
330
331    /// Check if this middleware should run for a given protocol
332    fn supports_protocol(&self, protocol: Protocol) -> bool;
333}
334
335/// Trait for request matching across protocols
336pub trait RequestMatcher: Send + Sync {
337    /// Match a request and return a score (higher = better match)
338    fn match_score(&self, request: &ProtocolRequest) -> f64;
339
340    /// Get the protocol this matcher handles
341    fn protocol(&self) -> Protocol;
342}
343
344/// Unified fixture format supporting all protocols
345#[derive(Debug, Clone, Serialize, Deserialize)]
346pub struct UnifiedFixture {
347    /// Unique identifier for this fixture
348    pub id: String,
349
350    /// Human-readable name
351    pub name: String,
352
353    /// Description of what this fixture does
354    #[serde(default)]
355    pub description: String,
356
357    /// Protocol this fixture applies to
358    pub protocol: Protocol,
359
360    /// Request matching criteria
361    pub request: FixtureRequest,
362
363    /// Response configuration
364    pub response: FixtureResponse,
365
366    /// Additional metadata
367    #[serde(default)]
368    pub metadata: HashMap<String, serde_json::Value>,
369
370    /// Whether this fixture is enabled
371    #[serde(default = "default_true")]
372    pub enabled: bool,
373
374    /// Priority for matching (higher = matched first)
375    #[serde(default)]
376    pub priority: i32,
377
378    /// Tags for organization
379    #[serde(default)]
380    pub tags: Vec<String>,
381}
382
383/// Request matching criteria for fixtures
384#[derive(Debug, Clone, Serialize, Deserialize)]
385pub struct FixtureRequest {
386    /// Message pattern to match
387    #[serde(default)]
388    pub pattern: Option<MessagePattern>,
389
390    /// Operation/method to match (exact or regex)
391    pub operation: Option<String>,
392
393    /// Path/route to match (exact or regex)
394    pub path: Option<String>,
395
396    /// Topic to match (for pub/sub protocols)
397    pub topic: Option<String>,
398
399    /// Routing key to match (for message queuing)
400    pub routing_key: Option<String>,
401
402    /// Partition to match
403    pub partition: Option<i32>,
404
405    /// QoS level to match
406    pub qos: Option<u8>,
407
408    /// Headers/metadata to match (key-value pairs)
409    #[serde(default)]
410    pub headers: HashMap<String, String>,
411
412    /// Request body pattern (regex for text, or exact match)
413    pub body_pattern: Option<String>,
414
415    /// Custom matching logic (script or expression)
416    pub custom_matcher: Option<String>,
417}
418
419/// Response configuration for fixtures
420#[derive(Debug, Clone, Serialize, Deserialize)]
421pub struct FixtureResponse {
422    /// Response status
423    pub status: FixtureStatus,
424
425    /// Response headers
426    #[serde(default)]
427    pub headers: HashMap<String, String>,
428
429    /// Response body (can be string, JSON, or base64-encoded binary)
430    pub body: Option<serde_json::Value>,
431
432    /// Content type
433    pub content_type: Option<String>,
434
435    /// Response delay in milliseconds
436    #[serde(default)]
437    pub delay_ms: u64,
438
439    /// Template variables for dynamic responses
440    #[serde(default)]
441    pub template_vars: HashMap<String, serde_json::Value>,
442}
443
444/// Status representation for fixtures
445#[derive(Debug, Clone, Serialize, Deserialize)]
446#[serde(untagged)]
447pub enum FixtureStatus {
448    /// HTTP status code
449    Http(u16),
450    /// gRPC status code
451    Grpc(i32),
452    /// Generic success/failure
453    Generic(bool),
454    /// Custom status with code and message
455    Custom {
456        /// Custom status code
457        code: i32,
458        /// Custom status message
459        message: String,
460    },
461}
462
463fn default_true() -> bool {
464    true
465}
466
467impl UnifiedFixture {
468    /// Check if this fixture matches the given protocol request
469    pub fn matches(&self, request: &ProtocolRequest) -> bool {
470        // Check protocol
471        if request.protocol != self.protocol {
472            return false;
473        }
474
475        // Check pattern
476        if let Some(pattern) = &self.request.pattern {
477            if request.pattern != *pattern {
478                return false;
479            }
480        }
481
482        // Check operation
483        if let Some(operation) = &self.request.operation {
484            if !self.matches_pattern(&request.operation, operation) {
485                return false;
486            }
487        }
488
489        // Check path
490        if let Some(path) = &self.request.path {
491            if !self.matches_pattern(&request.path, path) {
492                return false;
493            }
494        }
495
496        // Check topic
497        if let Some(topic) = &self.request.topic {
498            if !self.matches_pattern(request.topic.as_ref().unwrap_or(&String::new()), topic) {
499                return false;
500            }
501        }
502
503        // Check routing key
504        if let Some(routing_key) = &self.request.routing_key {
505            if !self.matches_pattern(
506                request.routing_key.as_ref().unwrap_or(&String::new()),
507                routing_key,
508            ) {
509                return false;
510            }
511        }
512
513        // Check partition
514        if let Some(partition) = self.request.partition {
515            if request.partition != Some(partition) {
516                return false;
517            }
518        }
519
520        // Check QoS
521        if let Some(qos) = self.request.qos {
522            if request.qos != Some(qos) {
523                return false;
524            }
525        }
526
527        // Check headers
528        for (key, expected_value) in &self.request.headers {
529            if let Some(actual_value) = request.metadata.get(key) {
530                if !self.matches_pattern(actual_value, expected_value) {
531                    return false;
532                }
533            } else {
534                return false;
535            }
536        }
537
538        // Check body pattern
539        if let Some(pattern) = &self.request.body_pattern {
540            if let Some(body) = &request.body {
541                let body_str = String::from_utf8_lossy(body);
542                if !self.matches_pattern(&body_str, pattern) {
543                    return false;
544                }
545            } else {
546                return false;
547            }
548        }
549
550        // Check custom matcher logic
551        if let Some(custom_matcher) = &self.request.custom_matcher {
552            if !self.evaluate_custom_matcher(custom_matcher, request) {
553                return false;
554            }
555        }
556
557        true
558    }
559
560    /// Helper method to match patterns (supports regex and exact match)
561    fn matches_pattern(&self, value: &str, pattern: &str) -> bool {
562        use regex::Regex;
563
564        // Try regex first
565        if let Ok(re) = Regex::new(pattern) {
566            re.is_match(value)
567        } else {
568            // Fall back to exact match
569            value == pattern
570        }
571    }
572
573    /// Evaluate custom matcher expression
574    fn evaluate_custom_matcher(&self, expression: &str, request: &ProtocolRequest) -> bool {
575        // Simple expression evaluator for custom matching logic
576        // Supports basic conditions like:
577        // - operation == "GET"
578        // - path =~ "/api/.*"
579        // - headers.content-type == "application/json"
580        // - body contains "test"
581
582        let expr = expression.trim();
583
584        // Handle different types of expressions
585        if expr.contains("==") {
586            self.evaluate_equality(expr, request)
587        } else if expr.contains("=~") {
588            self.evaluate_regex_match(expr, request)
589        } else if expr.contains("contains") {
590            self.evaluate_contains(expr, request)
591        } else {
592            // Unknown expression format, log warning and return false
593            tracing::warn!("Unknown custom matcher expression format: {}", expr);
594            false
595        }
596    }
597
598    /// Evaluate equality expressions (field == "value")
599    fn evaluate_equality(&self, expr: &str, request: &ProtocolRequest) -> bool {
600        let parts: Vec<&str> = expr.split("==").map(|s| s.trim()).collect();
601        if parts.len() != 2 {
602            return false;
603        }
604
605        let field = parts[0];
606        let expected_value = parts[1].trim_matches('"');
607
608        match field {
609            "operation" => request.operation == expected_value,
610            "path" => request.path == expected_value,
611            "topic" => request.topic.as_ref().unwrap_or(&String::new()) == expected_value,
612            "routing_key" => {
613                request.routing_key.as_ref().unwrap_or(&String::new()) == expected_value
614            }
615            _ if field.starts_with("headers.") => {
616                let header_name = &field[8..]; // Remove "headers." prefix
617                request.metadata.get(header_name).is_some_and(|v| v == expected_value)
618            }
619            _ => {
620                tracing::warn!("Unknown field in equality expression: {}", field);
621                false
622            }
623        }
624    }
625
626    /// Evaluate regex match expressions (field =~ "pattern")
627    fn evaluate_regex_match(&self, expr: &str, request: &ProtocolRequest) -> bool {
628        let parts: Vec<&str> = expr.split("=~").map(|s| s.trim()).collect();
629        if parts.len() != 2 {
630            return false;
631        }
632
633        let field = parts[0];
634        let pattern = parts[1].trim_matches('"');
635
636        let value: String = match field {
637            "operation" => request.operation.clone(),
638            "path" => request.path.clone(),
639            "topic" => request.topic.clone().unwrap_or_default(),
640            "routing_key" => request.routing_key.clone().unwrap_or_default(),
641            _ if field.starts_with("headers.") => {
642                let header_name = &field[8..]; // Remove "headers." prefix
643                request.metadata.get(header_name).cloned().unwrap_or_default()
644            }
645            _ => {
646                tracing::warn!("Unknown field in regex expression: {}", field);
647                return false;
648            }
649        };
650
651        use regex::Regex;
652        match Regex::new(pattern) {
653            Ok(re) => re.is_match(&value),
654            Err(e) => {
655                tracing::warn!("Invalid regex pattern '{}': {}", pattern, e);
656                false
657            }
658        }
659    }
660
661    /// Evaluate contains expressions (field contains "substring")
662    fn evaluate_contains(&self, expr: &str, request: &ProtocolRequest) -> bool {
663        let parts: Vec<&str> = expr.split("contains").map(|s| s.trim()).collect();
664        if parts.len() != 2 {
665            return false;
666        }
667
668        let field = parts[0];
669        let substring = parts[1].trim_matches('"');
670
671        let value: String = match field {
672            "body" => {
673                if let Some(body) = &request.body {
674                    String::from_utf8_lossy(body).to_string()
675                } else {
676                    return false;
677                }
678            }
679            _ if field.starts_with("headers.") => {
680                let header_name = &field[8..]; // Remove "headers." prefix
681                request.metadata.get(header_name).cloned().unwrap_or_default()
682            }
683            _ => {
684                tracing::warn!("Unsupported field for contains expression: {}", field);
685                return false;
686            }
687        };
688
689        value.contains(substring)
690    }
691
692    /// Convert fixture response to ProtocolResponse
693    pub fn to_protocol_response(&self) -> Result<ProtocolResponse> {
694        let status = match &self.response.status {
695            FixtureStatus::Http(code) => ResponseStatus::HttpStatus(*code),
696            FixtureStatus::Grpc(code) => ResponseStatus::GrpcStatus(*code),
697            FixtureStatus::Generic(success) => ResponseStatus::GraphQLStatus(*success), // Using GraphQL as generic
698            FixtureStatus::Custom { code, .. } => ResponseStatus::GrpcStatus(*code), // Using gRPC as custom
699        };
700
701        let body = match &self.response.body {
702            Some(serde_json::Value::String(s)) => s.clone().into_bytes(),
703            Some(value) => serde_json::to_string(value)?.into_bytes(),
704            None => Vec::new(),
705        };
706
707        let content_type = self
708            .response
709            .content_type
710            .clone()
711            .unwrap_or_else(|| "application/json".to_string());
712
713        Ok(ProtocolResponse {
714            status,
715            metadata: self.response.headers.clone(),
716            body,
717            content_type,
718        })
719    }
720}
721
722/// Middleware chain for composing and executing multiple middleware in sequence
723pub struct MiddlewareChain {
724    /// Ordered list of middleware to execute
725    middleware: Vec<Arc<dyn ProtocolMiddleware>>,
726}
727
728impl MiddlewareChain {
729    /// Create a new middleware chain
730    pub fn new() -> Self {
731        Self {
732            middleware: Vec::new(),
733        }
734    }
735
736    /// Add middleware to the chain
737    pub fn with_middleware(mut self, middleware: Arc<dyn ProtocolMiddleware>) -> Self {
738        self.middleware.push(middleware);
739        self
740    }
741
742    /// Process a request through all middleware
743    ///
744    /// Returns `Ok(None)` if all middleware passed (continue to handler),
745    /// or `Ok(Some(response))` if a middleware short-circuited the chain.
746    pub async fn process_request(
747        &self,
748        request: &mut ProtocolRequest,
749    ) -> Result<Option<ProtocolResponse>> {
750        for middleware in &self.middleware {
751            if middleware.supports_protocol(request.protocol) {
752                match middleware.process_request(request).await? {
753                    MiddlewareAction::Continue => {}
754                    MiddlewareAction::ShortCircuit(response) => {
755                        tracing::debug!(
756                            middleware = middleware.name(),
757                            "Middleware short-circuited request processing"
758                        );
759                        return Ok(Some(response));
760                    }
761                }
762            }
763        }
764        Ok(None)
765    }
766
767    /// Process a response through all middleware (in reverse order)
768    pub async fn process_response(
769        &self,
770        request: &ProtocolRequest,
771        response: &mut ProtocolResponse,
772    ) -> Result<()> {
773        for middleware in self.middleware.iter().rev() {
774            if middleware.supports_protocol(request.protocol) {
775                middleware.process_response(request, response).await?;
776            }
777        }
778        Ok(())
779    }
780}
781
782impl Default for MiddlewareChain {
783    fn default() -> Self {
784        Self::new()
785    }
786}
787
788#[cfg(test)]
789mod tests {
790    use super::*;
791
792    #[test]
793    fn test_protocol_display() {
794        assert_eq!(Protocol::Http.to_string(), "HTTP");
795        assert_eq!(Protocol::GraphQL.to_string(), "GraphQL");
796        assert_eq!(Protocol::Grpc.to_string(), "gRPC");
797        assert_eq!(Protocol::WebSocket.to_string(), "WebSocket");
798        assert_eq!(Protocol::Smtp.to_string(), "SMTP");
799        assert_eq!(Protocol::Mqtt.to_string(), "MQTT");
800        assert_eq!(Protocol::Ftp.to_string(), "FTP");
801        assert_eq!(Protocol::Kafka.to_string(), "Kafka");
802        assert_eq!(Protocol::RabbitMq.to_string(), "RabbitMQ");
803        assert_eq!(Protocol::Amqp.to_string(), "AMQP");
804        assert_eq!(Protocol::Tcp.to_string(), "TCP");
805    }
806
807    #[test]
808    fn test_response_status_is_success() {
809        assert!(ResponseStatus::HttpStatus(200).is_success());
810        assert!(ResponseStatus::HttpStatus(204).is_success());
811        assert!(!ResponseStatus::HttpStatus(404).is_success());
812        assert!(!ResponseStatus::HttpStatus(500).is_success());
813
814        assert!(ResponseStatus::GrpcStatus(0).is_success());
815        assert!(!ResponseStatus::GrpcStatus(2).is_success());
816
817        assert!(ResponseStatus::GraphQLStatus(true).is_success());
818        assert!(!ResponseStatus::GraphQLStatus(false).is_success());
819    }
820
821    #[test]
822    fn test_response_status_as_code() {
823        assert_eq!(ResponseStatus::HttpStatus(200).as_code(), Some(200));
824        assert_eq!(ResponseStatus::GrpcStatus(0).as_code(), Some(0));
825        assert_eq!(ResponseStatus::GraphQLStatus(true).as_code(), None);
826    }
827
828    #[test]
829    fn test_validation_result_success() {
830        let result = ValidationResult::success();
831        assert!(result.valid);
832        assert_eq!(result.errors.len(), 0);
833        assert_eq!(result.warnings.len(), 0);
834    }
835
836    #[test]
837    fn test_validation_result_failure() {
838        let errors = vec![ValidationError {
839            message: "Invalid field".to_string(),
840            path: Some("body.field".to_string()),
841            code: Some("INVALID_FIELD".to_string()),
842        }];
843        let result = ValidationResult::failure(errors);
844        assert!(!result.valid);
845        assert_eq!(result.errors.len(), 1);
846    }
847
848    #[test]
849    fn test_validation_result_with_warning() {
850        let result = ValidationResult::success().with_warning("Deprecated field used".to_string());
851        assert!(result.valid);
852        assert_eq!(result.warnings.len(), 1);
853    }
854
855    #[test]
856    fn test_middleware_chain_creation() {
857        let chain = MiddlewareChain::new();
858        assert_eq!(chain.middleware.len(), 0);
859    }
860
861    #[test]
862    fn test_protocol_request_creation() {
863        let request = ProtocolRequest {
864            protocol: Protocol::Http,
865            operation: "GET".to_string(),
866            path: "/users".to_string(),
867            client_ip: Some("127.0.0.1".to_string()),
868            ..Default::default()
869        };
870        assert_eq!(request.protocol, Protocol::Http);
871        assert_eq!(request.pattern, MessagePattern::RequestResponse);
872        assert_eq!(request.operation, "GET");
873        assert_eq!(request.path, "/users");
874    }
875
876    #[test]
877    fn test_protocol_response_creation() {
878        let response = ProtocolResponse {
879            status: ResponseStatus::HttpStatus(200),
880            metadata: HashMap::new(),
881            body: b"{}".to_vec(),
882            content_type: "application/json".to_string(),
883        };
884        assert!(response.status.is_success());
885        assert_eq!(response.content_type, "application/json");
886    }
887
888    #[test]
889    fn test_unified_fixture_matching() {
890        let fixture = UnifiedFixture {
891            id: "test-fixture".to_string(),
892            name: "Test Fixture".to_string(),
893            description: "A test fixture".to_string(),
894            protocol: Protocol::Http,
895            request: FixtureRequest {
896                pattern: Some(MessagePattern::RequestResponse),
897                operation: Some("GET".to_string()),
898                path: Some("/api/users".to_string()),
899                topic: None,
900                routing_key: None,
901                partition: None,
902                qos: None,
903                headers: HashMap::new(),
904                body_pattern: None,
905                custom_matcher: None,
906            },
907            response: FixtureResponse {
908                status: FixtureStatus::Http(200),
909                headers: HashMap::new(),
910                body: Some(serde_json::json!({"users": ["john", "jane"]})),
911                content_type: Some("application/json".to_string()),
912                delay_ms: 0,
913                template_vars: HashMap::new(),
914            },
915            metadata: HashMap::new(),
916            enabled: true,
917            priority: 0,
918            tags: vec![],
919        };
920
921        let matching_request = ProtocolRequest {
922            protocol: Protocol::Http,
923            pattern: MessagePattern::RequestResponse,
924            operation: "GET".to_string(),
925            path: "/api/users".to_string(),
926            topic: None,
927            routing_key: None,
928            partition: None,
929            qos: None,
930            metadata: HashMap::new(),
931            body: None,
932            client_ip: None,
933        };
934
935        let non_matching_request = ProtocolRequest {
936            protocol: Protocol::Http,
937            pattern: MessagePattern::RequestResponse,
938            operation: "POST".to_string(),
939            path: "/api/users".to_string(),
940            topic: None,
941            routing_key: None,
942            partition: None,
943            qos: None,
944            metadata: HashMap::new(),
945            body: None,
946            client_ip: None,
947        };
948
949        assert!(fixture.matches(&matching_request));
950        assert!(!fixture.matches(&non_matching_request));
951    }
952
953    #[test]
954    fn test_fixture_to_protocol_response() {
955        let fixture = UnifiedFixture {
956            id: "test".to_string(),
957            name: "Test".to_string(),
958            description: "".to_string(),
959            protocol: Protocol::Http,
960            request: FixtureRequest {
961                pattern: None,
962                operation: None,
963                path: None,
964                topic: None,
965                routing_key: None,
966                partition: None,
967                qos: None,
968                headers: HashMap::new(),
969                body_pattern: None,
970                custom_matcher: None,
971            },
972            response: FixtureResponse {
973                status: FixtureStatus::Http(200),
974                headers: {
975                    let mut h = HashMap::new();
976                    h.insert("content-type".to_string(), "application/json".to_string());
977                    h
978                },
979                body: Some(serde_json::json!({"message": "ok"})),
980                content_type: Some("application/json".to_string()),
981                delay_ms: 0,
982                template_vars: HashMap::new(),
983            },
984            metadata: HashMap::new(),
985            enabled: true,
986            priority: 0,
987            tags: vec![],
988        };
989
990        let response = fixture.to_protocol_response().unwrap();
991        assert!(response.status.is_success());
992        assert_eq!(response.content_type, "application/json");
993        assert_eq!(response.metadata.get("content-type"), Some(&"application/json".to_string()));
994    }
995
996    #[test]
997    fn test_fixture_status_serialization() {
998        // Test HTTP status
999        let status = FixtureStatus::Http(404);
1000        let serialized = serde_json::to_string(&status).unwrap();
1001        assert_eq!(serialized, "404");
1002
1003        // Test gRPC status
1004        let status = FixtureStatus::Grpc(5);
1005        let serialized = serde_json::to_string(&status).unwrap();
1006        assert_eq!(serialized, "5");
1007
1008        // Test generic status
1009        let status = FixtureStatus::Generic(true);
1010        let serialized = serde_json::to_string(&status).unwrap();
1011        assert_eq!(serialized, "true");
1012
1013        // Test custom status
1014        let status = FixtureStatus::Custom {
1015            code: 500,
1016            message: "Internal Error".to_string(),
1017        };
1018        let serialized = serde_json::to_string(&status).unwrap();
1019        let expected: serde_json::Value =
1020            serde_json::json!({"code": 500, "message": "Internal Error"});
1021        assert_eq!(serde_json::from_str::<serde_json::Value>(&serialized).unwrap(), expected);
1022    }
1023
1024    #[test]
1025    fn test_fixture_pattern_matching() {
1026        let fixture = UnifiedFixture {
1027            id: "test".to_string(),
1028            name: "Test".to_string(),
1029            description: "".to_string(),
1030            protocol: Protocol::Http,
1031            request: FixtureRequest {
1032                pattern: Some(MessagePattern::RequestResponse),
1033                operation: Some("GET".to_string()),
1034                path: Some("/api/.*".to_string()),
1035                topic: None,
1036                routing_key: None,
1037                partition: None,
1038                qos: None,
1039                headers: HashMap::new(),
1040                body_pattern: None,
1041                custom_matcher: None,
1042            },
1043            response: FixtureResponse {
1044                status: FixtureStatus::Http(200),
1045                headers: HashMap::new(),
1046                body: None,
1047                content_type: None,
1048                delay_ms: 0,
1049                template_vars: HashMap::new(),
1050            },
1051            metadata: HashMap::new(),
1052            enabled: true,
1053            priority: 0,
1054            tags: vec![],
1055        };
1056
1057        // Test matching request
1058        let request = ProtocolRequest {
1059            protocol: Protocol::Http,
1060            pattern: MessagePattern::RequestResponse,
1061            operation: "GET".to_string(),
1062            path: "/api/users".to_string(),
1063            topic: None,
1064            routing_key: None,
1065            partition: None,
1066            qos: None,
1067            metadata: HashMap::new(),
1068            body: None,
1069            client_ip: None,
1070        };
1071        assert!(fixture.matches(&request));
1072
1073        // Test non-matching protocol
1074        let grpc_request = ProtocolRequest {
1075            protocol: Protocol::Grpc,
1076            pattern: MessagePattern::RequestResponse,
1077            operation: "GET".to_string(),
1078            path: "/api/users".to_string(),
1079            topic: None,
1080            routing_key: None,
1081            partition: None,
1082            qos: None,
1083            metadata: HashMap::new(),
1084            body: None,
1085            client_ip: None,
1086        };
1087        assert!(!fixture.matches(&grpc_request));
1088
1089        // Test non-matching operation
1090        let post_request = ProtocolRequest {
1091            protocol: Protocol::Http,
1092            pattern: MessagePattern::RequestResponse,
1093            operation: "POST".to_string(),
1094            path: "/api/users".to_string(),
1095            topic: None,
1096            routing_key: None,
1097            partition: None,
1098            qos: None,
1099            metadata: HashMap::new(),
1100            body: None,
1101            client_ip: None,
1102        };
1103        assert!(!fixture.matches(&post_request));
1104    }
1105
1106    #[test]
1107    fn test_custom_matcher_equality() {
1108        let fixture = UnifiedFixture {
1109            id: "test".to_string(),
1110            name: "Test".to_string(),
1111            description: "".to_string(),
1112            protocol: Protocol::Http,
1113            request: FixtureRequest {
1114                pattern: Some(MessagePattern::RequestResponse),
1115                operation: Some("GET".to_string()),
1116                path: Some("/api/users".to_string()),
1117                topic: None,
1118                routing_key: None,
1119                partition: None,
1120                qos: None,
1121                headers: HashMap::new(),
1122                body_pattern: None,
1123                custom_matcher: Some("operation == \"GET\"".to_string()),
1124            },
1125            response: FixtureResponse {
1126                status: FixtureStatus::Http(200),
1127                headers: HashMap::new(),
1128                body: None,
1129                content_type: None,
1130                delay_ms: 0,
1131                template_vars: HashMap::new(),
1132            },
1133            metadata: HashMap::new(),
1134            enabled: true,
1135            priority: 0,
1136            tags: vec![],
1137        };
1138
1139        // Test matching request
1140        let request = ProtocolRequest {
1141            protocol: Protocol::Http,
1142            pattern: MessagePattern::RequestResponse,
1143            operation: "GET".to_string(),
1144            path: "/api/users".to_string(),
1145            topic: None,
1146            routing_key: None,
1147            partition: None,
1148            qos: None,
1149            metadata: HashMap::new(),
1150            body: None,
1151            client_ip: None,
1152        };
1153        assert!(fixture.matches(&request));
1154
1155        // Test non-matching request
1156        let post_request = ProtocolRequest {
1157            protocol: Protocol::Http,
1158            pattern: MessagePattern::RequestResponse,
1159            operation: "POST".to_string(),
1160            path: "/api/users".to_string(),
1161            topic: None,
1162            routing_key: None,
1163            partition: None,
1164            qos: None,
1165            metadata: HashMap::new(),
1166            body: None,
1167            client_ip: None,
1168        };
1169        assert!(!fixture.matches(&post_request));
1170    }
1171
1172    #[test]
1173    fn test_custom_matcher_regex() {
1174        let fixture = UnifiedFixture {
1175            id: "test".to_string(),
1176            name: "Test".to_string(),
1177            description: "".to_string(),
1178            protocol: Protocol::Http,
1179            request: FixtureRequest {
1180                pattern: Some(MessagePattern::RequestResponse),
1181                operation: Some("GET".to_string()),
1182                path: Some("/api/.*".to_string()),
1183                topic: None,
1184                routing_key: None,
1185                partition: None,
1186                qos: None,
1187                headers: HashMap::new(),
1188                body_pattern: None,
1189                custom_matcher: Some("path =~ \"/api/.*\"".to_string()),
1190            },
1191            response: FixtureResponse {
1192                status: FixtureStatus::Http(200),
1193                headers: HashMap::new(),
1194                body: None,
1195                content_type: None,
1196                delay_ms: 0,
1197                template_vars: HashMap::new(),
1198            },
1199            metadata: HashMap::new(),
1200            enabled: true,
1201            priority: 0,
1202            tags: vec![],
1203        };
1204
1205        // Test matching request
1206        let request = ProtocolRequest {
1207            protocol: Protocol::Http,
1208            pattern: MessagePattern::RequestResponse,
1209            operation: "GET".to_string(),
1210            path: "/api/users".to_string(),
1211            topic: None,
1212            routing_key: None,
1213            partition: None,
1214            qos: None,
1215            metadata: HashMap::new(),
1216            body: None,
1217            client_ip: None,
1218        };
1219        assert!(fixture.matches(&request));
1220
1221        // Test non-matching request
1222        let other_request = ProtocolRequest {
1223            protocol: Protocol::Http,
1224            pattern: MessagePattern::RequestResponse,
1225            operation: "GET".to_string(),
1226            path: "/other/path".to_string(),
1227            topic: None,
1228            routing_key: None,
1229            partition: None,
1230            qos: None,
1231            metadata: HashMap::new(),
1232            body: None,
1233            client_ip: None,
1234        };
1235        assert!(!fixture.matches(&other_request));
1236    }
1237
1238    #[test]
1239    fn test_custom_matcher_contains() {
1240        let fixture = UnifiedFixture {
1241            id: "test".to_string(),
1242            name: "Test".to_string(),
1243            description: "".to_string(),
1244            protocol: Protocol::Http,
1245            request: FixtureRequest {
1246                pattern: Some(MessagePattern::RequestResponse),
1247                operation: Some("POST".to_string()),
1248                path: Some("/api/users".to_string()),
1249                topic: None,
1250                routing_key: None,
1251                partition: None,
1252                qos: None,
1253                headers: HashMap::new(),
1254                body_pattern: None,
1255                custom_matcher: Some("body contains \"test\"".to_string()),
1256            },
1257            response: FixtureResponse {
1258                status: FixtureStatus::Http(200),
1259                headers: HashMap::new(),
1260                body: None,
1261                content_type: None,
1262                delay_ms: 0,
1263                template_vars: HashMap::new(),
1264            },
1265            metadata: HashMap::new(),
1266            enabled: true,
1267            priority: 0,
1268            tags: vec![],
1269        };
1270
1271        // Test matching request
1272        let request = ProtocolRequest {
1273            protocol: Protocol::Http,
1274            pattern: MessagePattern::RequestResponse,
1275            operation: "POST".to_string(),
1276            path: "/api/users".to_string(),
1277            topic: None,
1278            routing_key: None,
1279            partition: None,
1280            qos: None,
1281            metadata: HashMap::new(),
1282            body: Some(b"{\"name\": \"test user\"}".to_vec()),
1283            client_ip: None,
1284        };
1285        assert!(fixture.matches(&request));
1286
1287        // Test non-matching request
1288        let no_match_request = ProtocolRequest {
1289            protocol: Protocol::Http,
1290            pattern: MessagePattern::RequestResponse,
1291            operation: "POST".to_string(),
1292            path: "/api/users".to_string(),
1293            topic: None,
1294            routing_key: None,
1295            partition: None,
1296            qos: None,
1297            metadata: HashMap::new(),
1298            body: Some(b"{\"name\": \"other user\"}".to_vec()),
1299            client_ip: None,
1300        };
1301        assert!(!fixture.matches(&no_match_request));
1302    }
1303
1304    #[test]
1305    fn test_custom_matcher_header() {
1306        let fixture = UnifiedFixture {
1307            id: "test".to_string(),
1308            name: "Test".to_string(),
1309            description: "".to_string(),
1310            protocol: Protocol::Http,
1311            request: FixtureRequest {
1312                pattern: Some(MessagePattern::RequestResponse),
1313                operation: Some("GET".to_string()),
1314                path: Some("/api/data".to_string()),
1315                topic: None,
1316                routing_key: None,
1317                partition: None,
1318                qos: None,
1319                headers: HashMap::new(),
1320                body_pattern: None,
1321                custom_matcher: Some("headers.content-type == \"application/json\"".to_string()),
1322            },
1323            response: FixtureResponse {
1324                status: FixtureStatus::Http(200),
1325                headers: HashMap::new(),
1326                body: None,
1327                content_type: None,
1328                delay_ms: 0,
1329                template_vars: HashMap::new(),
1330            },
1331            metadata: HashMap::new(),
1332            enabled: true,
1333            priority: 0,
1334            tags: vec![],
1335        };
1336
1337        // Test matching request
1338        let mut headers = HashMap::new();
1339        headers.insert("content-type".to_string(), "application/json".to_string());
1340        let request = ProtocolRequest {
1341            protocol: Protocol::Http,
1342            pattern: MessagePattern::RequestResponse,
1343            operation: "GET".to_string(),
1344            path: "/api/data".to_string(),
1345            topic: None,
1346            routing_key: None,
1347            partition: None,
1348            qos: None,
1349            metadata: headers,
1350            body: None,
1351            client_ip: None,
1352        };
1353        assert!(fixture.matches(&request));
1354
1355        // Test non-matching request
1356        let mut wrong_headers = HashMap::new();
1357        wrong_headers.insert("content-type".to_string(), "text/plain".to_string());
1358        let wrong_request = ProtocolRequest {
1359            protocol: Protocol::Http,
1360            pattern: MessagePattern::RequestResponse,
1361            operation: "GET".to_string(),
1362            path: "/api/data".to_string(),
1363            topic: None,
1364            routing_key: None,
1365            partition: None,
1366            qos: None,
1367            metadata: wrong_headers,
1368            body: None,
1369            client_ip: None,
1370        };
1371        assert!(!fixture.matches(&wrong_request));
1372    }
1373}