balius_runtime/http/
mod.rs

1use 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}