ripht_php_sapi/execution/
result.rs

1use super::header::ResponseHeader;
2use super::message::{ExecutionMessage, SyslogLevel};
3
4/// Result of PHP script execution.
5///
6/// Contains the HTTP status code, response headers, body output,
7/// and any PHP errors/warnings/notices logged during execution.
8#[must_use]
9#[derive(Debug, Clone)]
10pub struct ExecutionResult {
11    pub status: u16,
12    pub body: Vec<u8>,
13    pub headers: Vec<ResponseHeader>,
14    pub messages: Vec<ExecutionMessage>,
15}
16
17impl ExecutionResult {
18    pub fn body_string(&self) -> String {
19        String::from_utf8_lossy(&self.body).into_owned()
20    }
21
22    pub fn body_str(&self) -> Result<&str, std::str::Utf8Error> {
23        std::str::from_utf8(&self.body)
24    }
25
26    pub fn status_code(&self) -> u16 {
27        self.status
28    }
29
30    pub fn has_errors(&self) -> bool {
31        self.messages
32            .iter()
33            .any(|m| m.is_error())
34    }
35
36    pub fn has_warnings(&self) -> bool {
37        self.messages
38            .iter()
39            .any(|m| m.is_error() || m.level == SyslogLevel::Warning)
40    }
41
42    pub fn errors(&self) -> impl Iterator<Item = &ExecutionMessage> {
43        self.messages
44            .iter()
45            .filter(|m| m.is_error())
46    }
47
48    pub fn all_messages(&self) -> impl Iterator<Item = &ExecutionMessage> {
49        self.messages.iter()
50    }
51
52    pub fn header(&self, name: &str) -> Option<&str> {
53        self.headers
54            .iter()
55            .find(|h| {
56                h.name()
57                    .eq_ignore_ascii_case(name)
58            })
59            .map(|h| h.value())
60    }
61
62    pub fn headers_all(&self, name: &str) -> Vec<&str> {
63        self.headers
64            .iter()
65            .filter(|h| {
66                h.name()
67                    .eq_ignore_ascii_case(name)
68            })
69            .map(|h| h.value())
70            .collect()
71    }
72
73    pub fn is_success(&self) -> bool {
74        (200..300).contains(&self.status)
75    }
76
77    pub fn is_redirect(&self) -> bool {
78        (300..400).contains(&self.status)
79    }
80
81    pub fn is_client_error(&self) -> bool {
82        (400..500).contains(&self.status)
83    }
84
85    pub fn is_server_error(&self) -> bool {
86        (500..600).contains(&self.status)
87    }
88}
89
90impl Default for ExecutionResult {
91    fn default() -> Self {
92        Self {
93            status: 200,
94            body: Vec::new(),
95            headers: Vec::new(),
96            messages: Vec::new(),
97        }
98    }
99}
100
101#[cfg(feature = "http")]
102impl ExecutionResult {
103    pub fn into_http_response(self) -> http::Response<Vec<u8>> {
104        let mut builder = http::Response::builder().status(self.status);
105
106        for h in &self.headers {
107            builder = builder.header(h.name(), h.value());
108        }
109
110        builder
111            .body(self.body)
112            .unwrap_or_else(|_| http::Response::new(Vec::new()))
113    }
114}
115
116#[cfg(feature = "http")]
117impl From<ExecutionResult> for http::Response<Vec<u8>> {
118    fn from(res: ExecutionResult) -> Self {
119        res.into_http_response()
120    }
121}