index : license-api-rs.git

ascending towards madness

use async_std::{
    fs,
    path::{Path, PathBuf},
};
use data::LicenseDatabase;
use tide::{http::mime, log, Response, Result, Server, StatusCode};

use crate::{
    config::{RunningConfig, ServerConfig},
    data::Queryable,
};

mod config;
mod data;

const ERROR_LICENSE_NOT_FOUND: &str =
    " is undefined. use the 'list' endpoint to see defined licenses.";

#[async_std::main]
async fn main() -> Result<()> {
    log::start();

    match <ServerConfig as jealousy::FromEnv>::from_env() {
        Ok(config) => {
            // TODO: fix the borrow issue here
            let run_config = RunningConfig {
                config: config.clone(),
                database: LicenseDatabase::load(config.database.clone()),
            };

            let mut app = tide::with_state(run_config);

            create_routes(&mut app);

            app.listen(format!("{}:{}", config.server_ip, config.server_port))
                .await?;
        }
        Err(_) => (), // jealousy prints its own error message so this is safe to ignore...i think
    };

    Ok(())
}

fn create_routes(server: &mut Server<RunningConfig>) {
    // TODO: figure out how to not need clone
    let url = server.clone().state().config.base_url.clone();
    let mut base_route = server.at(&url);
    base_route.get(get_welcome_message);

    base_route.at(":license").get(get_requested_license);
    base_route.at("list").get(get_license_list);
    base_route.at("list/full").get(get_detailed_license_list);

    // nyaaaaa whyyyy https://github.com/http-rs/tide/issues/205
    base_route.at("").get(get_welcome_message);
    base_route.at("list/").get(get_license_list);
    base_route.at("list/full/").get(get_detailed_license_list);
}

fn build_response(status: StatusCode, data: String) -> Response {
    // this is a plain text only service
    let response = Response::builder(status)
        .body(format!("{}\n", data))
        .content_type(mime::PLAIN)
        .build();

    response
}

async fn get_welcome_message(req: tide::Request<RunningConfig>) -> tide::Result<Response> {
    log::info!("{:?}", req);

    Ok(build_response(
        StatusCode::Ok,
        format!("{}", "Welcome message goes here").to_string(),
    ))
}

async fn get_license_list(req: tide::Request<RunningConfig>) -> tide::Result<Response> {
    log::info!("{:?}", req);

    // TODO: env var the chunks
    match req.state().database.get_licenses(5) {
        Some(licenses) => Ok(build_response(StatusCode::Ok, format!("{}", licenses))),
        None => Ok(build_response(
            StatusCode::NotFound,
            String::from("License not found"),
        )),
    }
}

async fn get_detailed_license_list(req: tide::Request<RunningConfig>) -> tide::Result<Response> {
    log::info!("{:?}", req);

    match req.state().database.get_detailed_licenses() {
        Some(licenses) => Ok(build_response(StatusCode::Ok, format!("{}", licenses))),
        None => Ok(build_response(
            StatusCode::NotFound,
            String::from("No licenses found."),
        )),
    }
}

async fn get_requested_license(req: tide::Request<RunningConfig>) -> tide::Result<Response> {
    log::info!("{:?}", req);

    // remove prefix from requested route
    // TODO: make this better
    let request = req.url().path().strip_prefix("/").unwrap_or("");

    let response = match req.state().database.get_license(request) {
        Some(license) => {
            // is there a better way to do this?
            let source_dir = PathBuf::from(req.state().config.database.clone());
            let source_dir = source_dir.parent().unwrap_or(Path::new(""));

            let mut license_file_path = PathBuf::from(source_dir);
            license_file_path.push(license.filename);

            match fs::read_to_string(&license_file_path).await {
                Ok(contents) => {
                    build_response(StatusCode::Accepted, contents.trim_end().to_string())
                }
                Err(_) => {
                    log::error!(
                        "Unable to read `{}`.",
                        license_file_path.to_string_lossy().to_string()
                    );

                    build_response(
                        StatusCode::NotFound,
                        format!("ERROR - `{}`{}", request, ERROR_LICENSE_NOT_FOUND).to_string(),
                    )
                }
            }
        }
        None => {
            log::error!("License not found: `{}`", request);

            build_response(
                StatusCode::NotFound,
                format!("ERROR - `{}`{}", request, ERROR_LICENSE_NOT_FOUND).to_string(),
            )
        }
    };

    Ok(response)
}