playwright_rs/protocol/
api_request_context.rs1use crate::error::Result;
12use crate::protocol::route::FetchResponse;
13use crate::server::channel::Channel;
14use crate::server::channel_owner::{
15 ChannelOwner, ChannelOwnerImpl, DisposeReason, ParentOrConnection,
16};
17use crate::server::connection::ConnectionLike;
18use serde_json::{Value, json};
19use std::any::Any;
20use std::sync::Arc;
21
22#[derive(Clone)]
32pub struct APIRequestContext {
33 base: ChannelOwnerImpl,
34}
35
36impl APIRequestContext {
37 pub fn new(
38 parent: ParentOrConnection,
39 type_name: String,
40 guid: Arc<str>,
41 initializer: Value,
42 ) -> Result<Self> {
43 Ok(Self {
44 base: ChannelOwnerImpl::new(parent, type_name, guid, initializer),
45 })
46 }
47
48 pub(crate) async fn inner_fetch(
60 &self,
61 url: &str,
62 options: Option<InnerFetchOptions>,
63 ) -> Result<FetchResponse> {
64 let opts = options.unwrap_or_default();
65
66 let mut params = json!({
67 "url": url,
68 "timeout": opts.timeout.unwrap_or(crate::DEFAULT_TIMEOUT_MS)
69 });
70
71 if let Some(method) = opts.method {
72 params["method"] = json!(method);
73 }
74 if let Some(headers) = opts.headers {
75 let headers_array: Vec<Value> = headers
76 .into_iter()
77 .map(|(name, value)| json!({"name": name, "value": value}))
78 .collect();
79 params["headers"] = json!(headers_array);
80 }
81 if let Some(post_data) = opts.post_data {
82 use base64::Engine;
83 let encoded = base64::engine::general_purpose::STANDARD.encode(post_data.as_bytes());
84 params["postData"] = json!(encoded);
85 }
86 if let Some(post_data_bytes) = opts.post_data_bytes {
87 use base64::Engine;
88 let encoded = base64::engine::general_purpose::STANDARD.encode(&post_data_bytes);
89 params["postData"] = json!(encoded);
90 }
91 if let Some(max_redirects) = opts.max_redirects {
92 params["maxRedirects"] = json!(max_redirects);
93 }
94 if let Some(max_retries) = opts.max_retries {
95 params["maxRetries"] = json!(max_retries);
96 }
97
98 #[derive(serde::Deserialize)]
100 struct FetchResult {
101 response: ApiResponseData,
102 }
103
104 #[derive(serde::Deserialize)]
105 #[serde(rename_all = "camelCase")]
106 struct ApiResponseData {
107 fetch_uid: String,
108 #[allow(dead_code)]
109 url: String,
110 status: u16,
111 status_text: String,
112 headers: Vec<HeaderEntry>,
113 }
114
115 #[derive(serde::Deserialize)]
116 struct HeaderEntry {
117 name: String,
118 value: String,
119 }
120
121 let result: FetchResult = self.base.channel().send("fetch", params).await?;
122
123 let body = self.fetch_response_body(&result.response.fetch_uid).await?;
125
126 let _ = self.dispose_api_response(&result.response.fetch_uid).await;
128
129 Ok(FetchResponse {
130 status: result.response.status,
131 status_text: result.response.status_text,
132 headers: result
133 .response
134 .headers
135 .into_iter()
136 .map(|h| (h.name, h.value))
137 .collect(),
138 body,
139 })
140 }
141
142 async fn fetch_response_body(&self, fetch_uid: &str) -> Result<Vec<u8>> {
144 #[derive(serde::Deserialize)]
145 struct BodyResult {
146 #[serde(default)]
147 binary: Option<String>,
148 }
149
150 let result: BodyResult = self
151 .base
152 .channel()
153 .send("fetchResponseBody", json!({ "fetchUid": fetch_uid }))
154 .await?;
155
156 match result.binary {
157 Some(encoded) if !encoded.is_empty() => {
158 use base64::Engine;
159 base64::engine::general_purpose::STANDARD
160 .decode(&encoded)
161 .map_err(|e| {
162 crate::error::Error::ProtocolError(format!(
163 "Failed to decode response body: {}",
164 e
165 ))
166 })
167 }
168 _ => Ok(vec![]),
169 }
170 }
171
172 async fn dispose_api_response(&self, fetch_uid: &str) -> Result<()> {
174 self.base
175 .channel()
176 .send_no_result("disposeAPIResponse", json!({ "fetchUid": fetch_uid }))
177 .await
178 }
179}
180
181#[derive(Debug, Clone, Default)]
183pub(crate) struct InnerFetchOptions {
184 pub method: Option<String>,
185 pub headers: Option<std::collections::HashMap<String, String>>,
186 pub post_data: Option<String>,
187 pub post_data_bytes: Option<Vec<u8>>,
188 pub max_redirects: Option<u32>,
189 pub max_retries: Option<u32>,
190 pub timeout: Option<f64>,
191}
192
193impl ChannelOwner for APIRequestContext {
194 fn guid(&self) -> &str {
195 self.base.guid()
196 }
197
198 fn type_name(&self) -> &str {
199 self.base.type_name()
200 }
201
202 fn parent(&self) -> Option<Arc<dyn ChannelOwner>> {
203 self.base.parent()
204 }
205
206 fn connection(&self) -> Arc<dyn ConnectionLike> {
207 self.base.connection()
208 }
209
210 fn initializer(&self) -> &Value {
211 self.base.initializer()
212 }
213
214 fn channel(&self) -> &Channel {
215 self.base.channel()
216 }
217
218 fn dispose(&self, reason: DisposeReason) {
219 self.base.dispose(reason)
220 }
221
222 fn adopt(&self, child: Arc<dyn ChannelOwner>) {
223 self.base.adopt(child)
224 }
225
226 fn add_child(&self, guid: Arc<str>, child: Arc<dyn ChannelOwner>) {
227 self.base.add_child(guid, child)
228 }
229
230 fn remove_child(&self, guid: &str) {
231 self.base.remove_child(guid)
232 }
233
234 fn on_event(&self, method: &str, params: Value) {
235 self.base.on_event(method, params)
236 }
237
238 fn was_collected(&self) -> bool {
239 self.base.was_collected()
240 }
241
242 fn as_any(&self) -> &dyn Any {
243 self
244 }
245}
246
247impl std::fmt::Debug for APIRequestContext {
248 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
249 f.debug_struct("APIRequestContext")
250 .field("guid", &self.guid())
251 .finish()
252 }
253}