balius_runtime/http/
mod.rs

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