balius_runtime/http/
mod.rs1use std::{error::Error, time::Duration};
2
3use crate::wit::balius::app::http as wit;
4use async_trait::async_trait;
5use reqwest::{
6 header::{HeaderMap, HeaderName, HeaderValue},
7 Method,
8};
9
10#[derive(Clone)]
11pub enum Http {
12 Mock,
13 Reqwest(reqwest::Client),
14}
15
16#[async_trait]
17impl wit::Host for Http {
18 async fn request(
19 &mut self,
20 request: wit::OutgoingRequest,
21 options: Option<wit::RequestOptions>,
22 ) -> Result<wit::IncomingResponse, wit::ErrorCode> {
23 match self {
24 Self::Mock => Ok(wit::IncomingResponse {
25 status: 200,
26 headers: vec![],
27 body: vec![],
28 }),
29 Self::Reqwest(client) => {
30 let scheme = match &request.scheme {
31 Some(wit::Scheme::Http) => "http",
32 Some(wit::Scheme::Https) => "https",
33 Some(wit::Scheme::Other(scheme)) => scheme,
34 None => "http",
35 };
36 let uri = match (
37 request.authority.as_deref(),
38 request.path_and_query.as_deref(),
39 ) {
40 (None, None) => return Err(wit::ErrorCode::HttpRequestUriInvalid),
41 (auth, path) => format!(
42 "{scheme}://{}{}",
43 auth.unwrap_or_default(),
44 path.unwrap_or_default()
45 ),
46 };
47
48 let method = match request.method {
49 wit::Method::Get => Method::GET,
50 wit::Method::Head => Method::HEAD,
51 wit::Method::Post => Method::POST,
52 wit::Method::Put => Method::PUT,
53 wit::Method::Delete => Method::DELETE,
54 wit::Method::Connect => Method::CONNECT,
55 wit::Method::Options => Method::OPTIONS,
56 wit::Method::Trace => Method::TRACE,
57 wit::Method::Patch => Method::PATCH,
58 wit::Method::Other(name) => Method::from_bytes(name.as_bytes())
59 .map_err(|_| wit::ErrorCode::HttpRequestMethodInvalid)?,
60 };
61
62 let mut header_map = HeaderMap::new();
63 for (key, value) in request.headers {
64 let header_name = HeaderName::from_bytes(key.as_bytes()).map_err(|e| {
65 wit::ErrorCode::InternalError(Some(format!(
66 "Invalid header name \"{key}\": {e}"
67 )))
68 })?;
69 let header_value = HeaderValue::from_bytes(&value).map_err(|e| {
70 wit::ErrorCode::InternalError(Some(format!(
71 "Invalid header value for \"{key}\": {e}"
72 )))
73 })?;
74 header_map.append(header_name, header_value);
75 }
76
77 let mut builder = client.request(method, uri).headers(header_map);
78 if let Some(body) = request.body {
79 builder = builder.body(body);
80 }
81
82 let mut request = builder
83 .build()
84 .map_err(|_| wit::ErrorCode::HttpRequestUriInvalid)?;
85
86 if let Some(timeout) = options.and_then(|o| o.between_bytes_timeout) {
87 request.timeout_mut().replace(Duration::from_nanos(timeout));
88 }
89
90 let response = client
91 .execute(request)
92 .await
93 .map_err(map_reqwest_response_err)?;
94
95 let status = response.status().as_u16();
96 let headers = response
97 .headers()
98 .into_iter()
99 .map(|(header, value)| {
100 let key = header.to_string();
101 let val = value.as_bytes().to_vec();
102 (key, val)
103 })
104 .collect();
105 let body = response
106 .bytes()
107 .await
108 .map_err(map_reqwest_response_err)?
109 .to_vec();
110
111 Ok(wit::IncomingResponse {
112 status,
113 headers,
114 body,
115 })
116 }
117 }
118 }
119}
120
121fn map_reqwest_response_err(e: reqwest::Error) -> wit::ErrorCode {
122 if e.is_timeout() {
123 wit::ErrorCode::HttpResponseTimeout
124 } else {
125 let message = match e.source() {
126 Some(source) => format!("{}: {}", e, source),
127 None => e.to_string(),
128 };
129 wit::ErrorCode::InternalError(Some(message))
130 }
131}