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    status: u16,
12    body: Vec<u8>,
13    headers: Vec<ResponseHeader>,
14    messages: Vec<ExecutionMessage>,
15}
16
17impl ExecutionResult {
18    pub fn new(
19        status: u16,
20        body: Vec<u8>,
21        headers: Vec<ResponseHeader>,
22        messages: Vec<ExecutionMessage>,
23    ) -> Self {
24        Self {
25            status,
26            body,
27            headers,
28            messages,
29        }
30    }
31
32    pub fn body(&self) -> Vec<u8> {
33        self.body.to_owned()
34    }
35
36    pub fn take_body(&mut self) -> Vec<u8> {
37        std::mem::take(&mut self.body)
38    }
39
40    pub fn body_string(&self) -> String {
41        if self.body.is_empty() {
42            return String::default();
43        }
44
45        String::from_utf8_lossy(&self.body).into_owned()
46    }
47
48    pub fn body_str(&self) -> Result<&str, std::str::Utf8Error> {
49        std::str::from_utf8(&self.body)
50    }
51
52    pub fn status_code(&self) -> u16 {
53        self.status
54    }
55
56    pub fn has_errors(&self) -> bool {
57        self.messages
58            .iter()
59            .any(|m| m.is_error())
60    }
61
62    pub fn has_message_level(&self, level: SyslogLevel) -> bool {
63        self.messages
64            .iter()
65            .any(|m| m.level == level)
66    }
67
68    pub fn errors(&self) -> impl Iterator<Item = &ExecutionMessage> {
69        self.messages
70            .iter()
71            .filter(|m| m.is_error())
72    }
73
74    pub fn all_messages(&self) -> impl Iterator<Item = &ExecutionMessage> {
75        self.messages.iter()
76    }
77
78    pub fn all_headers(&self) -> impl Iterator<Item = &ResponseHeader> {
79        self.headers.iter()
80    }
81
82    /// Returns the first header value for a given header name, if any.
83    pub fn header_val(&self, name: &str) -> Option<&str> {
84        self.headers
85            .iter()
86            .find(|h| {
87                h.name()
88                    .eq_ignore_ascii_case(name)
89            })
90            .map(|h| h.value())
91    }
92
93    /// Returns all headers for a given header name
94    /// e.g. "cookie" -> vec!["SESSID=abc123", "lang=en", "..."]
95    pub fn header_vals(&self, name: &str) -> Vec<&str> {
96        self.headers
97            .iter()
98            .filter(|h| {
99                h.name()
100                    .eq_ignore_ascii_case(name)
101            })
102            .map(|h| h.value())
103            .collect()
104    }
105
106    pub fn is_success(&self) -> bool {
107        (200..300).contains(&self.status)
108    }
109
110    pub fn is_redirect(&self) -> bool {
111        (300..400).contains(&self.status)
112    }
113
114    pub fn is_client_error(&self) -> bool {
115        (400..500).contains(&self.status)
116    }
117
118    pub fn is_server_error(&self) -> bool {
119        (500..600).contains(&self.status)
120    }
121}
122
123impl Default for ExecutionResult {
124    fn default() -> Self {
125        Self {
126            status: 200,
127            body: Vec::new(),
128            headers: Vec::new(),
129            messages: Vec::new(),
130        }
131    }
132}
133
134#[cfg(feature = "http")]
135impl ExecutionResult {
136    pub fn into_http_response(self) -> http::Response<Vec<u8>> {
137        let mut builder = http::Response::builder().status(self.status);
138
139        for h in &self.headers {
140            builder = builder.header(h.name(), h.value());
141        }
142
143        builder
144            .body(self.body)
145            .unwrap_or_else(|_| http::Response::new(Vec::new()))
146    }
147}
148
149#[cfg(feature = "http")]
150impl From<ExecutionResult> for http::Response<Vec<u8>> {
151    fn from(res: ExecutionResult) -> Self {
152        res.into_http_response()
153    }
154}