wcgi/
lib.rs

1//! Common abstractions for implementing WCGI servers.
2
3pub mod convert;
4
5use std::io::{BufReader, Cursor, Read};
6
7pub use http::{header, HeaderMap, Method, StatusCode, Uri};
8
9pub type Request = http::Request<Body>;
10pub type Response = http::Response<Body>;
11pub type ResponseBuilder = http::response::Builder;
12
13pub fn response_builder() -> ResponseBuilder {
14    http::Response::builder()
15}
16
17pub struct Body(BodyInner);
18
19impl Body {
20    pub fn read_to_vec(self) -> Result<Vec<u8>, WcgiError> {
21        match self.0 {
22            BodyInner::Empty => Ok(Vec::new()),
23            BodyInner::Data(data) => Ok(data.into_inner()),
24            BodyInner::Stream(mut stream) => {
25                let mut buffer = Vec::new();
26                stream
27                    .read_to_end(&mut buffer)
28                    .map_err(|err| WcgiError::wrap(err, "could not read request body"))?;
29                Ok(buffer)
30            }
31        }
32    }
33}
34
35impl Read for Body {
36    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
37        match &mut self.0 {
38            BodyInner::Empty => Ok(0),
39            BodyInner::Data(data) => data.read(buf),
40            BodyInner::Stream(stream) => stream.read(buf),
41        }
42    }
43}
44
45impl Body {
46    pub fn new_reader<I: std::io::Read + 'static>(read: I) -> Self {
47        Self(BodyInner::Stream(BufReader::new(Box::new(read))))
48    }
49
50    pub fn new_bytes(data: impl Into<Vec<u8>>) -> Self {
51        Self(BodyInner::Data(Cursor::new(data.into())))
52    }
53
54    pub fn new_text<V: Into<String>>(value: V) -> Self {
55        Self(BodyInner::Data(Cursor::new(value.into().into_bytes())))
56    }
57
58    pub fn empty() -> Self {
59        Self(BodyInner::Empty)
60    }
61}
62
63enum BodyInner {
64    Empty,
65    Data(Cursor<Vec<u8>>),
66    Stream(BufReader<Box<dyn Read>>),
67}
68
69impl From<()> for Body {
70    fn from(_value: ()) -> Self {
71        Body(BodyInner::Empty)
72    }
73}
74
75impl From<Vec<u8>> for Body {
76    fn from(value: Vec<u8>) -> Self {
77        Body(BodyInner::Data(Cursor::new(value)))
78    }
79}
80
81impl From<String> for Body {
82    fn from(value: String) -> Self {
83        Body(BodyInner::Data(Cursor::new(value.into_bytes())))
84    }
85}
86
87pub trait RequestExt {
88    fn url(&self) -> url::Url;
89}
90
91impl RequestExt for Request {
92    fn url(&self) -> url::Url {
93        let host = self
94            .headers()
95            .get(header::HOST)
96            .and_then(|v| v.to_str().ok())
97            .map(|v| v.trim())
98            .filter(|v| !v.is_empty())
99            .unwrap_or("nohost");
100
101        let path = self.uri();
102        let raw = format!("http://{host}{path}");
103        raw.parse().unwrap()
104    }
105}
106
107#[derive(Debug)]
108pub struct WcgiError {
109    message: String,
110    source: Option<Box<dyn std::error::Error + Send + Sync>>,
111}
112
113impl WcgiError {
114    /// Wrap an error with a custom message.
115    #[track_caller]
116    pub fn wrap<E: std::error::Error + Send + Sync + 'static, M: Into<String>>(
117        error: E,
118        message: M,
119    ) -> Self {
120        Self {
121            message: message.into(),
122            source: Some(Box::new(error)),
123        }
124    }
125
126    /// Construct a new error with a simple error message.
127    #[track_caller]
128    pub fn msg<M: Into<String>>(message: M) -> Self {
129        Self {
130            message: message.into(),
131            source: None,
132        }
133    }
134
135    pub fn as_std(self) -> StdWcgiError {
136        StdWcgiError(self)
137    }
138
139    pub fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
140        match &self.source {
141            Some(s) => Some(&**s),
142            None => None,
143        }
144    }
145}
146
147impl std::fmt::Display for WcgiError {
148    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
149        write!(f, "{}", &self.message)?;
150        if let Some(source) = &self.source {
151            write!(f, "{source}")?;
152        }
153        Ok(())
154    }
155}
156
157impl<E: std::error::Error + Send + Sync + 'static> From<E> for WcgiError {
158    #[track_caller]
159    fn from(value: E) -> Self {
160        Self {
161            message: String::new(),
162            source: Some(Box::new(value)),
163        }
164    }
165}
166
167/// Wrapper for [`WcgiError`] that implements [`std::error::Error`];
168pub struct StdWcgiError(WcgiError);
169
170impl std::fmt::Debug for StdWcgiError {
171    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
172        self.0.fmt(f)
173    }
174}
175
176impl std::fmt::Display for StdWcgiError {
177    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
178        self.0.fmt(f)
179    }
180}
181
182impl std::error::Error for StdWcgiError {}
183
184impl From<WcgiError> for StdWcgiError {
185    fn from(value: WcgiError) -> Self {
186        Self(value)
187    }
188}
189
190pub trait WcgiHandler {
191    fn handle_request(&self, request: Request) -> Result<Response, WcgiError>;
192}
193
194impl<F: Fn(Request) -> Result<Response, WcgiError>> WcgiHandler for F {
195    fn handle_request(&self, request: Request) -> Result<Response, WcgiError> {
196        (self)(request)
197    }
198}
199
200/// Serve http requests with the provided handler.
201pub fn serve_once<H: WcgiHandler>(handler: H) {
202    let request = convert::extract_request().unwrap();
203    let response = handler.handle_request(request).unwrap();
204    convert::send_response_and_terminate(response);
205}