1use crate::error::Result;
12use crate::protocol::route::FetchOptions;
13use crate::protocol::route::FetchResponse;
14use crate::server::channel::Channel;
15use crate::server::channel_owner::{
16 ChannelOwner, ChannelOwnerImpl, DisposeReason, ParentOrConnection,
17};
18use crate::server::connection::ConnectionLike;
19use serde::de::DeserializeOwned;
20use serde_json::{Value, json};
21use std::any::Any;
22use std::collections::HashMap;
23use std::sync::Arc;
24
25#[derive(Clone)]
35pub struct APIRequestContext {
36 base: ChannelOwnerImpl,
37}
38
39impl APIRequestContext {
40 pub fn new(
41 parent: ParentOrConnection,
42 type_name: String,
43 guid: Arc<str>,
44 initializer: Value,
45 ) -> Result<Self> {
46 Ok(Self {
47 base: ChannelOwnerImpl::new(parent, type_name, guid, initializer),
48 })
49 }
50
51 pub async fn get(
55 &self,
56 url: &str,
57 options: impl Into<Option<FetchOptions>>,
58 ) -> Result<APIResponse> {
59 let options = options.into();
60 let mut opts = options.unwrap_or_default();
61 opts.method = Some("GET".to_string());
62 self.fetch(url, Some(opts)).await
63 }
64
65 pub async fn post(
69 &self,
70 url: &str,
71 options: impl Into<Option<FetchOptions>>,
72 ) -> Result<APIResponse> {
73 let options = options.into();
74 let mut opts = options.unwrap_or_default();
75 opts.method = Some("POST".to_string());
76 self.fetch(url, Some(opts)).await
77 }
78
79 pub async fn put(
83 &self,
84 url: &str,
85 options: impl Into<Option<FetchOptions>>,
86 ) -> Result<APIResponse> {
87 let options = options.into();
88 let mut opts = options.unwrap_or_default();
89 opts.method = Some("PUT".to_string());
90 self.fetch(url, Some(opts)).await
91 }
92
93 pub async fn delete(
97 &self,
98 url: &str,
99 options: impl Into<Option<FetchOptions>>,
100 ) -> Result<APIResponse> {
101 let options = options.into();
102 let mut opts = options.unwrap_or_default();
103 opts.method = Some("DELETE".to_string());
104 self.fetch(url, Some(opts)).await
105 }
106
107 pub async fn patch(
111 &self,
112 url: &str,
113 options: impl Into<Option<FetchOptions>>,
114 ) -> Result<APIResponse> {
115 let options = options.into();
116 let mut opts = options.unwrap_or_default();
117 opts.method = Some("PATCH".to_string());
118 self.fetch(url, Some(opts)).await
119 }
120
121 pub async fn head(
125 &self,
126 url: &str,
127 options: impl Into<Option<FetchOptions>>,
128 ) -> Result<APIResponse> {
129 let options = options.into();
130 let mut opts = options.unwrap_or_default();
131 opts.method = Some("HEAD".to_string());
132 self.fetch(url, Some(opts)).await
133 }
134
135 pub async fn fetch(
142 &self,
143 url: &str,
144 options: impl Into<Option<FetchOptions>>,
145 ) -> Result<APIResponse> {
146 let options = options.into();
147 let opts = options.unwrap_or_default();
148
149 let mut params = json!({
150 "url": url,
151 "timeout": opts.timeout.unwrap_or(crate::DEFAULT_TIMEOUT_MS)
152 });
153
154 if let Some(method) = opts.method {
155 params["method"] = json!(method);
156 }
157 if let Some(headers) = opts.headers {
158 params["headers"] = Value::Array(crate::protocol::route_params::header_array(headers));
159 }
160 if let Some(encoded) =
161 crate::protocol::route_params::post_data_param(opts.post_data, opts.post_data_bytes)
162 {
163 params["postData"] = Value::String(encoded);
164 }
165 if let Some(max_redirects) = opts.max_redirects {
166 params["maxRedirects"] = json!(max_redirects);
167 }
168 if let Some(max_retries) = opts.max_retries {
169 params["maxRetries"] = json!(max_retries);
170 }
171
172 #[derive(serde::Deserialize)]
173 struct FetchResult {
174 response: ApiResponseData,
175 }
176
177 #[derive(serde::Deserialize)]
178 #[serde(rename_all = "camelCase")]
179 struct ApiResponseData {
180 fetch_uid: String,
181 url: String,
182 status: u16,
183 status_text: String,
184 headers: Vec<HeaderEntry>,
185 #[serde(default)]
186 security_details: Option<crate::protocol::response::SecurityDetails>,
187 #[serde(default)]
188 server_addr: Option<crate::protocol::response::RemoteAddr>,
189 #[serde(default)]
190 timing: Option<serde_json::Value>,
191 #[serde(default)]
192 response_end_timing: Option<f64>,
193 }
194
195 #[derive(serde::Deserialize)]
196 struct HeaderEntry {
197 name: String,
198 value: String,
199 }
200
201 let result: FetchResult = self.base.channel().send("fetch", params).await?;
202
203 let headers: HashMap<String, String> = result
204 .response
205 .headers
206 .into_iter()
207 .map(|h| (h.name, h.value))
208 .collect();
209
210 Ok(APIResponse {
211 context: self.clone(),
212 url: result.response.url,
213 status: result.response.status,
214 status_text: result.response.status_text,
215 headers,
216 fetch_uid: result.response.fetch_uid,
217 security_details: result.response.security_details,
218 server_addr: result.response.server_addr,
219 timing: result.response.timing.clone().and_then(|mut t| {
220 crate::protocol::ResourceTiming::merge_response_end(
221 &mut t,
222 result.response.response_end_timing,
223 );
224 crate::protocol::ResourceTiming::from_protocol(&t)
225 }),
226 response_end_timing: result.response.response_end_timing,
227 })
228 }
229
230 pub async fn dispose(&self) -> Result<()> {
236 self.base
237 .channel()
238 .send_no_result("dispose", json!({}))
239 .await
240 }
241
242 pub(crate) async fn inner_fetch(
254 &self,
255 url: &str,
256 options: Option<InnerFetchOptions>,
257 ) -> Result<FetchResponse> {
258 let opts = options.unwrap_or_default();
259
260 let mut params = json!({
261 "url": url,
262 "timeout": opts.timeout.unwrap_or(crate::DEFAULT_TIMEOUT_MS)
263 });
264
265 if let Some(method) = opts.method {
266 params["method"] = json!(method);
267 }
268 if let Some(headers) = opts.headers {
269 params["headers"] = Value::Array(crate::protocol::route_params::header_array(headers));
270 }
271 if let Some(encoded) =
272 crate::protocol::route_params::post_data_param(opts.post_data, opts.post_data_bytes)
273 {
274 params["postData"] = Value::String(encoded);
275 }
276 if let Some(max_redirects) = opts.max_redirects {
277 params["maxRedirects"] = json!(max_redirects);
278 }
279 if let Some(max_retries) = opts.max_retries {
280 params["maxRetries"] = json!(max_retries);
281 }
282
283 #[derive(serde::Deserialize)]
285 struct FetchResult {
286 response: ApiResponseData,
287 }
288
289 #[derive(serde::Deserialize)]
290 #[serde(rename_all = "camelCase")]
291 struct ApiResponseData {
292 fetch_uid: String,
293 #[allow(dead_code)]
294 url: String,
295 status: u16,
296 status_text: String,
297 headers: Vec<HeaderEntry>,
298 }
299
300 #[derive(serde::Deserialize)]
301 struct HeaderEntry {
302 name: String,
303 value: String,
304 }
305
306 let result: FetchResult = self.base.channel().send("fetch", params).await?;
307
308 let body = self.fetch_response_body(&result.response.fetch_uid).await?;
310
311 let _ = self.dispose_api_response(&result.response.fetch_uid).await;
313
314 Ok(FetchResponse {
315 status: result.response.status,
316 status_text: result.response.status_text,
317 headers: result
318 .response
319 .headers
320 .into_iter()
321 .map(|h| (h.name, h.value))
322 .collect(),
323 body,
324 })
325 }
326
327 async fn fetch_response_body(&self, fetch_uid: &str) -> Result<Vec<u8>> {
329 #[derive(serde::Deserialize)]
330 struct BodyResult {
331 #[serde(default)]
332 binary: Option<String>,
333 }
334
335 let result: BodyResult = self
336 .base
337 .channel()
338 .send("fetchResponseBody", json!({ "fetchUid": fetch_uid }))
339 .await?;
340
341 match result.binary {
342 Some(encoded) if !encoded.is_empty() => {
343 use base64::Engine;
344 base64::engine::general_purpose::STANDARD
345 .decode(&encoded)
346 .map_err(|e| {
347 crate::error::Error::ProtocolError(format!(
348 "Failed to decode response body: {}",
349 e
350 ))
351 })
352 }
353 _ => Ok(vec![]),
354 }
355 }
356
357 async fn dispose_api_response(&self, fetch_uid: &str) -> Result<()> {
359 self.base
360 .channel()
361 .send_no_result("disposeAPIResponse", json!({ "fetchUid": fetch_uid }))
362 .await
363 }
364}
365
366#[derive(Clone)]
373pub struct APIResponse {
374 context: APIRequestContext,
375 url: String,
376 status: u16,
377 status_text: String,
378 headers: HashMap<String, String>,
379 fetch_uid: String,
380 security_details: Option<crate::protocol::response::SecurityDetails>,
381 server_addr: Option<crate::protocol::response::RemoteAddr>,
382 timing: Option<crate::protocol::ResourceTiming>,
383 response_end_timing: Option<f64>,
384}
385
386impl APIResponse {
387 pub fn url(&self) -> &str {
389 &self.url
390 }
391
392 pub fn status(&self) -> u16 {
394 self.status
395 }
396
397 pub fn status_text(&self) -> &str {
399 &self.status_text
400 }
401
402 pub fn ok(&self) -> bool {
404 (200..300).contains(&self.status)
405 }
406
407 pub fn headers(&self) -> &HashMap<String, String> {
409 &self.headers
410 }
411
412 pub fn timing(&self) -> Option<&crate::protocol::ResourceTiming> {
426 self.timing.as_ref()
427 }
428
429 pub fn response_end_timing(&self) -> Option<f64> {
432 self.response_end_timing
433 }
434
435 pub fn security_details(&self) -> Option<&crate::protocol::response::SecurityDetails> {
440 self.security_details.as_ref()
441 }
442
443 pub fn server_addr(&self) -> Option<&crate::protocol::response::RemoteAddr> {
448 self.server_addr.as_ref()
449 }
450
451 pub async fn body(&self) -> Result<Vec<u8>> {
455 self.context.fetch_response_body(&self.fetch_uid).await
456 }
457
458 pub async fn text(&self) -> Result<String> {
462 let bytes = self.body().await?;
463 String::from_utf8(bytes).map_err(|e| {
464 crate::error::Error::ProtocolError(format!("Response body is not valid UTF-8: {}", e))
465 })
466 }
467
468 pub async fn json<T: DeserializeOwned>(&self) -> Result<T> {
472 let bytes = self.body().await?;
473 serde_json::from_slice(&bytes).map_err(|e| {
474 crate::error::Error::ProtocolError(format!("Failed to parse response JSON: {}", e))
475 })
476 }
477
478 pub async fn dispose(&self) -> Result<()> {
482 self.context.dispose_api_response(&self.fetch_uid).await
483 }
484}
485
486impl std::fmt::Debug for APIResponse {
487 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
488 f.debug_struct("APIResponse")
489 .field("url", &self.url)
490 .field("status", &self.status)
491 .field("status_text", &self.status_text)
492 .finish()
493 }
494}
495
496#[derive(Debug, Clone, Default)]
500#[non_exhaustive]
501pub struct APIRequestContextOptions {
502 pub base_url: Option<String>,
504 pub extra_http_headers: Option<HashMap<String, String>>,
506 pub ignore_https_errors: Option<bool>,
508 pub user_agent: Option<String>,
510 pub timeout: Option<f64>,
512 pub http_credentials: Option<Vec<crate::protocol::HttpCredentials>>,
517}
518
519impl APIRequestContextOptions {
520 pub fn base_url(mut self, base_url: impl Into<String>) -> Self {
522 self.base_url = Some(base_url.into());
523 self
524 }
525 pub fn extra_http_headers(mut self, headers: HashMap<String, String>) -> Self {
527 self.extra_http_headers = Some(headers);
528 self
529 }
530 pub fn ignore_https_errors(mut self, ignore: bool) -> Self {
532 self.ignore_https_errors = Some(ignore);
533 self
534 }
535 pub fn http_credentials(mut self, credentials: Vec<crate::protocol::HttpCredentials>) -> Self {
537 self.http_credentials = Some(credentials);
538 self
539 }
540 pub fn user_agent(mut self, user_agent: impl Into<String>) -> Self {
542 self.user_agent = Some(user_agent.into());
543 self
544 }
545 pub fn timeout(mut self, timeout: f64) -> Self {
547 self.timeout = Some(timeout);
548 self
549 }
550}
551
552pub struct APIRequest {
583 channel: crate::server::channel::Channel,
584 connection: Arc<dyn ConnectionLike>,
585}
586
587impl APIRequest {
588 pub(crate) fn new(
589 channel: crate::server::channel::Channel,
590 connection: Arc<dyn ConnectionLike>,
591 ) -> Self {
592 Self {
593 channel,
594 connection,
595 }
596 }
597
598 pub async fn new_context(
606 &self,
607 options: impl Into<Option<APIRequestContextOptions>>,
608 ) -> Result<APIRequestContext> {
609 use crate::server::connection::ConnectionExt;
610
611 let options = options.into();
612 let mut params = json!({});
613
614 if let Some(opts) = options {
615 if let Some(base_url) = opts.base_url {
616 params["baseURL"] = json!(base_url);
617 }
618 if let Some(headers) = opts.extra_http_headers {
619 params["extraHTTPHeaders"] =
620 Value::Array(crate::protocol::route_params::header_array(headers));
621 }
622 if let Some(ignore) = opts.ignore_https_errors {
623 params["ignoreHTTPSErrors"] = json!(ignore);
624 }
625 if let Some(ua) = opts.user_agent {
626 params["userAgent"] = json!(ua);
627 }
628 if let Some(credentials) = opts.http_credentials {
629 params["httpCredentials"] = json!(credentials);
630 }
631 if let Some(timeout) = opts.timeout {
632 params["timeout"] = json!(timeout);
633 }
634 }
635
636 #[derive(serde::Deserialize)]
637 struct NewRequestResult {
638 request: GuidRef,
639 }
640
641 #[derive(serde::Deserialize)]
642 struct GuidRef {
643 guid: String,
644 }
645
646 let result: NewRequestResult = self.channel.send("newRequest", params).await?;
647
648 self.connection
649 .get_typed::<APIRequestContext>(&result.request.guid)
650 .await
651 }
652}
653
654impl std::fmt::Debug for APIRequest {
655 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
656 f.debug_struct("APIRequest").finish()
657 }
658}
659
660#[derive(Debug, Clone, Default)]
662pub(crate) struct InnerFetchOptions {
663 pub method: Option<String>,
664 pub headers: Option<std::collections::HashMap<String, String>>,
665 pub post_data: Option<String>,
666 pub post_data_bytes: Option<Vec<u8>>,
667 pub max_redirects: Option<u32>,
668 pub max_retries: Option<u32>,
669 pub timeout: Option<f64>,
670}
671
672impl ChannelOwner for APIRequestContext {
673 fn guid(&self) -> &str {
674 self.base.guid()
675 }
676
677 fn type_name(&self) -> &str {
678 self.base.type_name()
679 }
680
681 fn parent(&self) -> Option<Arc<dyn ChannelOwner>> {
682 self.base.parent()
683 }
684
685 fn connection(&self) -> Arc<dyn ConnectionLike> {
686 self.base.connection()
687 }
688
689 fn initializer(&self) -> &Value {
690 self.base.initializer()
691 }
692
693 fn channel(&self) -> &Channel {
694 self.base.channel()
695 }
696
697 fn dispose(&self, reason: DisposeReason) {
698 self.base.dispose(reason)
699 }
700
701 fn adopt(&self, child: Arc<dyn ChannelOwner>) {
702 self.base.adopt(child)
703 }
704
705 fn add_child(&self, guid: Arc<str>, child: Arc<dyn ChannelOwner>) {
706 self.base.add_child(guid, child)
707 }
708
709 fn remove_child(&self, guid: &str) {
710 self.base.remove_child(guid)
711 }
712
713 fn on_event(&self, method: &str, params: Value) {
714 self.base.on_event(method, params)
715 }
716
717 fn was_collected(&self) -> bool {
718 self.base.was_collected()
719 }
720
721 fn as_any(&self) -> &dyn Any {
722 self
723 }
724}
725
726impl std::fmt::Debug for APIRequestContext {
727 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
728 f.debug_struct("APIRequestContext")
729 .field("guid", &self.guid())
730 .finish()
731 }
732}