balius_runtime/http/
mod.rs1use 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}