maker_web 0.1.2

Security-first, high-performance, zero-allocation HTTP server for microservices
Documentation
use crate::{query, Version};
use std::{error, fmt, io};

#[derive(Debug, Clone, PartialEq)]
pub(crate) enum ErrorKind {
    InvalidMethod,

    InvalidUrl,
    DoubleSlash,
    #[allow(dead_code)]
    Query(query::Error),

    InvalidVersion,
    UnsupportedVersion,

    InvalidHeader,
    TooManyHeaders,
    InvalidContentLength,
    InvalidConnection,

    BodyTooLarge,
    #[allow(dead_code)]
    BodyMismatch {
        expected: usize,
        available: usize,
    },
    #[allow(dead_code)]
    UnexpectedBody(usize),

    InvalidEncoding,
    ServiceUnavailable,
    Io(IoError),
}

macro_rules! http_errors {
    ($($name:ident: $status_code:expr, $len:literal => $json:literal; )*) => {
        pub(crate) const fn as_http(
            &self,
            version: Version,
            json: bool,
        ) -> &'static [u8] {
            match (json, self, version) { $(
                (true, Self::$name { .. }, Version::Http11) => concat!(
                    "HTTP/1.1 ", $status_code, "\r\n",
                    "connection: close\r\n",
                    "content-length: ", $len, "\r\n",
                    "content-type: application/json\r\n",
                    "\r\n",
                    $json
                ),
                (false, Self::$name { .. }, Version::Http11) => concat!(
                    "HTTP/1.1 ", $status_code, "\r\n",
                    "connection: close\r\n",
                    "content-length: 0\r\n\r\n",
                ),
                (true, Self::$name { .. }, Version::Http10) => concat!(
                    "HTTP/1.0 ", $status_code, "\r\n",
                    "connection: close\r\n",
                    "content-length: ", $len, "\r\n",
                    "content-type: application/json\r\n",
                    "\r\n",
                    $json
                ),
                (false, Self::$name { .. }, Version::Http10) => concat!(
                    "HTTP/1.0 ", $status_code, "\r\n",
                    "connection: close\r\n",
                    "content-length: 0\r\n\r\n",
                ),
                (_, Self::$name { .. }, Version::Http09) => concat!(
                    "ERROR: ", stringify!($status_code)
                ),
            )* }.as_bytes()
        }
    };
}

impl ErrorKind {
    http_errors! {
        InvalidMethod: "400 Bad Request", "55"
            => r#"{"error":"Invalid HTTP method","code":"INVALID_METHOD"}"#;

        InvalidUrl: "400 Bad Request", "51"
            => r#"{"error":"Invalid URL format","code":"INVALID_URL"}"#;
        DoubleSlash: "400 Bad Request", "81"
            => r#"{"error":"Consecutive slashes in URL","code":"DOUBLE_SLASH","msg":"fix yourself"}"#;
        Query: "400 Bad Request", "55"
            => r#"{"error":"Invalid query string","code":"INVALID_QUERY"}"#;

        InvalidVersion: "400 Bad Request", "57"
            => r#"{"error":"Invalid HTTP version","code":"INVALID_VERSION"}"#;
        UnsupportedVersion: "505 HTTP Version Not Supported", "67"
            => r#"{"error":"HTTP version not supported","code":"UNSUPPORTED_VERSION"}"#;

        InvalidHeader: "400 Bad Request", "57"
            => r#"{"error":"Invalid header format","code":"INVALID_HEADER"}"#;
        TooManyHeaders: "431 Request Header Fields Too Large", "54"
            => r#"{"error":"Too many headers","code":"TOO_MANY_HEADERS"}"#;
        InvalidContentLength: "400 Bad Request", "66"
            => r#"{"error":"Invalid Content-Length","code":"INVALID_CONTENT_LENGTH"}"#;
        InvalidConnection: "400 Bad Request", "65"
            => r#"{"error":"Invalid Connection header","code":"INVALID_CONNECTION"}"#;

        BodyTooLarge: "413 Payload Too Large", "58"
            => r#"{"error":"Request body too large","code":"BODY_TOO_LARGE"}"#;
        BodyMismatch: "400 Bad Request", "55"
            => r#"{"error":"Body length mismatch","code":"BODY_MISMATCH"}"#;
        UnexpectedBody: "400 Bad Request", "60"
            => r#"{"error":"Unexpected request body","code":"UNEXPECTED_BODY"}"#;

        InvalidEncoding: "400 Bad Request", "64"
            => r#"{"error":"Invalid character encoding","code":"INVALID_ENCODING"}"#;
        ServiceUnavailable: "503 Service Unavailable", "72"
            => r#"{"error":"Service temporarily unavailable","code":"SERVICE_UNAVAILABLE"}"#;
        Io: "503 Service Unavailable", "48"
            => r#"{"error":"I/O error occurred","code":"IO_ERROR"}"#;
    }
}

impl error::Error for ErrorKind {}
impl fmt::Display for ErrorKind {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{:?}", self)
    }
}

impl From<query::Error> for ErrorKind {
    fn from(err: query::Error) -> Self {
        ErrorKind::Query(err)
    }
}
impl From<io::Error> for ErrorKind {
    fn from(err: io::Error) -> Self {
        ErrorKind::Io(IoError(err))
    }
}

#[derive(Debug)]
pub(crate) struct IoError(pub(crate) io::Error);

impl PartialEq for IoError {
    fn eq(&self, other: &Self) -> bool {
        self.0.kind() == other.0.kind()
    }
}

impl Clone for IoError {
    fn clone(&self) -> Self {
        // To avoid using it in production code
        println!("Clone IoError");

        Self(io::Error::new(self.0.kind(), self.0.to_string()))
    }
}