1use crate::marshal::register_typed_async_fn_3_full;
50use crate::marshal::register_typed_async_fn_2_full;
51use crate::module_exports::{ModuleExports, ModuleParam};
52use crate::typed_module_exports::{ConcreteReturn, ConcreteType, TypedReturn};
53use shape_value::heap_value::HeapValue;
54use std::sync::Arc;
55
56fn build_response_pairs(
61 status: u16,
62 headers: Vec<(String, String)>,
63 body: String,
64) -> Vec<(String, ConcreteReturn)> {
65 vec![
66 ("status".to_string(), ConcreteReturn::I64(status as i64)),
67 (
68 "headers".to_string(),
69 ConcreteReturn::HashMapStringString(headers),
70 ),
71 ("body".to_string(), ConcreteReturn::String(body)),
72 (
73 "ok".to_string(),
74 ConcreteReturn::Bool((200..300).contains(&status)),
75 ),
76 ]
77}
78
79fn extract_headers(options: &[(Arc<String>, Arc<HeapValue>)]) -> Vec<(String, String)> {
85 for (k, v) in options.iter() {
86 if k.as_str() == "headers" {
87 if let HeapValue::HashMap(kref) = &**v {
88 use shape_value::heap_value::HashMapKindedRef;
93 if let HashMapKindedRef::String(arc) = kref {
94 let n = arc.len();
95 let mut out = Vec::with_capacity(n);
96 for i in 0..n {
97 let key: String = unsafe {
98 let ptr = shape_value::v2::typed_array::TypedArray::get_unchecked(
99 arc.keys, i as u32,
100 );
101 shape_value::v2::string_obj::StringObj::as_str(ptr).to_owned()
102 };
103 let val: String = unsafe {
104 let v_ptr: *const shape_value::v2::string_obj::StringObj =
105 *(*arc.values).data.add(i);
106 shape_value::v2::string_obj::StringObj::as_str(v_ptr).to_owned()
107 };
108 out.push((key, val));
109 }
110 return out;
111 }
112 return Vec::new();
113 }
114 }
115 }
116 Vec::new()
117}
118
119fn extract_timeout(
130 options: &[(Arc<String>, Arc<HeapValue>)],
131) -> Option<std::time::Duration> {
132 for (k, v) in options.iter() {
133 if k.as_str() == "timeout" {
134 if let HeapValue::BigInt(ms) = &**v {
135 let n = **ms;
136 if n > 0 {
137 return Some(std::time::Duration::from_millis(n as u64));
138 }
139 }
140 }
141 }
142 None
143}
144
145pub fn create_http_module() -> ModuleExports {
147 let mut module = ModuleExports::new("std::core::http");
148 module.description = "HTTP client for making web requests".to_string();
149
150 let url_param = ModuleParam {
151 name: "url".to_string(),
152 type_name: "string".to_string(),
153 required: true,
154 description: "URL to request".to_string(),
155 ..Default::default()
156 };
157
158 let options_param = ModuleParam {
159 name: "options".to_string(),
160 type_name: "HashMap<string, any>".to_string(),
161 required: false,
162 description: "Request options: { headers?: HashMap, timeout?: int }"
163 .to_string(),
164 default_snippet: Some("{}".to_string()),
165 ..Default::default()
166 };
167
168 let response_ty =
169 ConcreteType::Result(Box::new(ConcreteType::Named("HttpResponse".to_string())));
170
171 register_typed_async_fn_2_full::<_, _, Arc<String>, Vec<(Arc<String>, Arc<HeapValue>)>>(
173 &mut module,
174 "get",
175 "Perform an HTTP GET request",
176 [url_param.clone(), options_param.clone()],
177 response_ty.clone(),
178 |url: Arc<String>, options: Vec<(Arc<String>, Arc<HeapValue>)>| async move {
179 let mut builder = reqwest::Client::new().get(url.as_str());
180
181 for (k, v) in extract_headers(&options) {
182 builder = builder.header(&k, &v);
183 }
184 if let Some(timeout) = extract_timeout(&options) {
185 builder = builder.timeout(timeout);
186 }
187
188 let resp = builder
189 .send()
190 .await
191 .map_err(|e| format!("http.get() failed: {}", e))?;
192
193 let status = resp.status().as_u16();
194 let headers: Vec<(String, String)> = resp
195 .headers()
196 .iter()
197 .map(|(k, v)| (k.as_str().to_string(), v.to_str().unwrap_or("").to_string()))
198 .collect();
199 let body = resp
200 .text()
201 .await
202 .map_err(|e| format!("http.get() body read failed: {}", e))?;
203
204 Ok(TypedReturn::OkObjectPairs(build_response_pairs(
205 status, headers, body,
206 )))
207 },
208 );
209
210 register_typed_async_fn_2_full::<_, _, Arc<String>, Vec<(Arc<String>, Arc<HeapValue>)>>(
212 &mut module,
213 "delete",
214 "Perform an HTTP DELETE request",
215 [url_param, options_param],
216 response_ty,
217 |url: Arc<String>, options: Vec<(Arc<String>, Arc<HeapValue>)>| async move {
218 let mut builder = reqwest::Client::new().delete(url.as_str());
219
220 for (k, v) in extract_headers(&options) {
221 builder = builder.header(&k, &v);
222 }
223 if let Some(timeout) = extract_timeout(&options) {
224 builder = builder.timeout(timeout);
225 }
226
227 let resp = builder
228 .send()
229 .await
230 .map_err(|e| format!("http.delete() failed: {}", e))?;
231
232 let status = resp.status().as_u16();
233 let headers: Vec<(String, String)> = resp
234 .headers()
235 .iter()
236 .map(|(k, v)| (k.as_str().to_string(), v.to_str().unwrap_or("").to_string()))
237 .collect();
238 let body = resp
239 .text()
240 .await
241 .map_err(|e| format!("http.delete() body read failed: {}", e))?;
242
243 Ok(TypedReturn::OkObjectPairs(build_response_pairs(
244 status, headers, body,
245 )))
246 },
247 );
248
249 let url_param_3 = ModuleParam {
256 name: "url".to_string(),
257 type_name: "string".to_string(),
258 required: true,
259 description: "URL to request".to_string(),
260 ..Default::default()
261 };
262 let options_param_3 = ModuleParam {
263 name: "options".to_string(),
264 type_name: "HashMap<string, any>".to_string(),
265 required: false,
266 description: "Request options: { headers?: HashMap, timeout?: int }"
267 .to_string(),
268 default_snippet: Some("{}".to_string()),
269 ..Default::default()
270 };
271 let body_text_param = ModuleParam {
272 name: "body".to_string(),
273 type_name: "string".to_string(),
274 required: true,
275 description: "Request body as a string (sent verbatim)".to_string(),
276 ..Default::default()
277 };
278 let body_bytes_param = ModuleParam {
279 name: "body".to_string(),
280 type_name: "Array<int>".to_string(),
281 required: true,
282 description: "Request body as a byte array".to_string(),
283 ..Default::default()
284 };
285 let response_ty_3 =
286 ConcreteType::Result(Box::new(ConcreteType::Named("HttpResponse".to_string())));
287
288 register_typed_async_fn_3_full::<
290 _,
291 _,
292 Arc<String>,
293 Arc<String>,
294 Vec<(Arc<String>, Arc<HeapValue>)>,
295 >(
296 &mut module,
297 "post_text",
298 "Perform an HTTP POST request with a text body",
299 [
300 url_param_3.clone(),
301 body_text_param.clone(),
302 options_param_3.clone(),
303 ],
304 response_ty_3.clone(),
305 |url: Arc<String>,
306 body: Arc<String>,
307 options: Vec<(Arc<String>, Arc<HeapValue>)>| async move {
308 let mut builder = reqwest::Client::new()
309 .post(url.as_str())
310 .header(
311 reqwest::header::CONTENT_TYPE,
312 "text/plain; charset=utf-8",
313 )
314 .body(body.as_str().to_string());
315
316 for (k, v) in extract_headers(&options) {
317 builder = builder.header(&k, &v);
318 }
319 if let Some(timeout) = extract_timeout(&options) {
320 builder = builder.timeout(timeout);
321 }
322
323 let resp = builder
324 .send()
325 .await
326 .map_err(|e| format!("http.post_text() failed: {}", e))?;
327
328 let status = resp.status().as_u16();
329 let headers: Vec<(String, String)> = resp
330 .headers()
331 .iter()
332 .map(|(k, v)| (k.as_str().to_string(), v.to_str().unwrap_or("").to_string()))
333 .collect();
334 let body_out = resp
335 .text()
336 .await
337 .map_err(|e| format!("http.post_text() body read failed: {}", e))?;
338
339 Ok(TypedReturn::OkObjectPairs(build_response_pairs(
340 status, headers, body_out,
341 )))
342 },
343 );
344
345 register_typed_async_fn_3_full::<
347 _,
348 _,
349 Arc<String>,
350 Vec<u8>,
351 Vec<(Arc<String>, Arc<HeapValue>)>,
352 >(
353 &mut module,
354 "post_bytes",
355 "Perform an HTTP POST request with a binary body",
356 [
357 url_param_3.clone(),
358 body_bytes_param.clone(),
359 options_param_3.clone(),
360 ],
361 response_ty_3.clone(),
362 |url: Arc<String>,
363 body: Vec<u8>,
364 options: Vec<(Arc<String>, Arc<HeapValue>)>| async move {
365 let mut builder = reqwest::Client::new()
366 .post(url.as_str())
367 .header(
368 reqwest::header::CONTENT_TYPE,
369 "application/octet-stream",
370 )
371 .body(body);
372
373 for (k, v) in extract_headers(&options) {
374 builder = builder.header(&k, &v);
375 }
376 if let Some(timeout) = extract_timeout(&options) {
377 builder = builder.timeout(timeout);
378 }
379
380 let resp = builder
381 .send()
382 .await
383 .map_err(|e| format!("http.post_bytes() failed: {}", e))?;
384
385 let status = resp.status().as_u16();
386 let headers: Vec<(String, String)> = resp
387 .headers()
388 .iter()
389 .map(|(k, v)| (k.as_str().to_string(), v.to_str().unwrap_or("").to_string()))
390 .collect();
391 let body_out = resp
392 .text()
393 .await
394 .map_err(|e| format!("http.post_bytes() body read failed: {}", e))?;
395
396 Ok(TypedReturn::OkObjectPairs(build_response_pairs(
397 status, headers, body_out,
398 )))
399 },
400 );
401
402 register_typed_async_fn_3_full::<
404 _,
405 _,
406 Arc<String>,
407 Arc<String>,
408 Vec<(Arc<String>, Arc<HeapValue>)>,
409 >(
410 &mut module,
411 "put_text",
412 "Perform an HTTP PUT request with a text body",
413 [
414 url_param_3.clone(),
415 body_text_param,
416 options_param_3.clone(),
417 ],
418 response_ty_3.clone(),
419 |url: Arc<String>,
420 body: Arc<String>,
421 options: Vec<(Arc<String>, Arc<HeapValue>)>| async move {
422 let mut builder = reqwest::Client::new()
423 .put(url.as_str())
424 .header(
425 reqwest::header::CONTENT_TYPE,
426 "text/plain; charset=utf-8",
427 )
428 .body(body.as_str().to_string());
429
430 for (k, v) in extract_headers(&options) {
431 builder = builder.header(&k, &v);
432 }
433 if let Some(timeout) = extract_timeout(&options) {
434 builder = builder.timeout(timeout);
435 }
436
437 let resp = builder
438 .send()
439 .await
440 .map_err(|e| format!("http.put_text() failed: {}", e))?;
441
442 let status = resp.status().as_u16();
443 let headers: Vec<(String, String)> = resp
444 .headers()
445 .iter()
446 .map(|(k, v)| (k.as_str().to_string(), v.to_str().unwrap_or("").to_string()))
447 .collect();
448 let body_out = resp
449 .text()
450 .await
451 .map_err(|e| format!("http.put_text() body read failed: {}", e))?;
452
453 Ok(TypedReturn::OkObjectPairs(build_response_pairs(
454 status, headers, body_out,
455 )))
456 },
457 );
458
459 register_typed_async_fn_3_full::<
461 _,
462 _,
463 Arc<String>,
464 Vec<u8>,
465 Vec<(Arc<String>, Arc<HeapValue>)>,
466 >(
467 &mut module,
468 "put_bytes",
469 "Perform an HTTP PUT request with a binary body",
470 [url_param_3, body_bytes_param, options_param_3],
471 response_ty_3,
472 |url: Arc<String>,
473 body: Vec<u8>,
474 options: Vec<(Arc<String>, Arc<HeapValue>)>| async move {
475 let mut builder = reqwest::Client::new()
476 .put(url.as_str())
477 .header(
478 reqwest::header::CONTENT_TYPE,
479 "application/octet-stream",
480 )
481 .body(body);
482
483 for (k, v) in extract_headers(&options) {
484 builder = builder.header(&k, &v);
485 }
486 if let Some(timeout) = extract_timeout(&options) {
487 builder = builder.timeout(timeout);
488 }
489
490 let resp = builder
491 .send()
492 .await
493 .map_err(|e| format!("http.put_bytes() failed: {}", e))?;
494
495 let status = resp.status().as_u16();
496 let headers: Vec<(String, String)> = resp
497 .headers()
498 .iter()
499 .map(|(k, v)| (k.as_str().to_string(), v.to_str().unwrap_or("").to_string()))
500 .collect();
501 let body_out = resp
502 .text()
503 .await
504 .map_err(|e| format!("http.put_bytes() body read failed: {}", e))?;
505
506 Ok(TypedReturn::OkObjectPairs(build_response_pairs(
507 status, headers, body_out,
508 )))
509 },
510 );
511
512 let url_param_post_json = ModuleParam {
529 name: "url".to_string(),
530 type_name: "string".to_string(),
531 required: true,
532 description: "URL to request".to_string(),
533 ..Default::default()
534 };
535 let body_object_param_post = ModuleParam {
536 name: "body".to_string(),
537 type_name: "object".to_string(),
538 required: true,
539 description: "Request body as an object (sent as JSON)".to_string(),
540 ..Default::default()
541 };
542 let options_param_post_json = ModuleParam {
543 name: "options".to_string(),
544 type_name: "HashMap<string, any>".to_string(),
545 required: false,
546 description: "Request options: { headers?: HashMap, timeout?: int }"
547 .to_string(),
548 default_snippet: Some("{}".to_string()),
549 ..Default::default()
550 };
551 let response_ty_post_json =
552 ConcreteType::Result(Box::new(ConcreteType::Named("HttpResponse".to_string())));
553
554 register_typed_async_fn_3_full::<
556 _,
557 _,
558 Arc<String>,
559 Vec<(Arc<String>, Arc<HeapValue>)>,
560 Vec<(Arc<String>, Arc<HeapValue>)>,
561 >(
562 &mut module,
563 "post_json",
564 "Perform an HTTP POST request with a JSON body",
565 [
566 url_param_post_json.clone(),
567 body_object_param_post,
568 options_param_post_json.clone(),
569 ],
570 response_ty_post_json.clone(),
571 |url: Arc<String>,
572 body: Vec<(Arc<String>, Arc<HeapValue>)>,
573 options: Vec<(Arc<String>, Arc<HeapValue>)>| async move {
574 let mut json_pairs: Vec<(String, crate::json_value::JsonValue)> =
575 Vec::with_capacity(body.len());
576 for (k, v) in body.iter() {
577 json_pairs.push(((**k).clone(), crate::json_value::heap_to_json_value(v)?));
578 }
579 let json_value = crate::json_value::JsonValue::Object(json_pairs);
580 let serde_json_v = crate::json_value::json_value_to_serde_json(&json_value);
581 let body_str = serde_json::to_string(&serde_json_v)
582 .map_err(|e| format!("http.post_json() body serialization failed: {}", e))?;
583
584 let mut builder = reqwest::Client::new()
585 .post(url.as_str())
586 .header(reqwest::header::CONTENT_TYPE, "application/json")
587 .body(body_str);
588
589 for (k, v) in extract_headers(&options) {
590 builder = builder.header(&k, &v);
591 }
592 if let Some(timeout) = extract_timeout(&options) {
593 builder = builder.timeout(timeout);
594 }
595
596 let resp = builder
597 .send()
598 .await
599 .map_err(|e| format!("http.post_json() failed: {}", e))?;
600
601 let status = resp.status().as_u16();
602 let headers: Vec<(String, String)> = resp
603 .headers()
604 .iter()
605 .map(|(k, v)| (k.as_str().to_string(), v.to_str().unwrap_or("").to_string()))
606 .collect();
607 let body_out = resp
608 .text()
609 .await
610 .map_err(|e| format!("http.post_json() body read failed: {}", e))?;
611
612 Ok(TypedReturn::OkObjectPairs(build_response_pairs(
613 status, headers, body_out,
614 )))
615 },
616 );
617
618 let body_object_param_put = ModuleParam {
620 name: "body".to_string(),
621 type_name: "object".to_string(),
622 required: true,
623 description: "Request body as an object (sent as JSON)".to_string(),
624 ..Default::default()
625 };
626 register_typed_async_fn_3_full::<
627 _,
628 _,
629 Arc<String>,
630 Vec<(Arc<String>, Arc<HeapValue>)>,
631 Vec<(Arc<String>, Arc<HeapValue>)>,
632 >(
633 &mut module,
634 "put_json",
635 "Perform an HTTP PUT request with a JSON body",
636 [url_param_post_json, body_object_param_put, options_param_post_json],
637 response_ty_post_json,
638 |url: Arc<String>,
639 body: Vec<(Arc<String>, Arc<HeapValue>)>,
640 options: Vec<(Arc<String>, Arc<HeapValue>)>| async move {
641 let mut json_pairs: Vec<(String, crate::json_value::JsonValue)> =
642 Vec::with_capacity(body.len());
643 for (k, v) in body.iter() {
644 json_pairs.push(((**k).clone(), crate::json_value::heap_to_json_value(v)?));
645 }
646 let json_value = crate::json_value::JsonValue::Object(json_pairs);
647 let serde_json_v = crate::json_value::json_value_to_serde_json(&json_value);
648 let body_str = serde_json::to_string(&serde_json_v)
649 .map_err(|e| format!("http.put_json() body serialization failed: {}", e))?;
650
651 let mut builder = reqwest::Client::new()
652 .put(url.as_str())
653 .header(reqwest::header::CONTENT_TYPE, "application/json")
654 .body(body_str);
655
656 for (k, v) in extract_headers(&options) {
657 builder = builder.header(&k, &v);
658 }
659 if let Some(timeout) = extract_timeout(&options) {
660 builder = builder.timeout(timeout);
661 }
662
663 let resp = builder
664 .send()
665 .await
666 .map_err(|e| format!("http.put_json() failed: {}", e))?;
667
668 let status = resp.status().as_u16();
669 let headers: Vec<(String, String)> = resp
670 .headers()
671 .iter()
672 .map(|(k, v)| (k.as_str().to_string(), v.to_str().unwrap_or("").to_string()))
673 .collect();
674 let body_out = resp
675 .text()
676 .await
677 .map_err(|e| format!("http.put_json() body read failed: {}", e))?;
678
679 Ok(TypedReturn::OkObjectPairs(build_response_pairs(
680 status, headers, body_out,
681 )))
682 },
683 );
684
685 module
686}