Skip to main content

playwright_rs/protocol/
response.rs

1// Response protocol object
2//
3// Represents an HTTP response from navigation operations.
4// Response objects are created by the server when Frame.goto() or similar navigation
5// methods complete successfully.
6
7use crate::error::Result;
8use crate::server::channel_owner::{ChannelOwner, ChannelOwnerImpl, ParentOrConnection};
9use serde_json::Value;
10use std::any::Any;
11use std::sync::Arc;
12
13/// TLS/SSL security details for an HTTPS response.
14///
15/// All fields are optional — the server provides what's available.
16///
17/// See: <https://playwright.dev/docs/api/class-response#response-security-details>
18#[derive(Debug, Clone)]
19pub struct SecurityDetails {
20    /// Certificate issuer name.
21    pub issuer: Option<String>,
22    /// TLS protocol version (e.g., "TLS 1.3").
23    pub protocol: Option<String>,
24    /// Certificate subject name.
25    pub subject_name: Option<String>,
26    /// Unix timestamp (seconds) when the certificate becomes valid.
27    pub valid_from: Option<f64>,
28    /// Unix timestamp (seconds) when the certificate expires.
29    pub valid_to: Option<f64>,
30}
31
32/// Remote server address (IP and port).
33///
34/// See: <https://playwright.dev/docs/api/class-response#response-server-addr>
35#[derive(Debug, Clone)]
36pub struct RemoteAddr {
37    /// Server IP address.
38    pub ip_address: String,
39    /// Server port.
40    pub port: u16,
41}
42
43/// Resource size information for a request/response pair.
44///
45/// See: <https://playwright.dev/docs/api/class-request#request-sizes>
46#[derive(Debug, Clone)]
47pub struct RequestSizes {
48    /// Size of the request body in bytes. Set to 0 if there was no body.
49    pub request_body_size: i64,
50    /// Total number of bytes from the start of the HTTP request message
51    /// until (and including) the double CRLF before the body.
52    pub request_headers_size: i64,
53    /// Size of the received response body in bytes.
54    pub response_body_size: i64,
55    /// Total number of bytes from the start of the HTTP response message
56    /// until (and including) the double CRLF before the body.
57    pub response_headers_size: i64,
58}
59
60/// A single HTTP header entry with a name and value.
61///
62/// Used by `Response::headers_array()` to return all headers preserving duplicates.
63///
64/// See: <https://playwright.dev/docs/api/class-response#response-headers-array>
65#[derive(Debug, Clone)]
66pub struct HeaderEntry {
67    /// Header name (lowercase)
68    pub name: String,
69    /// Header value
70    pub value: String,
71}
72
73/// Response represents an HTTP response from a navigation operation.
74///
75/// Response objects are not created directly - they are returned from
76/// navigation methods like page.goto() or page.reload().
77///
78/// See: <https://playwright.dev/docs/api/class-response>
79#[derive(Clone)]
80pub struct ResponseObject {
81    base: ChannelOwnerImpl,
82}
83
84impl ResponseObject {
85    /// Creates a new Response from protocol initialization
86    ///
87    /// This is called by the object factory when the server sends a `__create__` message
88    /// for a Response object.
89    pub fn new(
90        parent: Arc<dyn ChannelOwner>,
91        type_name: String,
92        guid: Arc<str>,
93        initializer: Value,
94    ) -> Result<Self> {
95        let base = ChannelOwnerImpl::new(
96            ParentOrConnection::Parent(parent),
97            type_name,
98            guid,
99            initializer,
100        );
101
102        Ok(Self { base })
103    }
104
105    /// Returns the status code of the response (e.g., 200 for a success).
106    ///
107    /// See: <https://playwright.dev/docs/api/class-response#response-status>
108    pub fn status(&self) -> u16 {
109        self.initializer()
110            .get("status")
111            .and_then(|v| v.as_u64())
112            .unwrap_or(0) as u16
113    }
114
115    /// Returns the status text of the response (e.g. usually an "OK" for a success).
116    ///
117    /// See: <https://playwright.dev/docs/api/class-response#response-status-text>
118    pub fn status_text(&self) -> &str {
119        self.initializer()
120            .get("statusText")
121            .and_then(|v| v.as_str())
122            .unwrap_or("")
123    }
124
125    /// Returns the URL of the response.
126    ///
127    /// See: <https://playwright.dev/docs/api/class-response#response-url>
128    pub fn url(&self) -> &str {
129        self.initializer()
130            .get("url")
131            .and_then(|v| v.as_str())
132            .unwrap_or("")
133    }
134
135    /// Returns the response body as bytes.
136    ///
137    /// Sends a `"body"` RPC call to the Playwright server, which returns the body
138    /// as a base64-encoded binary string.
139    ///
140    /// See: <https://playwright.dev/docs/api/class-response#response-body>
141    pub async fn body(&self) -> Result<Vec<u8>> {
142        use serde::Deserialize;
143
144        #[derive(Deserialize)]
145        struct BodyResponse {
146            binary: String,
147        }
148
149        let result: BodyResponse = self.channel().send("body", serde_json::json!({})).await?;
150
151        use base64::Engine;
152        let bytes = base64::engine::general_purpose::STANDARD
153            .decode(&result.binary)
154            .map_err(|e| {
155                crate::error::Error::ProtocolError(format!(
156                    "Failed to decode response body from base64: {}",
157                    e
158                ))
159            })?;
160        Ok(bytes)
161    }
162
163    /// Returns TLS/SSL security details for HTTPS connections, or `None` for HTTP.
164    ///
165    /// See: <https://playwright.dev/docs/api/class-response#response-security-details>
166    pub async fn security_details(&self) -> Result<Option<SecurityDetails>> {
167        let result: serde_json::Value = self
168            .channel()
169            .send("securityDetails", serde_json::json!({}))
170            .await?;
171
172        let value = result.get("value");
173        match value {
174            Some(v) if v.is_object() && !v.as_object().unwrap().is_empty() => {
175                Ok(Some(SecurityDetails {
176                    issuer: v.get("issuer").and_then(|v| v.as_str()).map(String::from),
177                    protocol: v.get("protocol").and_then(|v| v.as_str()).map(String::from),
178                    subject_name: v
179                        .get("subjectName")
180                        .and_then(|v| v.as_str())
181                        .map(String::from),
182                    valid_from: v.get("validFrom").and_then(|v| v.as_f64()),
183                    valid_to: v.get("validTo").and_then(|v| v.as_f64()),
184                }))
185            }
186            _ => Ok(None),
187        }
188    }
189
190    /// Returns the server's IP address and port for this response, or `None`.
191    ///
192    /// See: <https://playwright.dev/docs/api/class-response#response-server-addr>
193    pub async fn server_addr(&self) -> Result<Option<RemoteAddr>> {
194        let result: serde_json::Value = self
195            .channel()
196            .send("serverAddr", serde_json::json!({}))
197            .await?;
198
199        let value = result.get("value");
200        match value {
201            Some(v) if !v.is_null() => {
202                let ip_address = v
203                    .get("ipAddress")
204                    .and_then(|v| v.as_str())
205                    .unwrap_or("")
206                    .to_string();
207                let port = v.get("port").and_then(|v| v.as_u64()).unwrap_or(0) as u16;
208                Ok(Some(RemoteAddr { ip_address, port }))
209            }
210            _ => Ok(None),
211        }
212    }
213
214    /// Returns resource size information for this response.
215    ///
216    /// See: <https://playwright.dev/docs/api/class-request#request-sizes>
217    pub async fn sizes(&self) -> Result<RequestSizes> {
218        use serde::Deserialize;
219
220        #[derive(Deserialize)]
221        #[serde(rename_all = "camelCase")]
222        struct SizesRaw {
223            request_body_size: i64,
224            request_headers_size: i64,
225            response_body_size: i64,
226            response_headers_size: i64,
227        }
228
229        #[derive(Deserialize)]
230        struct RpcResult {
231            sizes: SizesRaw,
232        }
233
234        let result: RpcResult = self.channel().send("sizes", serde_json::json!({})).await?;
235
236        Ok(RequestSizes {
237            request_body_size: result.sizes.request_body_size,
238            request_headers_size: result.sizes.request_headers_size,
239            response_body_size: result.sizes.response_body_size,
240            response_headers_size: result.sizes.response_headers_size,
241        })
242    }
243
244    /// Returns the raw response headers as name-value pairs (preserving duplicates).
245    ///
246    /// Sends a `"rawResponseHeaders"` RPC call to the Playwright server.
247    ///
248    /// See: <https://playwright.dev/docs/api/class-response#response-headers-array>
249    pub async fn raw_headers(&self) -> Result<Vec<HeaderEntry>> {
250        use serde::Deserialize;
251
252        #[derive(Deserialize)]
253        struct RawHeadersResponse {
254            headers: Vec<HeaderEntryRaw>,
255        }
256
257        #[derive(Deserialize)]
258        struct HeaderEntryRaw {
259            name: String,
260            value: String,
261        }
262
263        let result: RawHeadersResponse = self
264            .channel()
265            .send("rawResponseHeaders", serde_json::json!({}))
266            .await?;
267
268        Ok(result
269            .headers
270            .into_iter()
271            .map(|h| HeaderEntry {
272                name: h.name,
273                value: h.value,
274            })
275            .collect())
276    }
277}
278
279impl ChannelOwner for ResponseObject {
280    fn guid(&self) -> &str {
281        self.base.guid()
282    }
283
284    fn type_name(&self) -> &str {
285        self.base.type_name()
286    }
287
288    fn parent(&self) -> Option<Arc<dyn ChannelOwner>> {
289        self.base.parent()
290    }
291
292    fn connection(&self) -> Arc<dyn crate::server::connection::ConnectionLike> {
293        self.base.connection()
294    }
295
296    fn initializer(&self) -> &Value {
297        self.base.initializer()
298    }
299
300    fn channel(&self) -> &crate::server::channel::Channel {
301        self.base.channel()
302    }
303
304    fn dispose(&self, reason: crate::server::channel_owner::DisposeReason) {
305        self.base.dispose(reason)
306    }
307
308    fn adopt(&self, child: Arc<dyn ChannelOwner>) {
309        self.base.adopt(child)
310    }
311
312    fn add_child(&self, guid: Arc<str>, child: Arc<dyn ChannelOwner>) {
313        self.base.add_child(guid, child)
314    }
315
316    fn remove_child(&self, guid: &str) {
317        self.base.remove_child(guid)
318    }
319
320    fn on_event(&self, _method: &str, _params: Value) {
321        // Response objects don't have events
322    }
323
324    fn was_collected(&self) -> bool {
325        self.base.was_collected()
326    }
327
328    fn as_any(&self) -> &dyn Any {
329        self
330    }
331}
332
333impl std::fmt::Debug for ResponseObject {
334    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
335        f.debug_struct("ResponseObject")
336            .field("guid", &self.guid())
337            .finish()
338    }
339}