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 }
198
199 #[derive(serde::Deserialize)]
200 struct HeaderEntry {
201 name: String,
202 value: String,
203 }
204
205 let result: FetchResult = self.base.channel().send("fetch", params).await?;
206
207 let headers: HashMap<String, String> = result
208 .response
209 .headers
210 .into_iter()
211 .map(|h| (h.name, h.value))
212 .collect();
213
214 Ok(APIResponse {
215 context: self.clone(),
216 url: result.response.url,
217 status: result.response.status,
218 status_text: result.response.status_text,
219 headers,
220 fetch_uid: result.response.fetch_uid,
221 security_details: result.response.security_details,
222 server_addr: result.response.server_addr,
223 })
224 }
225
226 pub async fn dispose(&self) -> Result<()> {
232 self.base
233 .channel()
234 .send_no_result("dispose", json!({}))
235 .await
236 }
237
238 pub(crate) async fn inner_fetch(
250 &self,
251 url: &str,
252 options: Option<InnerFetchOptions>,
253 ) -> Result<FetchResponse> {
254 let opts = options.unwrap_or_default();
255
256 let mut params = json!({
257 "url": url,
258 "timeout": opts.timeout.unwrap_or(crate::DEFAULT_TIMEOUT_MS)
259 });
260
261 if let Some(method) = opts.method {
262 params["method"] = json!(method);
263 }
264 if let Some(headers) = opts.headers {
265 let headers_array: Vec<Value> = headers
266 .into_iter()
267 .map(|(name, value)| json!({"name": name, "value": value}))
268 .collect();
269 params["headers"] = json!(headers_array);
270 }
271 if let Some(post_data) = opts.post_data {
272 use base64::Engine;
273 let encoded = base64::engine::general_purpose::STANDARD.encode(post_data.as_bytes());
274 params["postData"] = json!(encoded);
275 }
276 if let Some(post_data_bytes) = opts.post_data_bytes {
277 use base64::Engine;
278 let encoded = base64::engine::general_purpose::STANDARD.encode(&post_data_bytes);
279 params["postData"] = json!(encoded);
280 }
281 if let Some(max_redirects) = opts.max_redirects {
282 params["maxRedirects"] = json!(max_redirects);
283 }
284 if let Some(max_retries) = opts.max_retries {
285 params["maxRetries"] = json!(max_retries);
286 }
287
288 #[derive(serde::Deserialize)]
290 struct FetchResult {
291 response: ApiResponseData,
292 }
293
294 #[derive(serde::Deserialize)]
295 #[serde(rename_all = "camelCase")]
296 struct ApiResponseData {
297 fetch_uid: String,
298 #[allow(dead_code)]
299 url: String,
300 status: u16,
301 status_text: String,
302 headers: Vec<HeaderEntry>,
303 }
304
305 #[derive(serde::Deserialize)]
306 struct HeaderEntry {
307 name: String,
308 value: String,
309 }
310
311 let result: FetchResult = self.base.channel().send("fetch", params).await?;
312
313 let body = self.fetch_response_body(&result.response.fetch_uid).await?;
315
316 let _ = self.dispose_api_response(&result.response.fetch_uid).await;
318
319 Ok(FetchResponse {
320 status: result.response.status,
321 status_text: result.response.status_text,
322 headers: result
323 .response
324 .headers
325 .into_iter()
326 .map(|h| (h.name, h.value))
327 .collect(),
328 body,
329 })
330 }
331
332 async fn fetch_response_body(&self, fetch_uid: &str) -> Result<Vec<u8>> {
334 #[derive(serde::Deserialize)]
335 struct BodyResult {
336 #[serde(default)]
337 binary: Option<String>,
338 }
339
340 let result: BodyResult = self
341 .base
342 .channel()
343 .send("fetchResponseBody", json!({ "fetchUid": fetch_uid }))
344 .await?;
345
346 match result.binary {
347 Some(encoded) if !encoded.is_empty() => {
348 use base64::Engine;
349 base64::engine::general_purpose::STANDARD
350 .decode(&encoded)
351 .map_err(|e| {
352 crate::error::Error::ProtocolError(format!(
353 "Failed to decode response body: {}",
354 e
355 ))
356 })
357 }
358 _ => Ok(vec![]),
359 }
360 }
361
362 async fn dispose_api_response(&self, fetch_uid: &str) -> Result<()> {
364 self.base
365 .channel()
366 .send_no_result("disposeAPIResponse", json!({ "fetchUid": fetch_uid }))
367 .await
368 }
369}
370
371#[derive(Clone)]
378pub struct APIResponse {
379 context: APIRequestContext,
380 url: String,
381 status: u16,
382 status_text: String,
383 headers: HashMap<String, String>,
384 fetch_uid: String,
385 security_details: Option<crate::protocol::response::SecurityDetails>,
386 server_addr: Option<crate::protocol::response::RemoteAddr>,
387}
388
389impl APIResponse {
390 pub fn url(&self) -> &str {
392 &self.url
393 }
394
395 pub fn status(&self) -> u16 {
397 self.status
398 }
399
400 pub fn status_text(&self) -> &str {
402 &self.status_text
403 }
404
405 pub fn ok(&self) -> bool {
407 (200..300).contains(&self.status)
408 }
409
410 pub fn headers(&self) -> &HashMap<String, String> {
412 &self.headers
413 }
414
415 pub fn security_details(&self) -> Option<&crate::protocol::response::SecurityDetails> {
420 self.security_details.as_ref()
421 }
422
423 pub fn server_addr(&self) -> Option<&crate::protocol::response::RemoteAddr> {
428 self.server_addr.as_ref()
429 }
430
431 pub async fn body(&self) -> Result<Vec<u8>> {
435 self.context.fetch_response_body(&self.fetch_uid).await
436 }
437
438 pub async fn text(&self) -> Result<String> {
442 let bytes = self.body().await?;
443 String::from_utf8(bytes).map_err(|e| {
444 crate::error::Error::ProtocolError(format!("Response body is not valid UTF-8: {}", e))
445 })
446 }
447
448 pub async fn json<T: DeserializeOwned>(&self) -> Result<T> {
452 let bytes = self.body().await?;
453 serde_json::from_slice(&bytes).map_err(|e| {
454 crate::error::Error::ProtocolError(format!("Failed to parse response JSON: {}", e))
455 })
456 }
457
458 pub async fn dispose(&self) -> Result<()> {
462 self.context.dispose_api_response(&self.fetch_uid).await
463 }
464}
465
466impl std::fmt::Debug for APIResponse {
467 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
468 f.debug_struct("APIResponse")
469 .field("url", &self.url)
470 .field("status", &self.status)
471 .field("status_text", &self.status_text)
472 .finish()
473 }
474}
475
476#[derive(Debug, Clone, Default)]
480#[non_exhaustive]
481pub struct APIRequestContextOptions {
482 pub base_url: Option<String>,
484 pub extra_http_headers: Option<HashMap<String, String>>,
486 pub ignore_https_errors: Option<bool>,
488 pub user_agent: Option<String>,
490 pub timeout: Option<f64>,
492}
493
494impl APIRequestContextOptions {
495 pub fn base_url(mut self, base_url: impl Into<String>) -> Self {
497 self.base_url = Some(base_url.into());
498 self
499 }
500 pub fn extra_http_headers(mut self, headers: HashMap<String, String>) -> Self {
502 self.extra_http_headers = Some(headers);
503 self
504 }
505 pub fn ignore_https_errors(mut self, ignore: bool) -> Self {
507 self.ignore_https_errors = Some(ignore);
508 self
509 }
510 pub fn user_agent(mut self, user_agent: impl Into<String>) -> Self {
512 self.user_agent = Some(user_agent.into());
513 self
514 }
515 pub fn timeout(mut self, timeout: f64) -> Self {
517 self.timeout = Some(timeout);
518 self
519 }
520}
521
522pub struct APIRequest {
553 channel: crate::server::channel::Channel,
554 connection: Arc<dyn ConnectionLike>,
555}
556
557impl APIRequest {
558 pub(crate) fn new(
559 channel: crate::server::channel::Channel,
560 connection: Arc<dyn ConnectionLike>,
561 ) -> Self {
562 Self {
563 channel,
564 connection,
565 }
566 }
567
568 pub async fn new_context(
576 &self,
577 options: impl Into<Option<APIRequestContextOptions>>,
578 ) -> Result<APIRequestContext> {
579 use crate::server::connection::ConnectionExt;
580
581 let options = options.into();
582 let mut params = json!({});
583
584 if let Some(opts) = options {
585 if let Some(base_url) = opts.base_url {
586 params["baseURL"] = json!(base_url);
587 }
588 if let Some(headers) = opts.extra_http_headers {
589 let arr: Vec<Value> = headers
590 .into_iter()
591 .map(|(name, value)| json!({"name": name, "value": value}))
592 .collect();
593 params["extraHTTPHeaders"] = json!(arr);
594 }
595 if let Some(ignore) = opts.ignore_https_errors {
596 params["ignoreHTTPSErrors"] = json!(ignore);
597 }
598 if let Some(ua) = opts.user_agent {
599 params["userAgent"] = json!(ua);
600 }
601 if let Some(timeout) = opts.timeout {
602 params["timeout"] = json!(timeout);
603 }
604 }
605
606 #[derive(serde::Deserialize)]
607 struct NewRequestResult {
608 request: GuidRef,
609 }
610
611 #[derive(serde::Deserialize)]
612 struct GuidRef {
613 guid: String,
614 }
615
616 let result: NewRequestResult = self.channel.send("newRequest", params).await?;
617
618 self.connection
619 .get_typed::<APIRequestContext>(&result.request.guid)
620 .await
621 }
622}
623
624impl std::fmt::Debug for APIRequest {
625 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
626 f.debug_struct("APIRequest").finish()
627 }
628}
629
630#[derive(Debug, Clone, Default)]
632pub(crate) struct InnerFetchOptions {
633 pub method: Option<String>,
634 pub headers: Option<std::collections::HashMap<String, String>>,
635 pub post_data: Option<String>,
636 pub post_data_bytes: Option<Vec<u8>>,
637 pub max_redirects: Option<u32>,
638 pub max_retries: Option<u32>,
639 pub timeout: Option<f64>,
640}
641
642impl ChannelOwner for APIRequestContext {
643 fn guid(&self) -> &str {
644 self.base.guid()
645 }
646
647 fn type_name(&self) -> &str {
648 self.base.type_name()
649 }
650
651 fn parent(&self) -> Option<Arc<dyn ChannelOwner>> {
652 self.base.parent()
653 }
654
655 fn connection(&self) -> Arc<dyn ConnectionLike> {
656 self.base.connection()
657 }
658
659 fn initializer(&self) -> &Value {
660 self.base.initializer()
661 }
662
663 fn channel(&self) -> &Channel {
664 self.base.channel()
665 }
666
667 fn dispose(&self, reason: DisposeReason) {
668 self.base.dispose(reason)
669 }
670
671 fn adopt(&self, child: Arc<dyn ChannelOwner>) {
672 self.base.adopt(child)
673 }
674
675 fn add_child(&self, guid: Arc<str>, child: Arc<dyn ChannelOwner>) {
676 self.base.add_child(guid, child)
677 }
678
679 fn remove_child(&self, guid: &str) {
680 self.base.remove_child(guid)
681 }
682
683 fn on_event(&self, method: &str, params: Value) {
684 self.base.on_event(method, params)
685 }
686
687 fn was_collected(&self) -> bool {
688 self.base.was_collected()
689 }
690
691 fn as_any(&self) -> &dyn Any {
692 self
693 }
694}
695
696impl std::fmt::Debug for APIRequestContext {
697 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
698 f.debug_struct("APIRequestContext")
699 .field("guid", &self.guid())
700 .finish()
701 }
702}