Skip to main content

playwright_rs/protocol/page/
response.rs

1use crate::server::connection::downcast_parent;
2
3/// Response from navigation operations.
4///
5/// Returned from `page.goto()`, `page.reload()`, `page.go_back()`, and similar
6/// navigation methods. Provides access to the HTTP response status, headers, and body.
7///
8/// See: <https://playwright.dev/docs/api/class-response>
9#[derive(Clone)]
10pub struct Response {
11    url: String,
12    status: u16,
13    status_text: String,
14    ok: bool,
15    headers: std::collections::HashMap<String, String>,
16    /// Reference to the backing channel owner for RPC calls (body, rawHeaders, etc.)
17    /// Stored as the generic trait object so it can be downcast to ResponseObject when needed.
18    response_channel_owner: Option<std::sync::Arc<dyn crate::server::channel_owner::ChannelOwner>>,
19}
20
21impl Response {
22    /// Creates a new Response from protocol data.
23    ///
24    /// This is used internally when constructing a Response from the protocol
25    /// initializer (e.g., after `goto` or `reload`).
26    pub(crate) fn new(
27        url: String,
28        status: u16,
29        status_text: String,
30        headers: std::collections::HashMap<String, String>,
31        response_channel_owner: Option<
32            std::sync::Arc<dyn crate::server::channel_owner::ChannelOwner>,
33        >,
34    ) -> Self {
35        Self {
36            url,
37            status,
38            status_text,
39            ok: (200..300).contains(&status),
40            headers,
41            response_channel_owner,
42        }
43    }
44}
45
46impl Response {
47    /// Returns the URL of the response.
48    ///
49    /// See: <https://playwright.dev/docs/api/class-response#response-url>
50    pub fn url(&self) -> &str {
51        &self.url
52    }
53
54    /// Returns the HTTP status code.
55    ///
56    /// See: <https://playwright.dev/docs/api/class-response#response-status>
57    pub fn status(&self) -> u16 {
58        self.status
59    }
60
61    /// Returns the HTTP status text.
62    ///
63    /// See: <https://playwright.dev/docs/api/class-response#response-status-text>
64    pub fn status_text(&self) -> &str {
65        &self.status_text
66    }
67
68    /// Returns whether the response was successful (status 200-299).
69    ///
70    /// See: <https://playwright.dev/docs/api/class-response#response-ok>
71    pub fn ok(&self) -> bool {
72        self.ok
73    }
74
75    /// Returns the response headers as a HashMap.
76    ///
77    /// Note: these are the headers from the protocol initializer. For the full
78    /// raw headers (including duplicates), use `headers_array()` or `all_headers()`.
79    ///
80    /// See: <https://playwright.dev/docs/api/class-response#response-headers>
81    pub fn headers(&self) -> &std::collections::HashMap<String, String> {
82        &self.headers
83    }
84
85    /// Returns the [`Request`](crate::protocol::Request) that triggered this response.
86    ///
87    /// Navigates the protocol object hierarchy: ResponseObject → parent (Request).
88    ///
89    /// See: <https://playwright.dev/docs/api/class-response#response-request>
90    pub fn request(&self) -> Option<crate::protocol::Request> {
91        let owner = self.response_channel_owner.as_ref()?;
92        downcast_parent::<crate::protocol::Request>(&**owner)
93    }
94
95    /// Returns the [`Frame`](crate::protocol::Frame) that initiated the request for this response.
96    ///
97    /// Navigates the protocol object hierarchy: ResponseObject → Request → Frame.
98    ///
99    /// See: <https://playwright.dev/docs/api/class-response#response-frame>
100    pub fn frame(&self) -> Option<crate::protocol::Frame> {
101        let request = self.request()?;
102        request.frame()
103    }
104
105    /// Returns the backing `ResponseObject`, or an error if unavailable.
106    pub(crate) fn response_object(&self) -> crate::error::Result<crate::protocol::ResponseObject> {
107        let arc = self.response_channel_owner.as_ref().ok_or_else(|| {
108            crate::error::Error::ProtocolError(
109                "Response has no backing protocol object".to_string(),
110            )
111        })?;
112        arc.as_any()
113            .downcast_ref::<crate::protocol::ResponseObject>()
114            .cloned()
115            .ok_or_else(|| crate::error::Error::TypeMismatch {
116                guid: arc.guid().to_string(),
117                expected: "ResponseObject".to_string(),
118                actual: arc.type_name().to_string(),
119            })
120    }
121
122    /// Returns TLS/SSL security details for HTTPS connections, or `None` for HTTP.
123    ///
124    /// See: <https://playwright.dev/docs/api/class-response#response-security-details>
125    #[tracing::instrument(level = "debug", skip_all, fields(url = %self.url()))]
126    pub async fn security_details(
127        &self,
128    ) -> crate::error::Result<Option<crate::protocol::response::SecurityDetails>> {
129        self.response_object()?.security_details().await
130    }
131
132    /// Returns the server's IP address and port, or `None`.
133    ///
134    /// See: <https://playwright.dev/docs/api/class-response#response-server-addr>
135    #[tracing::instrument(level = "debug", skip_all, fields(url = %self.url()))]
136    pub async fn server_addr(
137        &self,
138    ) -> crate::error::Result<Option<crate::protocol::response::RemoteAddr>> {
139        self.response_object()?.server_addr().await
140    }
141
142    /// Waits for this response to finish loading.
143    ///
144    /// For responses obtained from navigation methods (`goto`, `reload`), the response
145    /// is already finished when returned. For responses from `on_response` handlers,
146    /// the body may still be loading.
147    ///
148    /// See: <https://playwright.dev/docs/api/class-response#response-finished>
149    #[tracing::instrument(level = "debug", skip_all, fields(url = %self.url()))]
150    pub async fn finished(&self) -> crate::error::Result<()> {
151        // The Playwright protocol dispatches `requestFinished` as a separate event
152        // rather than exposing a `finished` RPC method on Response.
153        // For responses from goto/reload, the response is already complete.
154        // TODO: For on_response handlers, implement proper waiting via requestFinished event.
155        Ok(())
156    }
157
158    /// Returns the HTTP version used by this response (e.g. `"HTTP/1.1"` or `"HTTP/2.0"`).
159    ///
160    /// Makes an RPC call to the Playwright server.
161    ///
162    /// # Errors
163    ///
164    /// Returns an error if:
165    /// - No backing protocol object is available (edge case)
166    /// - The RPC call to the server fails
167    ///
168    /// See: <https://playwright.dev/docs/api/class-response#response-http-version>
169    #[tracing::instrument(level = "debug", skip_all, fields(url = %self.url(), version = tracing::field::Empty))]
170    pub async fn http_version(&self) -> crate::error::Result<String> {
171        let v = self.response_object()?.http_version().await?;
172        tracing::Span::current().record("version", &v);
173        Ok(v)
174    }
175
176    /// Returns the response body as raw bytes.
177    ///
178    /// Makes an RPC call to the Playwright server to fetch the response body.
179    ///
180    /// # Errors
181    ///
182    /// Returns an error if:
183    /// - No backing protocol object is available (edge case)
184    /// - The RPC call to the server fails
185    /// - The base64 response cannot be decoded
186    ///
187    /// See: <https://playwright.dev/docs/api/class-response#response-body>
188    #[tracing::instrument(level = "debug", skip_all, fields(url = %self.url(), bytes_len = tracing::field::Empty))]
189    pub async fn body(&self) -> crate::error::Result<Vec<u8>> {
190        let bytes = self.response_object()?.body().await?;
191        tracing::Span::current().record("bytes_len", bytes.len());
192        Ok(bytes)
193    }
194
195    /// Returns the response body as a UTF-8 string.
196    ///
197    /// Calls `body()` then converts bytes to a UTF-8 string.
198    ///
199    /// # Errors
200    ///
201    /// Returns an error if:
202    /// - `body()` fails
203    /// - The body is not valid UTF-8
204    ///
205    /// See: <https://playwright.dev/docs/api/class-response#response-text>
206    #[tracing::instrument(level = "debug", skip_all, fields(url = %self.url()))]
207    pub async fn text(&self) -> crate::error::Result<String> {
208        let bytes = self.body().await?;
209        String::from_utf8(bytes).map_err(|e| {
210            crate::error::Error::ProtocolError(format!("Response body is not valid UTF-8: {}", e))
211        })
212    }
213
214    /// Parses the response body as JSON and deserializes it into type `T`.
215    ///
216    /// Calls `text()` then uses `serde_json` to deserialize the body.
217    ///
218    /// # Errors
219    ///
220    /// Returns an error if:
221    /// - `text()` fails
222    /// - The body is not valid JSON or doesn't match the expected type
223    ///
224    /// See: <https://playwright.dev/docs/api/class-response#response-json>
225    #[tracing::instrument(level = "debug", skip_all, fields(url = %self.url()))]
226    pub async fn json<T: serde::de::DeserializeOwned>(&self) -> crate::error::Result<T> {
227        let text = self.text().await?;
228        serde_json::from_str(&text).map_err(|e| {
229            crate::error::Error::ProtocolError(format!("Failed to parse response JSON: {}", e))
230        })
231    }
232
233    /// Returns all response headers as name-value pairs, preserving duplicates.
234    ///
235    /// Makes an RPC call for `"rawHeaders"` which returns the complete header list.
236    ///
237    /// # Errors
238    ///
239    /// Returns an error if:
240    /// - No backing protocol object is available (edge case)
241    /// - The RPC call to the server fails
242    ///
243    /// See: <https://playwright.dev/docs/api/class-response#response-headers-array>
244    #[tracing::instrument(level = "debug", skip_all, fields(url = %self.url()))]
245    pub async fn headers_array(
246        &self,
247    ) -> crate::error::Result<Vec<crate::protocol::response::HeaderEntry>> {
248        self.response_object()?.raw_headers().await
249    }
250
251    /// Returns all response headers merged into a HashMap with lowercase keys.
252    ///
253    /// When multiple headers have the same name, their values are joined with `, `.
254    /// This matches the behavior of `response.allHeaders()` in other Playwright bindings.
255    ///
256    /// # Errors
257    ///
258    /// Returns an error if:
259    /// - No backing protocol object is available (edge case)
260    /// - The RPC call to the server fails
261    ///
262    /// See: <https://playwright.dev/docs/api/class-response#response-all-headers>
263    #[tracing::instrument(level = "debug", skip_all, fields(url = %self.url()))]
264    pub async fn all_headers(
265        &self,
266    ) -> crate::error::Result<std::collections::HashMap<String, String>> {
267        Ok(crate::protocol::route_params::merge_headers(
268            self.headers_array()
269                .await?
270                .into_iter()
271                .map(|entry| (entry.name, entry.value)),
272            Some(", "),
273        ))
274    }
275
276    /// Returns the value for a single response header, or `None` if not present.
277    ///
278    /// The lookup is case-insensitive.
279    ///
280    /// # Errors
281    ///
282    /// Returns an error if:
283    /// - No backing protocol object is available (edge case)
284    /// - The RPC call to the server fails
285    ///
286    /// See: <https://playwright.dev/docs/api/class-response#response-header-value>
287    /// Returns the value for a single response header, or `None` if not present.
288    ///
289    /// The lookup is case-insensitive. When multiple headers share the same name,
290    /// their values are joined with `, ` (matching Playwright's behavior).
291    ///
292    /// Uses the raw headers from the server for accurate results.
293    ///
294    /// # Errors
295    ///
296    /// Returns an error if the underlying `headers_array()` RPC call fails.
297    ///
298    /// See: <https://playwright.dev/docs/api/class-response#response-header-value>
299    #[tracing::instrument(level = "debug", skip_all, fields(url = %self.url(), name = %name))]
300    pub async fn header_value(&self, name: &str) -> crate::error::Result<Option<String>> {
301        Ok(self.all_headers().await?.get(&name.to_lowercase()).cloned())
302    }
303}
304
305impl std::fmt::Debug for Response {
306    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
307        f.debug_struct("Response")
308            .field("url", &self.url)
309            .field("status", &self.status)
310            .field("status_text", &self.status_text)
311            .field("ok", &self.ok)
312            .finish_non_exhaustive()
313    }
314}