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(&self, url: &str, options: Option<FetchOptions>) -> Result<APIResponse> {
55 let mut opts = options.unwrap_or_default();
56 opts.method = Some("GET".to_string());
57 self.fetch(url, Some(opts)).await
58 }
59
60 pub async fn post(&self, url: &str, options: Option<FetchOptions>) -> Result<APIResponse> {
64 let mut opts = options.unwrap_or_default();
65 opts.method = Some("POST".to_string());
66 self.fetch(url, Some(opts)).await
67 }
68
69 pub async fn put(&self, url: &str, options: Option<FetchOptions>) -> Result<APIResponse> {
73 let mut opts = options.unwrap_or_default();
74 opts.method = Some("PUT".to_string());
75 self.fetch(url, Some(opts)).await
76 }
77
78 pub async fn delete(&self, url: &str, options: Option<FetchOptions>) -> Result<APIResponse> {
82 let mut opts = options.unwrap_or_default();
83 opts.method = Some("DELETE".to_string());
84 self.fetch(url, Some(opts)).await
85 }
86
87 pub async fn patch(&self, url: &str, options: Option<FetchOptions>) -> Result<APIResponse> {
91 let mut opts = options.unwrap_or_default();
92 opts.method = Some("PATCH".to_string());
93 self.fetch(url, Some(opts)).await
94 }
95
96 pub async fn head(&self, url: &str, options: Option<FetchOptions>) -> Result<APIResponse> {
100 let mut opts = options.unwrap_or_default();
101 opts.method = Some("HEAD".to_string());
102 self.fetch(url, Some(opts)).await
103 }
104
105 pub async fn fetch(&self, url: &str, options: Option<FetchOptions>) -> Result<APIResponse> {
112 let opts = options.unwrap_or_default();
113
114 let mut params = json!({
115 "url": url,
116 "timeout": opts.timeout.unwrap_or(crate::DEFAULT_TIMEOUT_MS)
117 });
118
119 if let Some(method) = opts.method {
120 params["method"] = json!(method);
121 }
122 if let Some(headers) = opts.headers {
123 let headers_array: Vec<Value> = headers
124 .into_iter()
125 .map(|(name, value)| json!({"name": name, "value": value}))
126 .collect();
127 params["headers"] = json!(headers_array);
128 }
129 if let Some(post_data) = opts.post_data {
130 use base64::Engine;
131 let encoded = base64::engine::general_purpose::STANDARD.encode(post_data.as_bytes());
132 params["postData"] = json!(encoded);
133 } else if let Some(post_data_bytes) = opts.post_data_bytes {
134 use base64::Engine;
135 let encoded = base64::engine::general_purpose::STANDARD.encode(&post_data_bytes);
136 params["postData"] = json!(encoded);
137 }
138 if let Some(max_redirects) = opts.max_redirects {
139 params["maxRedirects"] = json!(max_redirects);
140 }
141 if let Some(max_retries) = opts.max_retries {
142 params["maxRetries"] = json!(max_retries);
143 }
144
145 #[derive(serde::Deserialize)]
146 struct FetchResult {
147 response: ApiResponseData,
148 }
149
150 #[derive(serde::Deserialize)]
151 #[serde(rename_all = "camelCase")]
152 struct ApiResponseData {
153 fetch_uid: String,
154 url: String,
155 status: u16,
156 status_text: String,
157 headers: Vec<HeaderEntry>,
158 }
159
160 #[derive(serde::Deserialize)]
161 struct HeaderEntry {
162 name: String,
163 value: String,
164 }
165
166 let result: FetchResult = self.base.channel().send("fetch", params).await?;
167
168 let headers: HashMap<String, String> = result
169 .response
170 .headers
171 .into_iter()
172 .map(|h| (h.name, h.value))
173 .collect();
174
175 Ok(APIResponse {
176 context: self.clone(),
177 url: result.response.url,
178 status: result.response.status,
179 status_text: result.response.status_text,
180 headers,
181 fetch_uid: result.response.fetch_uid,
182 })
183 }
184
185 pub async fn dispose(&self) -> Result<()> {
191 self.base
192 .channel()
193 .send_no_result("dispose", json!({}))
194 .await
195 }
196
197 pub(crate) async fn inner_fetch(
209 &self,
210 url: &str,
211 options: Option<InnerFetchOptions>,
212 ) -> Result<FetchResponse> {
213 let opts = options.unwrap_or_default();
214
215 let mut params = json!({
216 "url": url,
217 "timeout": opts.timeout.unwrap_or(crate::DEFAULT_TIMEOUT_MS)
218 });
219
220 if let Some(method) = opts.method {
221 params["method"] = json!(method);
222 }
223 if let Some(headers) = opts.headers {
224 let headers_array: Vec<Value> = headers
225 .into_iter()
226 .map(|(name, value)| json!({"name": name, "value": value}))
227 .collect();
228 params["headers"] = json!(headers_array);
229 }
230 if let Some(post_data) = opts.post_data {
231 use base64::Engine;
232 let encoded = base64::engine::general_purpose::STANDARD.encode(post_data.as_bytes());
233 params["postData"] = json!(encoded);
234 }
235 if let Some(post_data_bytes) = opts.post_data_bytes {
236 use base64::Engine;
237 let encoded = base64::engine::general_purpose::STANDARD.encode(&post_data_bytes);
238 params["postData"] = json!(encoded);
239 }
240 if let Some(max_redirects) = opts.max_redirects {
241 params["maxRedirects"] = json!(max_redirects);
242 }
243 if let Some(max_retries) = opts.max_retries {
244 params["maxRetries"] = json!(max_retries);
245 }
246
247 #[derive(serde::Deserialize)]
249 struct FetchResult {
250 response: ApiResponseData,
251 }
252
253 #[derive(serde::Deserialize)]
254 #[serde(rename_all = "camelCase")]
255 struct ApiResponseData {
256 fetch_uid: String,
257 #[allow(dead_code)]
258 url: String,
259 status: u16,
260 status_text: String,
261 headers: Vec<HeaderEntry>,
262 }
263
264 #[derive(serde::Deserialize)]
265 struct HeaderEntry {
266 name: String,
267 value: String,
268 }
269
270 let result: FetchResult = self.base.channel().send("fetch", params).await?;
271
272 let body = self.fetch_response_body(&result.response.fetch_uid).await?;
274
275 let _ = self.dispose_api_response(&result.response.fetch_uid).await;
277
278 Ok(FetchResponse {
279 status: result.response.status,
280 status_text: result.response.status_text,
281 headers: result
282 .response
283 .headers
284 .into_iter()
285 .map(|h| (h.name, h.value))
286 .collect(),
287 body,
288 })
289 }
290
291 async fn fetch_response_body(&self, fetch_uid: &str) -> Result<Vec<u8>> {
293 #[derive(serde::Deserialize)]
294 struct BodyResult {
295 #[serde(default)]
296 binary: Option<String>,
297 }
298
299 let result: BodyResult = self
300 .base
301 .channel()
302 .send("fetchResponseBody", json!({ "fetchUid": fetch_uid }))
303 .await?;
304
305 match result.binary {
306 Some(encoded) if !encoded.is_empty() => {
307 use base64::Engine;
308 base64::engine::general_purpose::STANDARD
309 .decode(&encoded)
310 .map_err(|e| {
311 crate::error::Error::ProtocolError(format!(
312 "Failed to decode response body: {}",
313 e
314 ))
315 })
316 }
317 _ => Ok(vec![]),
318 }
319 }
320
321 async fn dispose_api_response(&self, fetch_uid: &str) -> Result<()> {
323 self.base
324 .channel()
325 .send_no_result("disposeAPIResponse", json!({ "fetchUid": fetch_uid }))
326 .await
327 }
328}
329
330#[derive(Clone)]
337pub struct APIResponse {
338 context: APIRequestContext,
339 url: String,
340 status: u16,
341 status_text: String,
342 headers: HashMap<String, String>,
343 fetch_uid: String,
344}
345
346impl APIResponse {
347 pub fn url(&self) -> &str {
349 &self.url
350 }
351
352 pub fn status(&self) -> u16 {
354 self.status
355 }
356
357 pub fn status_text(&self) -> &str {
359 &self.status_text
360 }
361
362 pub fn ok(&self) -> bool {
364 (200..300).contains(&self.status)
365 }
366
367 pub fn headers(&self) -> &HashMap<String, String> {
369 &self.headers
370 }
371
372 pub async fn body(&self) -> Result<Vec<u8>> {
376 self.context.fetch_response_body(&self.fetch_uid).await
377 }
378
379 pub async fn text(&self) -> Result<String> {
383 let bytes = self.body().await?;
384 String::from_utf8(bytes).map_err(|e| {
385 crate::error::Error::ProtocolError(format!("Response body is not valid UTF-8: {}", e))
386 })
387 }
388
389 pub async fn json<T: DeserializeOwned>(&self) -> Result<T> {
393 let bytes = self.body().await?;
394 serde_json::from_slice(&bytes).map_err(|e| {
395 crate::error::Error::ProtocolError(format!("Failed to parse response JSON: {}", e))
396 })
397 }
398
399 pub async fn dispose(&self) -> Result<()> {
403 self.context.dispose_api_response(&self.fetch_uid).await
404 }
405}
406
407impl std::fmt::Debug for APIResponse {
408 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
409 f.debug_struct("APIResponse")
410 .field("url", &self.url)
411 .field("status", &self.status)
412 .field("status_text", &self.status_text)
413 .finish()
414 }
415}
416
417#[derive(Debug, Clone, Default)]
421pub struct APIRequestContextOptions {
422 pub base_url: Option<String>,
424 pub extra_http_headers: Option<HashMap<String, String>>,
426 pub ignore_https_errors: Option<bool>,
428 pub user_agent: Option<String>,
430 pub timeout: Option<f64>,
432}
433
434pub struct APIRequest {
465 channel: crate::server::channel::Channel,
466 connection: Arc<dyn ConnectionLike>,
467}
468
469impl APIRequest {
470 pub(crate) fn new(
471 channel: crate::server::channel::Channel,
472 connection: Arc<dyn ConnectionLike>,
473 ) -> Self {
474 Self {
475 channel,
476 connection,
477 }
478 }
479
480 pub async fn new_context(
488 &self,
489 options: Option<APIRequestContextOptions>,
490 ) -> Result<APIRequestContext> {
491 use crate::server::connection::ConnectionExt;
492
493 let mut params = json!({});
494
495 if let Some(opts) = options {
496 if let Some(base_url) = opts.base_url {
497 params["baseURL"] = json!(base_url);
498 }
499 if let Some(headers) = opts.extra_http_headers {
500 let arr: Vec<Value> = headers
501 .into_iter()
502 .map(|(name, value)| json!({"name": name, "value": value}))
503 .collect();
504 params["extraHTTPHeaders"] = json!(arr);
505 }
506 if let Some(ignore) = opts.ignore_https_errors {
507 params["ignoreHTTPSErrors"] = json!(ignore);
508 }
509 if let Some(ua) = opts.user_agent {
510 params["userAgent"] = json!(ua);
511 }
512 if let Some(timeout) = opts.timeout {
513 params["timeout"] = json!(timeout);
514 }
515 }
516
517 #[derive(serde::Deserialize)]
518 struct NewRequestResult {
519 request: GuidRef,
520 }
521
522 #[derive(serde::Deserialize)]
523 struct GuidRef {
524 guid: String,
525 }
526
527 let result: NewRequestResult = self.channel.send("newRequest", params).await?;
528
529 self.connection
530 .get_typed::<APIRequestContext>(&result.request.guid)
531 .await
532 }
533}
534
535impl std::fmt::Debug for APIRequest {
536 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
537 f.debug_struct("APIRequest").finish()
538 }
539}
540
541#[derive(Debug, Clone, Default)]
543pub(crate) struct InnerFetchOptions {
544 pub method: Option<String>,
545 pub headers: Option<std::collections::HashMap<String, String>>,
546 pub post_data: Option<String>,
547 pub post_data_bytes: Option<Vec<u8>>,
548 pub max_redirects: Option<u32>,
549 pub max_retries: Option<u32>,
550 pub timeout: Option<f64>,
551}
552
553impl ChannelOwner for APIRequestContext {
554 fn guid(&self) -> &str {
555 self.base.guid()
556 }
557
558 fn type_name(&self) -> &str {
559 self.base.type_name()
560 }
561
562 fn parent(&self) -> Option<Arc<dyn ChannelOwner>> {
563 self.base.parent()
564 }
565
566 fn connection(&self) -> Arc<dyn ConnectionLike> {
567 self.base.connection()
568 }
569
570 fn initializer(&self) -> &Value {
571 self.base.initializer()
572 }
573
574 fn channel(&self) -> &Channel {
575 self.base.channel()
576 }
577
578 fn dispose(&self, reason: DisposeReason) {
579 self.base.dispose(reason)
580 }
581
582 fn adopt(&self, child: Arc<dyn ChannelOwner>) {
583 self.base.adopt(child)
584 }
585
586 fn add_child(&self, guid: Arc<str>, child: Arc<dyn ChannelOwner>) {
587 self.base.add_child(guid, child)
588 }
589
590 fn remove_child(&self, guid: &str) {
591 self.base.remove_child(guid)
592 }
593
594 fn on_event(&self, method: &str, params: Value) {
595 self.base.on_event(method, params)
596 }
597
598 fn was_collected(&self) -> bool {
599 self.base.was_collected()
600 }
601
602 fn as_any(&self) -> &dyn Any {
603 self
604 }
605}
606
607impl std::fmt::Debug for APIRequestContext {
608 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
609 f.debug_struct("APIRequestContext")
610 .field("guid", &self.guid())
611 .finish()
612 }
613}