Skip to main content

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