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}