bravery 0.3.0

A fast http async web server
Documentation
extern crate serde;
#[macro_use]
extern crate serde_json;

#[macro_use]
extern crate struct2swagger;
#[macro_use]
extern crate struct2swagger_derive;

#[macro_use]
extern crate serde_derive;

#[macro_use]
extern crate slog;

use bravery::{error_500, App, EmptyState, Handler, HttpError, Request, Response};
use std::collections::HashMap;
use std::env;
use std::fs;
use std::net::SocketAddr;
use struct2swagger::{
    swagger_object::{ServerObject, SwaggerObject},
    JsonSchemaDefinition,
};

#[derive(Serialize, Swagger)]
struct JsonStruct {
    message: String,
}

#[derive(Clone)]
struct TestHandler {}
impl Handler<EmptyState> for TestHandler {
    fn invoke(&self, _req: Request<EmptyState>) -> Result<Response, HttpError> {
        let json = JsonStruct {
            message: "Hello, World!".to_string(),
        };

        let val = serde_json::to_string(&json)
            .map_err(error_500("Unable to serialize"))?
            .into_bytes();

        Ok(Response {
            status_code: 200,
            content_type: Some("application/json".to_string()),
            body: val,
            headers: HashMap::new(),
        })
    }
}

#[derive(Clone)]
struct SwaggerHandler {
    swagger_object: SwaggerObject,
}
impl Handler<EmptyState> for SwaggerHandler {
    fn invoke(&self, _req: Request<EmptyState>) -> Result<Response, HttpError> {
        let val = serde_json::to_string(&self.swagger_object)
            .map_err(error_500("Unable to serialize"))?
            .into_bytes();

        Ok(Response {
            status_code: 200,
            content_type: Some("application/json".to_string()),
            body: val,
            headers: HashMap::new(),
        })
    }
}

fn get_content_type_from_extension(extension: String) -> Option<String> {
    match extension.as_ref() {
        "png" => Some("image/png".to_owned()),
        "json" => Some("application/json".to_owned()),
        "map" => Some("application/json".to_owned()),
        _ => None,
    }
}

#[derive(Clone)]
struct ServeStaticFile {
    path_fs: String,
    mount_path: String,
}
impl Handler<EmptyState> for ServeStaticFile {
    fn invoke(&self, req: Request<EmptyState>) -> Result<Response, HttpError> {
        let mut request_path = req.path.clone();
        if request_path.ends_with('/') {
            request_path += "index.html";
        }

        request_path.replace_range(0..self.mount_path.len(), &self.path_fs);
        info!(req.logger, "path"; "req_path" => req.path, "fs_path" => &request_path);

        let extension_position = &request_path.rfind('.');
        let content_type = match extension_position {
            Some(pos) => {
                let mut extension = request_path.clone();
                extension.replace_range(0..=*pos, "");
                info!(req.logger, "path"; "extension" => &extension);
                get_content_type_from_extension(extension)
            }
            None => None,
        };
        let bytes = fs::read(request_path).map_err(error_500("Unable to find path"))?;
        info!(req.logger, "bytes"; "bytes" => bytes.len());

        Ok(Response {
            status_code: 200,
            content_type,
            body: bytes,
            headers: HashMap::new(),
        })
    }
}

fn get_app() -> App<EmptyState> {
    let mut swagger_object =
        struct2swagger::swagger_object::SwaggerObject::new("Swagger example", "1.0.0");
    swagger_object.servers = Some(vec![ServerObject {
        url: "http://localhost:8880".to_string(),
        description: None,
        variables: None,
    }]);

    let mut app: App<EmptyState> = Default::default();

    app.get("/", Box::new(TestHandler {}));
    swagger_add_router!(swagger_object, "GET", "/", 200, "the say!", JsonStruct);

    app.get(
        "/swagger-ui*",
        Box::new(ServeStaticFile {
            path_fs: "./examples/swagger-ui-dist".to_string(),
            mount_path: "/swagger-ui".to_string(),
        }),
    );
    app.get("/openapi.json", Box::new(SwaggerHandler { swagger_object }));

    app
}

fn main() -> Result<(), Box<dyn std::error::Error>> {
    let addr = env::args()
        .nth(1)
        .unwrap_or_else(|| "127.0.0.1:8880".to_string());
    let addr = addr.parse::<SocketAddr>()?;

    get_app().run(addr)?;

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn swagger() {
        let app = get_app();

        let request = app.create_request("GET", "/", "", b"".to_vec());
        let response = app.inject(request);

        assert_eq!(response.status_code, 200);
        assert_eq!(
            response.body,
            serde_json::to_string(&JsonStruct {
                message: "Hello, World!"
            })
            .unwrap()
        );
    }
}