1use std::time::Duration;
2
3use crate::wit::balius::app::http as wit;
4use serde::{Deserialize, Serialize};
5use url::Url;
6pub use wit::ErrorCode as HttpError;
7
8#[derive(Clone)]
9pub struct HttpRequest {
10 pub method: wit::Method,
11 pub url: Url,
12 pub headers: Vec<(wit::FieldName, wit::FieldValue)>,
13 pub body: Option<Vec<u8>>,
14 pub timeout: Option<Duration>,
15}
16
17macro_rules! helper_method {
18 ($name:ident, $method:expr) => {
19 pub fn $name(url: Url) -> Self {
20 Self::new($method, url)
21 }
22 };
23}
24
25impl HttpRequest {
26 pub fn new(method: wit::Method, url: Url) -> Self {
27 Self {
28 method,
29 url,
30 headers: vec![],
31 body: None,
32 timeout: None,
33 }
34 }
35
36 pub fn header(mut self, name: impl AsRef<str>, value: impl AsHeader) -> Self {
37 self.headers
38 .push((name.as_ref().to_string(), value.into_bytes()));
39 self
40 }
41
42 pub fn json<T: Serialize>(self, body: &T) -> Result<Self, HttpError> {
43 let body_bytes = serde_json::to_vec(body)
44 .map_err(|e| HttpError::InternalError(Some(format!("Invalid JSON: {e}"))))?;
45 Ok(Self {
46 body: Some(body_bytes),
47 ..self.header("content-type", "application/json")
48 })
49 }
50
51 pub fn send(self) -> Result<HttpResponse, HttpError> {
52 let scheme = match self.url.scheme() {
53 "http" => Some(wit::Scheme::Http),
54 "https" => Some(wit::Scheme::Https),
55 "" => None,
56 other => Some(wit::Scheme::Other(other.to_string())),
57 };
58 let authority = match self.url.authority() {
59 "" => None,
60 auth => Some(auth.to_string()),
61 };
62 let path_and_query = match (self.url.path(), self.url.query()) {
63 ("", None) => None,
64 (path, None) => Some(path.to_string()),
65 (path, Some(query)) => Some(format!("{path}?{query}")),
66 };
67 let request = wit::OutgoingRequest {
68 scheme,
69 authority,
70 path_and_query,
71 method: self.method,
72 headers: self.headers,
73 body: self.body,
74 };
75 let options = self.timeout.map(|t| wit::RequestOptions {
76 connect_timeout: None,
77 first_byte_timeout: None,
78 between_bytes_timeout: Some(t.as_nanos() as u64),
79 });
80 let response = wit::request(&request, options)?;
81 Ok(HttpResponse {
82 status: response.status,
83 headers: response.headers,
84 body: response.body,
85 })
86 }
87
88 helper_method!(get, wit::Method::Get);
89 helper_method!(put, wit::Method::Put);
90 helper_method!(post, wit::Method::Post);
91 helper_method!(patch, wit::Method::Patch);
92 helper_method!(delete, wit::Method::Delete);
93}
94
95pub struct HttpResponse {
96 pub status: u16,
97 pub headers: Vec<(wit::FieldName, wit::FieldValue)>,
98 pub body: Vec<u8>,
99}
100
101impl HttpResponse {
102 pub fn is_ok(&self) -> bool {
103 self.status >= 200 && self.status < 400
104 }
105
106 pub fn json<'a, T: Deserialize<'a>>(&'a self) -> Result<T, HttpError> {
107 serde_json::from_slice(&self.body)
108 .map_err(|e| HttpError::InternalError(Some(format!("Invalid JSON: {e}"))))?
109 }
110}
111
112pub trait AsHeader {
113 fn into_bytes(self) -> Vec<u8>;
114}
115
116impl AsHeader for String {
117 fn into_bytes(self) -> Vec<u8> {
118 self.into_bytes()
119 }
120}
121
122impl AsHeader for &str {
123 fn into_bytes(self) -> Vec<u8> {
124 self.as_bytes().to_vec()
125 }
126}
127
128impl AsHeader for Vec<u8> {
129 fn into_bytes(self) -> Vec<u8> {
130 self
131 }
132}
133
134impl AsHeader for &[u8] {
135 fn into_bytes(self) -> Vec<u8> {
136 self.to_vec()
137 }
138}