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)]
421#[non_exhaustive]
422pub struct APIRequestContextOptions {
423 pub base_url: Option<String>,
425 pub extra_http_headers: Option<HashMap<String, String>>,
427 pub ignore_https_errors: Option<bool>,
429 pub user_agent: Option<String>,
431 pub timeout: Option<f64>,
433}
434
435impl APIRequestContextOptions {
436 pub fn base_url(mut self, base_url: impl Into<String>) -> Self {
438 self.base_url = Some(base_url.into());
439 self
440 }
441 pub fn extra_http_headers(mut self, headers: HashMap<String, String>) -> Self {
443 self.extra_http_headers = Some(headers);
444 self
445 }
446 pub fn ignore_https_errors(mut self, ignore: bool) -> Self {
448 self.ignore_https_errors = Some(ignore);
449 self
450 }
451 pub fn user_agent(mut self, user_agent: impl Into<String>) -> Self {
453 self.user_agent = Some(user_agent.into());
454 self
455 }
456 pub fn timeout(mut self, timeout: f64) -> Self {
458 self.timeout = Some(timeout);
459 self
460 }
461}
462
463pub struct APIRequest {
494 channel: crate::server::channel::Channel,
495 connection: Arc<dyn ConnectionLike>,
496}
497
498impl APIRequest {
499 pub(crate) fn new(
500 channel: crate::server::channel::Channel,
501 connection: Arc<dyn ConnectionLike>,
502 ) -> Self {
503 Self {
504 channel,
505 connection,
506 }
507 }
508
509 pub async fn new_context(
517 &self,
518 options: Option<APIRequestContextOptions>,
519 ) -> Result<APIRequestContext> {
520 use crate::server::connection::ConnectionExt;
521
522 let mut params = json!({});
523
524 if let Some(opts) = options {
525 if let Some(base_url) = opts.base_url {
526 params["baseURL"] = json!(base_url);
527 }
528 if let Some(headers) = opts.extra_http_headers {
529 let arr: Vec<Value> = headers
530 .into_iter()
531 .map(|(name, value)| json!({"name": name, "value": value}))
532 .collect();
533 params["extraHTTPHeaders"] = json!(arr);
534 }
535 if let Some(ignore) = opts.ignore_https_errors {
536 params["ignoreHTTPSErrors"] = json!(ignore);
537 }
538 if let Some(ua) = opts.user_agent {
539 params["userAgent"] = json!(ua);
540 }
541 if let Some(timeout) = opts.timeout {
542 params["timeout"] = json!(timeout);
543 }
544 }
545
546 #[derive(serde::Deserialize)]
547 struct NewRequestResult {
548 request: GuidRef,
549 }
550
551 #[derive(serde::Deserialize)]
552 struct GuidRef {
553 guid: String,
554 }
555
556 let result: NewRequestResult = self.channel.send("newRequest", params).await?;
557
558 self.connection
559 .get_typed::<APIRequestContext>(&result.request.guid)
560 .await
561 }
562}
563
564impl std::fmt::Debug for APIRequest {
565 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
566 f.debug_struct("APIRequest").finish()
567 }
568}
569
570#[derive(Debug, Clone, Default)]
572pub(crate) struct InnerFetchOptions {
573 pub method: Option<String>,
574 pub headers: Option<std::collections::HashMap<String, String>>,
575 pub post_data: Option<String>,
576 pub post_data_bytes: Option<Vec<u8>>,
577 pub max_redirects: Option<u32>,
578 pub max_retries: Option<u32>,
579 pub timeout: Option<f64>,
580}
581
582impl ChannelOwner for APIRequestContext {
583 fn guid(&self) -> &str {
584 self.base.guid()
585 }
586
587 fn type_name(&self) -> &str {
588 self.base.type_name()
589 }
590
591 fn parent(&self) -> Option<Arc<dyn ChannelOwner>> {
592 self.base.parent()
593 }
594
595 fn connection(&self) -> Arc<dyn ConnectionLike> {
596 self.base.connection()
597 }
598
599 fn initializer(&self) -> &Value {
600 self.base.initializer()
601 }
602
603 fn channel(&self) -> &Channel {
604 self.base.channel()
605 }
606
607 fn dispose(&self, reason: DisposeReason) {
608 self.base.dispose(reason)
609 }
610
611 fn adopt(&self, child: Arc<dyn ChannelOwner>) {
612 self.base.adopt(child)
613 }
614
615 fn add_child(&self, guid: Arc<str>, child: Arc<dyn ChannelOwner>) {
616 self.base.add_child(guid, child)
617 }
618
619 fn remove_child(&self, guid: &str) {
620 self.base.remove_child(guid)
621 }
622
623 fn on_event(&self, method: &str, params: Value) {
624 self.base.on_event(method, params)
625 }
626
627 fn was_collected(&self) -> bool {
628 self.base.was_collected()
629 }
630
631 fn as_any(&self) -> &dyn Any {
632 self
633 }
634}
635
636impl std::fmt::Debug for APIRequestContext {
637 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
638 f.debug_struct("APIRequestContext")
639 .field("guid", &self.guid())
640 .finish()
641 }
642}