wcgi 0.1.2

Common abstractions for defining a WCGI server.
Documentation
//! Common abstractions for implementing WCGI servers.

pub mod convert;

use std::io::{BufReader, Cursor, Read};

pub use http::{header, HeaderMap, Method, StatusCode, Uri};

pub type Request = http::Request<Body>;
pub type Response = http::Response<Body>;
pub type ResponseBuilder = http::response::Builder;

pub fn response_builder() -> ResponseBuilder {
    http::Response::builder()
}

pub struct Body(BodyInner);

impl Body {
    pub fn read_to_vec(self) -> Result<Vec<u8>, WcgiError> {
        match self.0 {
            BodyInner::Empty => Ok(Vec::new()),
            BodyInner::Data(data) => Ok(data.into_inner()),
            BodyInner::Stream(mut stream) => {
                let mut buffer = Vec::new();
                stream
                    .read_to_end(&mut buffer)
                    .map_err(|err| WcgiError::wrap(err, "could not read request body"))?;
                Ok(buffer)
            }
        }
    }
}

impl Read for Body {
    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
        match &mut self.0 {
            BodyInner::Empty => Ok(0),
            BodyInner::Data(data) => data.read(buf),
            BodyInner::Stream(stream) => stream.read(buf),
        }
    }
}

impl Body {
    pub fn new_reader<I: std::io::Read + 'static>(read: I) -> Self {
        Self(BodyInner::Stream(BufReader::new(Box::new(read))))
    }

    pub fn new_bytes(data: impl Into<Vec<u8>>) -> Self {
        Self(BodyInner::Data(Cursor::new(data.into())))
    }

    pub fn new_text<V: Into<String>>(value: V) -> Self {
        Self(BodyInner::Data(Cursor::new(value.into().into_bytes())))
    }

    pub fn empty() -> Self {
        Self(BodyInner::Empty)
    }
}

enum BodyInner {
    Empty,
    Data(Cursor<Vec<u8>>),
    Stream(BufReader<Box<dyn Read>>),
}

impl From<()> for Body {
    fn from(_value: ()) -> Self {
        Body(BodyInner::Empty)
    }
}

impl From<Vec<u8>> for Body {
    fn from(value: Vec<u8>) -> Self {
        Body(BodyInner::Data(Cursor::new(value)))
    }
}

impl From<String> for Body {
    fn from(value: String) -> Self {
        Body(BodyInner::Data(Cursor::new(value.into_bytes())))
    }
}

pub trait RequestExt {
    fn url(&self) -> url::Url;
}

impl RequestExt for Request {
    fn url(&self) -> url::Url {
        let host = self
            .headers()
            .get(header::HOST)
            .and_then(|v| v.to_str().ok())
            .map(|v| v.trim())
            .filter(|v| !v.is_empty())
            .unwrap_or("nohost");

        let path = self.uri();
        let raw = format!("http://{host}{path}");
        raw.parse().unwrap()
    }
}

#[derive(Debug)]
pub struct WcgiError {
    message: String,
    source: Option<Box<dyn std::error::Error + Send + Sync>>,
}

impl WcgiError {
    /// Wrap an error with a custom message.
    #[track_caller]
    pub fn wrap<E: std::error::Error + Send + Sync + 'static, M: Into<String>>(
        error: E,
        message: M,
    ) -> Self {
        Self {
            message: message.into(),
            source: Some(Box::new(error)),
        }
    }

    /// Construct a new error with a simple error message.
    #[track_caller]
    pub fn msg<M: Into<String>>(message: M) -> Self {
        Self {
            message: message.into(),
            source: None,
        }
    }

    pub fn as_std(self) -> StdWcgiError {
        StdWcgiError(self)
    }

    pub fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
        match &self.source {
            Some(s) => Some(&**s),
            None => None,
        }
    }
}

impl std::fmt::Display for WcgiError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}", &self.message)?;
        if let Some(source) = &self.source {
            write!(f, "{source}")?;
        }
        Ok(())
    }
}

impl<E: std::error::Error + Send + Sync + 'static> From<E> for WcgiError {
    #[track_caller]
    fn from(value: E) -> Self {
        Self {
            message: String::new(),
            source: Some(Box::new(value)),
        }
    }
}

/// Wrapper for [`WcgiError`] that implements [`std::error::Error`];
pub struct StdWcgiError(WcgiError);

impl std::fmt::Debug for StdWcgiError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.fmt(f)
    }
}

impl std::fmt::Display for StdWcgiError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.fmt(f)
    }
}

impl std::error::Error for StdWcgiError {}

impl From<WcgiError> for StdWcgiError {
    fn from(value: WcgiError) -> Self {
        Self(value)
    }
}

pub trait WcgiHandler {
    fn handle_request(&self, request: Request) -> Result<Response, WcgiError>;
}

impl<F: Fn(Request) -> Result<Response, WcgiError>> WcgiHandler for F {
    fn handle_request(&self, request: Request) -> Result<Response, WcgiError> {
        (self)(request)
    }
}

/// Serve http requests with the provided handler.
pub fn serve_once<H: WcgiHandler>(handler: H) {
    let request = convert::extract_request().unwrap();
    let response = handler.handle_request(request).unwrap();
    convert::send_response_and_terminate(response);
}