scratch-server 1.0.1

Simple HTTP Server
Documentation
use std::fmt;
use std::ops::{Deref, DerefMut};

use crate::{http_parse_error::HttpParseError, Body, HttpResponse};

#[derive(Debug)]
pub struct ApiError(Box<ApiErrorImpl>);

#[derive(Debug)]
pub struct ApiErrorImpl {
    pub error_response: HttpResponse,
    pub method: Option<String>,
    pub path: Option<String>,
}

impl Deref for ApiError {
    type Target = ApiErrorImpl;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl DerefMut for ApiError {
    fn deref_mut(&mut self) -> &mut Self::Target {
        &mut self.0
    }
}

impl ApiError {
    pub fn new_with_html(code: u16, message: &str) -> Self {
        ApiError(Box::new(ApiErrorImpl {
            error_response: format_error(code, message),
            method: None,
            path: None,
        }))
    }

    pub fn new_with_json(code: u16, message: &str) -> Self {
        ApiError(Box::new(ApiErrorImpl {
            error_response: HttpResponse::new(
                Some(Body::Json(serde_json::json!({"message": message}))),
                None,
                code,
            ),
            method: None,
            path: None,
        }))
    }

    pub fn new_with_custom(response: HttpResponse) -> Self {
        ApiError(Box::new(ApiErrorImpl {
            error_response: response,
            method: None,
            path: None,
        }))
    }

    pub fn into_response(self) -> HttpResponse {
        self.0.error_response
    }
}

impl fmt::Display for ApiError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "{:?}", self)
    }
}

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

impl From<std::io::Error> for ApiError {
    fn from(error: std::io::Error) -> Self {
        ApiError::new_with_html(500, &format!("IO Error: {}", error))
    }
}

impl From<Box<dyn std::error::Error>> for ApiError {
    fn from(error: Box<dyn std::error::Error>) -> ApiError {
        ApiError::new_with_json(500, &error.to_string())
    }
}

impl From<serde_json::Error> for ApiError {
    fn from(error: serde_json::Error) -> Self {
        ApiError::new_with_json(400, &format!("JSON Serialization Error: {}", error))
    }
}

impl From<&str> for ApiError {
    fn from(error: &str) -> Self {
        ApiError::new_with_json(400, error)
    }
}

impl From<HttpParseError> for ApiError {
    fn from(error: HttpParseError) -> Self {
        ApiError::new_with_json(
            400,
            &format!("Error parsing HTTP request: {}", error.message),
        )
    }
}

fn format_error(error_code: u16, message: &str) -> HttpResponse {
    let html = format!(
        "<!DOCTYPE html>
    <html lang=\"en\">
    <head>
        <meta charset=\"UTF-8\">
        <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\">
        <title>Error</title>
        <style>
        body {{
            display: flex;
            justify-content: center;
            align-items: center;
            height: 100vh;
            font-family: Arial, sans-serif;
        }}
        .error-container {{
            text-align: center;
        }}
        .error-container h1 {{
            font-size: 3em;
            color: #ff0000;
        }}
        .error-container p {{
            font-size: 1.5em;
        }}
    </style>
    </head>

    <body>
        <div class=\"error-container\">
            <h1>{} {}</h1>
            <p>{}</p>
        </div>
    </body>
    </html>",
        error_code,
        get_cannonical_reason(error_code),
        message
    );
    HttpResponse::new(
        Some(Body::Text(html)),
        Some(String::from("text/html")),
        error_code,
    )
}

fn get_cannonical_reason<'a>(status_code: u16) -> &'a str {
    match status_code {
        200 => "OK",
        201 => "Created",
        204 => "No Content",
        301 => "Moved Permanently",
        302 => "Found",
        304 => "Not Modified",
        400 => "Bad Request",
        401 => "Unauthorized",
        403 => "Forbidden",
        404 => "Not Found",
        405 => "Method Not Allowed",
        500 => "Internal Server Error",
        501 => "Not Implemented",
        502 => "Bad Gateway",
        503 => "Service Unavailable",
        _ => "Unknown Status Code",
    }
}