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 let headers_array: Vec<Value> = headers
159 .into_iter()
160 .map(|(name, value)| json!({"name": name, "value": value}))
161 .collect();
162 params["headers"] = json!(headers_array);
163 }
164 if let Some(post_data) = opts.post_data {
165 use base64::Engine;
166 let encoded = base64::engine::general_purpose::STANDARD.encode(post_data.as_bytes());
167 params["postData"] = json!(encoded);
168 } else if let Some(post_data_bytes) = opts.post_data_bytes {
169 use base64::Engine;
170 let encoded = base64::engine::general_purpose::STANDARD.encode(&post_data_bytes);
171 params["postData"] = json!(encoded);
172 }
173 if let Some(max_redirects) = opts.max_redirects {
174 params["maxRedirects"] = json!(max_redirects);
175 }
176 if let Some(max_retries) = opts.max_retries {
177 params["maxRetries"] = json!(max_retries);
178 }
179
180 #[derive(serde::Deserialize)]
181 struct FetchResult {
182 response: ApiResponseData,
183 }
184
185 #[derive(serde::Deserialize)]
186 #[serde(rename_all = "camelCase")]
187 struct ApiResponseData {
188 fetch_uid: String,
189 url: String,
190 status: u16,
191 status_text: String,
192 headers: Vec<HeaderEntry>,
193 #[serde(default)]
194 security_details: Option<crate::protocol::response::SecurityDetails>,
195 #[serde(default)]
196 server_addr: Option<crate::protocol::response::RemoteAddr>,
197 #[serde(default)]
198 timing: Option<serde_json::Value>,
199 #[serde(default)]
200 response_end_timing: Option<f64>,
201 }
202
203 #[derive(serde::Deserialize)]
204 struct HeaderEntry {
205 name: String,
206 value: String,
207 }
208
209 let result: FetchResult = self.base.channel().send("fetch", params).await?;
210
211 let headers: HashMap<String, String> = result
212 .response
213 .headers
214 .into_iter()
215 .map(|h| (h.name, h.value))
216 .collect();
217
218 Ok(APIResponse {
219 context: self.clone(),
220 url: result.response.url,
221 status: result.response.status,
222 status_text: result.response.status_text,
223 headers,
224 fetch_uid: result.response.fetch_uid,
225 security_details: result.response.security_details,
226 server_addr: result.response.server_addr,
227 timing: result.response.timing.clone().and_then(|mut t| {
228 crate::protocol::ResourceTiming::merge_response_end(
229 &mut t,
230 result.response.response_end_timing,
231 );
232 crate::protocol::ResourceTiming::from_protocol(&t)
233 }),
234 response_end_timing: result.response.response_end_timing,
235 })
236 }
237
238 pub async fn dispose(&self) -> Result<()> {
244 self.base
245 .channel()
246 .send_no_result("dispose", json!({}))
247 .await
248 }
249
250 pub(crate) async fn inner_fetch(
262 &self,
263 url: &str,
264 options: Option<InnerFetchOptions>,
265 ) -> Result<FetchResponse> {
266 let opts = options.unwrap_or_default();
267
268 let mut params = json!({
269 "url": url,
270 "timeout": opts.timeout.unwrap_or(crate::DEFAULT_TIMEOUT_MS)
271 });
272
273 if let Some(method) = opts.method {
274 params["method"] = json!(method);
275 }
276 if let Some(headers) = opts.headers {
277 let headers_array: Vec<Value> = headers
278 .into_iter()
279 .map(|(name, value)| json!({"name": name, "value": value}))
280 .collect();
281 params["headers"] = json!(headers_array);
282 }
283 if let Some(post_data) = opts.post_data {
284 use base64::Engine;
285 let encoded = base64::engine::general_purpose::STANDARD.encode(post_data.as_bytes());
286 params["postData"] = json!(encoded);
287 }
288 if let Some(post_data_bytes) = opts.post_data_bytes {
289 use base64::Engine;
290 let encoded = base64::engine::general_purpose::STANDARD.encode(&post_data_bytes);
291 params["postData"] = json!(encoded);
292 }
293 if let Some(max_redirects) = opts.max_redirects {
294 params["maxRedirects"] = json!(max_redirects);
295 }
296 if let Some(max_retries) = opts.max_retries {
297 params["maxRetries"] = json!(max_retries);
298 }
299
300 #[derive(serde::Deserialize)]
302 struct FetchResult {
303 response: ApiResponseData,
304 }
305
306 #[derive(serde::Deserialize)]
307 #[serde(rename_all = "camelCase")]
308 struct ApiResponseData {
309 fetch_uid: String,
310 #[allow(dead_code)]
311 url: String,
312 status: u16,
313 status_text: String,
314 headers: Vec<HeaderEntry>,
315 }
316
317 #[derive(serde::Deserialize)]
318 struct HeaderEntry {
319 name: String,
320 value: String,
321 }
322
323 let result: FetchResult = self.base.channel().send("fetch", params).await?;
324
325 let body = self.fetch_response_body(&result.response.fetch_uid).await?;
327
328 let _ = self.dispose_api_response(&result.response.fetch_uid).await;
330
331 Ok(FetchResponse {
332 status: result.response.status,
333 status_text: result.response.status_text,
334 headers: result
335 .response
336 .headers
337 .into_iter()
338 .map(|h| (h.name, h.value))
339 .collect(),
340 body,
341 })
342 }
343
344 async fn fetch_response_body(&self, fetch_uid: &str) -> Result<Vec<u8>> {
346 #[derive(serde::Deserialize)]
347 struct BodyResult {
348 #[serde(default)]
349 binary: Option<String>,
350 }
351
352 let result: BodyResult = self
353 .base
354 .channel()
355 .send("fetchResponseBody", json!({ "fetchUid": fetch_uid }))
356 .await?;
357
358 match result.binary {
359 Some(encoded) if !encoded.is_empty() => {
360 use base64::Engine;
361 base64::engine::general_purpose::STANDARD
362 .decode(&encoded)
363 .map_err(|e| {
364 crate::error::Error::ProtocolError(format!(
365 "Failed to decode response body: {}",
366 e
367 ))
368 })
369 }
370 _ => Ok(vec![]),
371 }
372 }
373
374 async fn dispose_api_response(&self, fetch_uid: &str) -> Result<()> {
376 self.base
377 .channel()
378 .send_no_result("disposeAPIResponse", json!({ "fetchUid": fetch_uid }))
379 .await
380 }
381}
382
383#[derive(Clone)]
390pub struct APIResponse {
391 context: APIRequestContext,
392 url: String,
393 status: u16,
394 status_text: String,
395 headers: HashMap<String, String>,
396 fetch_uid: String,
397 security_details: Option<crate::protocol::response::SecurityDetails>,
398 server_addr: Option<crate::protocol::response::RemoteAddr>,
399 timing: Option<crate::protocol::ResourceTiming>,
400 response_end_timing: Option<f64>,
401}
402
403impl APIResponse {
404 pub fn url(&self) -> &str {
406 &self.url
407 }
408
409 pub fn status(&self) -> u16 {
411 self.status
412 }
413
414 pub fn status_text(&self) -> &str {
416 &self.status_text
417 }
418
419 pub fn ok(&self) -> bool {
421 (200..300).contains(&self.status)
422 }
423
424 pub fn headers(&self) -> &HashMap<String, String> {
426 &self.headers
427 }
428
429 pub fn timing(&self) -> Option<&crate::protocol::ResourceTiming> {
443 self.timing.as_ref()
444 }
445
446 pub fn response_end_timing(&self) -> Option<f64> {
449 self.response_end_timing
450 }
451
452 pub fn security_details(&self) -> Option<&crate::protocol::response::SecurityDetails> {
457 self.security_details.as_ref()
458 }
459
460 pub fn server_addr(&self) -> Option<&crate::protocol::response::RemoteAddr> {
465 self.server_addr.as_ref()
466 }
467
468 pub async fn body(&self) -> Result<Vec<u8>> {
472 self.context.fetch_response_body(&self.fetch_uid).await
473 }
474
475 pub async fn text(&self) -> Result<String> {
479 let bytes = self.body().await?;
480 String::from_utf8(bytes).map_err(|e| {
481 crate::error::Error::ProtocolError(format!("Response body is not valid UTF-8: {}", e))
482 })
483 }
484
485 pub async fn json<T: DeserializeOwned>(&self) -> Result<T> {
489 let bytes = self.body().await?;
490 serde_json::from_slice(&bytes).map_err(|e| {
491 crate::error::Error::ProtocolError(format!("Failed to parse response JSON: {}", e))
492 })
493 }
494
495 pub async fn dispose(&self) -> Result<()> {
499 self.context.dispose_api_response(&self.fetch_uid).await
500 }
501}
502
503impl std::fmt::Debug for APIResponse {
504 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
505 f.debug_struct("APIResponse")
506 .field("url", &self.url)
507 .field("status", &self.status)
508 .field("status_text", &self.status_text)
509 .finish()
510 }
511}
512
513#[derive(Debug, Clone, Default)]
517#[non_exhaustive]
518pub struct APIRequestContextOptions {
519 pub base_url: Option<String>,
521 pub extra_http_headers: Option<HashMap<String, String>>,
523 pub ignore_https_errors: Option<bool>,
525 pub user_agent: Option<String>,
527 pub timeout: Option<f64>,
529}
530
531impl APIRequestContextOptions {
532 pub fn base_url(mut self, base_url: impl Into<String>) -> Self {
534 self.base_url = Some(base_url.into());
535 self
536 }
537 pub fn extra_http_headers(mut self, headers: HashMap<String, String>) -> Self {
539 self.extra_http_headers = Some(headers);
540 self
541 }
542 pub fn ignore_https_errors(mut self, ignore: bool) -> Self {
544 self.ignore_https_errors = Some(ignore);
545 self
546 }
547 pub fn user_agent(mut self, user_agent: impl Into<String>) -> Self {
549 self.user_agent = Some(user_agent.into());
550 self
551 }
552 pub fn timeout(mut self, timeout: f64) -> Self {
554 self.timeout = Some(timeout);
555 self
556 }
557}
558
559pub struct APIRequest {
590 channel: crate::server::channel::Channel,
591 connection: Arc<dyn ConnectionLike>,
592}
593
594impl APIRequest {
595 pub(crate) fn new(
596 channel: crate::server::channel::Channel,
597 connection: Arc<dyn ConnectionLike>,
598 ) -> Self {
599 Self {
600 channel,
601 connection,
602 }
603 }
604
605 pub async fn new_context(
613 &self,
614 options: impl Into<Option<APIRequestContextOptions>>,
615 ) -> Result<APIRequestContext> {
616 use crate::server::connection::ConnectionExt;
617
618 let options = options.into();
619 let mut params = json!({});
620
621 if let Some(opts) = options {
622 if let Some(base_url) = opts.base_url {
623 params["baseURL"] = json!(base_url);
624 }
625 if let Some(headers) = opts.extra_http_headers {
626 let arr: Vec<Value> = headers
627 .into_iter()
628 .map(|(name, value)| json!({"name": name, "value": value}))
629 .collect();
630 params["extraHTTPHeaders"] = json!(arr);
631 }
632 if let Some(ignore) = opts.ignore_https_errors {
633 params["ignoreHTTPSErrors"] = json!(ignore);
634 }
635 if let Some(ua) = opts.user_agent {
636 params["userAgent"] = json!(ua);
637 }
638 if let Some(timeout) = opts.timeout {
639 params["timeout"] = json!(timeout);
640 }
641 }
642
643 #[derive(serde::Deserialize)]
644 struct NewRequestResult {
645 request: GuidRef,
646 }
647
648 #[derive(serde::Deserialize)]
649 struct GuidRef {
650 guid: String,
651 }
652
653 let result: NewRequestResult = self.channel.send("newRequest", params).await?;
654
655 self.connection
656 .get_typed::<APIRequestContext>(&result.request.guid)
657 .await
658 }
659}
660
661impl std::fmt::Debug for APIRequest {
662 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
663 f.debug_struct("APIRequest").finish()
664 }
665}
666
667#[derive(Debug, Clone, Default)]
669pub(crate) struct InnerFetchOptions {
670 pub method: Option<String>,
671 pub headers: Option<std::collections::HashMap<String, String>>,
672 pub post_data: Option<String>,
673 pub post_data_bytes: Option<Vec<u8>>,
674 pub max_redirects: Option<u32>,
675 pub max_retries: Option<u32>,
676 pub timeout: Option<f64>,
677}
678
679impl ChannelOwner for APIRequestContext {
680 fn guid(&self) -> &str {
681 self.base.guid()
682 }
683
684 fn type_name(&self) -> &str {
685 self.base.type_name()
686 }
687
688 fn parent(&self) -> Option<Arc<dyn ChannelOwner>> {
689 self.base.parent()
690 }
691
692 fn connection(&self) -> Arc<dyn ConnectionLike> {
693 self.base.connection()
694 }
695
696 fn initializer(&self) -> &Value {
697 self.base.initializer()
698 }
699
700 fn channel(&self) -> &Channel {
701 self.base.channel()
702 }
703
704 fn dispose(&self, reason: DisposeReason) {
705 self.base.dispose(reason)
706 }
707
708 fn adopt(&self, child: Arc<dyn ChannelOwner>) {
709 self.base.adopt(child)
710 }
711
712 fn add_child(&self, guid: Arc<str>, child: Arc<dyn ChannelOwner>) {
713 self.base.add_child(guid, child)
714 }
715
716 fn remove_child(&self, guid: &str) {
717 self.base.remove_child(guid)
718 }
719
720 fn on_event(&self, method: &str, params: Value) {
721 self.base.on_event(method, params)
722 }
723
724 fn was_collected(&self) -> bool {
725 self.base.was_collected()
726 }
727
728 fn as_any(&self) -> &dyn Any {
729 self
730 }
731}
732
733impl std::fmt::Debug for APIRequestContext {
734 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
735 f.debug_struct("APIRequestContext")
736 .field("guid", &self.guid())
737 .finish()
738 }
739}