use async_std::{
fs,
path::{Path, PathBuf},
};
use data::LicenseDatabase;
use jealousy::FromEnv;
use serde::Deserialize;
use std::{net::Ipv4Addr, str::FromStr};
use tide::{http::mime, log, Response, Result, Server, StatusCode};
use crate::data::{License, Queryable};
mod data;
#[macro_use]
extern crate lazy_static;
#[derive(Debug, Deserialize, FromEnv)]
#[from_env(prefix = "LICENSE_API")]
struct Config {
#[serde(default = "Config::default_base_url")]
base_url: String,
database: String,
#[serde(default = "Config::default_server_ip")]
server_ip: Ipv4Addr,
#[serde(default = "Config::default_server_port")]
server_port: u16,
}
impl Config {
fn default_base_url() -> String {
String::from("/")
}
fn default_server_ip() -> Ipv4Addr {
Ipv4Addr::new(0, 0, 0, 0)
}
fn default_server_port() -> u16 {
8080
}
}
lazy_static! {
#[derive (Debug)]
static ref DATABASE: LicenseDatabase = {
let config = Config::from_env().unwrap();
let db_path: std::path::PathBuf = std::path::PathBuf::from_str(&config.database).unwrap();
let database: LicenseDatabase = LicenseDatabase::load_file(db_path.as_path());
database
};
}
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 Config::from_env() {
Ok(config) => {
let mut app = tide::new();
create_routes(&mut app, &config);
app.listen(format!("{}:{}", config.server_ip, config.server_port))
.await?;
}
Err(_) => (),
};
Ok(())
}
fn create_routes(server: &mut Server<()>, config: &Config) {
let mut base_route = server.at(&config.base_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);
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 {
let response = Response::builder(status)
.body(format!("{}\n", data))
.content_type(mime::PLAIN)
.build();
response
}
async fn get_welcome_message(req: tide::Request<()>) -> 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<()>) -> tide::Result<Response> {
log::info!("{:?}", req);
let licenses: String = DATABASE
.licenses
.iter()
.map(|license| license.id.clone())
.collect::<Vec<String>>()
.chunks(5)
.map(|chunk| chunk.join(", "))
.collect::<Vec<String>>()
.join("\n");
Ok(build_response(
StatusCode::Ok,
format!("{}", licenses).to_string(),
))
}
async fn get_detailed_license_list(req: tide::Request<()>) -> tide::Result<Response> {
log::info!("{:?}", req);
const PADDING: usize = 20;
let licenses: String = DATABASE
.licenses
.iter()
.map(|license| {
format!(
"{:padding$}{}",
license.id,
license.description,
padding = PADDING
)
})
.collect::<Vec<String>>()
.join("\n");
Ok(build_response(
StatusCode::Ok,
format!("{}", licenses).to_string(),
))
}
async fn get_requested_license(req: tide::Request<()>) -> tide::Result<Response> {
log::info!("{:?}", req);
let config = Config::from_env().unwrap();
let request = req.url().path().strip_prefix("/").unwrap_or("");
let response = match DATABASE.get_license(request) {
Some(license) => {
let source_dir = PathBuf::from(&config.database);
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)
}