sentinel_modsec/engine/
phase.rs

1//! Request processing phases.
2
3/// ModSecurity processing phases.
4#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
5#[repr(u8)]
6pub enum Phase {
7    /// Phase 1: Request headers
8    RequestHeaders = 1,
9    /// Phase 2: Request body
10    RequestBody = 2,
11    /// Phase 3: Response headers
12    ResponseHeaders = 3,
13    /// Phase 4: Response body
14    ResponseBody = 4,
15    /// Phase 5: Logging
16    Logging = 5,
17}
18
19impl Phase {
20    /// Get the phase number.
21    pub fn number(&self) -> u8 {
22        *self as u8
23    }
24
25    /// Get phase name.
26    pub fn name(&self) -> &'static str {
27        match self {
28            Phase::RequestHeaders => "REQUEST_HEADERS",
29            Phase::RequestBody => "REQUEST_BODY",
30            Phase::ResponseHeaders => "RESPONSE_HEADERS",
31            Phase::ResponseBody => "RESPONSE_BODY",
32            Phase::Logging => "LOGGING",
33        }
34    }
35
36    /// Create from phase number.
37    pub fn from_number(n: u8) -> Option<Self> {
38        match n {
39            1 => Some(Phase::RequestHeaders),
40            2 => Some(Phase::RequestBody),
41            3 => Some(Phase::ResponseHeaders),
42            4 => Some(Phase::ResponseBody),
43            5 => Some(Phase::Logging),
44            _ => None,
45        }
46    }
47
48    /// Get all phases in order.
49    pub fn all() -> &'static [Phase] {
50        &[
51            Phase::RequestHeaders,
52            Phase::RequestBody,
53            Phase::ResponseHeaders,
54            Phase::ResponseBody,
55            Phase::Logging,
56        ]
57    }
58
59    /// Check if this is a request phase.
60    pub fn is_request_phase(&self) -> bool {
61        matches!(self, Phase::RequestHeaders | Phase::RequestBody)
62    }
63
64    /// Check if this is a response phase.
65    pub fn is_response_phase(&self) -> bool {
66        matches!(self, Phase::ResponseHeaders | Phase::ResponseBody)
67    }
68}
69
70impl Default for Phase {
71    fn default() -> Self {
72        Phase::RequestHeaders
73    }
74}
75
76impl TryFrom<u8> for Phase {
77    type Error = ();
78
79    fn try_from(value: u8) -> Result<Self, Self::Error> {
80        Phase::from_number(value).ok_or(())
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[test]
89    fn test_phase_number() {
90        assert_eq!(Phase::RequestHeaders.number(), 1);
91        assert_eq!(Phase::RequestBody.number(), 2);
92        assert_eq!(Phase::ResponseHeaders.number(), 3);
93        assert_eq!(Phase::ResponseBody.number(), 4);
94        assert_eq!(Phase::Logging.number(), 5);
95    }
96
97    #[test]
98    fn test_phase_from_number() {
99        assert_eq!(Phase::from_number(1), Some(Phase::RequestHeaders));
100        assert_eq!(Phase::from_number(2), Some(Phase::RequestBody));
101        assert_eq!(Phase::from_number(6), None);
102    }
103
104    #[test]
105    fn test_is_request_phase() {
106        assert!(Phase::RequestHeaders.is_request_phase());
107        assert!(Phase::RequestBody.is_request_phase());
108        assert!(!Phase::ResponseHeaders.is_request_phase());
109    }
110}