From c060cdc4deb3e58f7aeba75254343b08ae51e55f Mon Sep 17 00:00:00 2001 From: koehr Date: Tue, 23 Feb 2021 23:34:44 +0100 Subject: [PATCH] better documentation --- src/db.rs | 21 ++++++++++++++++++--- src/main.rs | 12 +++++++++--- src/response_types.rs | 2 ++ src/server.rs | 14 +++++++++++++- src/short_code.rs | 3 +++ 5 files changed, 45 insertions(+), 7 deletions(-) diff --git a/src/db.rs b/src/db.rs index 10483c2..e10ea1e 100644 --- a/src/db.rs +++ b/src/db.rs @@ -7,8 +7,11 @@ use rusqlite::NO_PARAMS; use super::short_code::{random_uuid, ShortCode}; +/// generalized Result type using failure to wrap different error types +/// and DBValue to wrap the possible result types type Result = std::result::Result; +/// Error type to wrap database or sqlite related errors #[derive(Debug, Fail)] pub enum DBError { #[fail(display = "The loaded database has not been initialized.")] @@ -18,6 +21,8 @@ pub enum DBError { SqliteError { msg: String, src: rusqlite::Error }, } +/// Result type to wrap database return values, +/// can be String, Number(i64) or None #[derive(Debug)] pub enum DBValue { String(String), @@ -29,6 +34,7 @@ pub enum DBValue { pub type Pool = r2d2::Pool; pub type Connection = r2d2::PooledConnection; +/// Describes the expected structure for posting new URLs #[derive(serde::Deserialize)] pub struct UrlPostData { pub url: String, @@ -37,6 +43,7 @@ pub struct UrlPostData { pub key: String, } +/// Possible database queries, used with db::query pub enum Queries { NeedsInit, CountUsers, @@ -46,6 +53,10 @@ pub enum Queries { StoreNewURL(UrlPostData), // api_key, url, title?, description? } +/// Queries the database schema and creates a descriptive string like +/// table1|field1 +/// table1|field2 +/// table2|field1 fn get_database_schema(conn: &Connection) -> Result { let mut stmt = conn.prepare( " @@ -74,6 +85,7 @@ fn get_database_schema(conn: &Connection) -> Result { Ok(schema) } +/// compares a hard coded expected database schema with the actual one fn check_database_schema(conn: Connection) -> Result { // TODO: is that really a good way to check the schema? let expected_schema = String::from( @@ -130,6 +142,7 @@ fn init_database(conn: Connection) -> Result { }) } +/// counts entries in Users table and returns that count as DBValue::Number fn count_users(conn: Connection) -> Result { conn.query_row("SELECT COUNT(rowid) FROM USERS", NO_PARAMS, |row| { row.get(0) @@ -141,7 +154,8 @@ fn count_users(conn: Connection) -> Result { }) } -/// Creates a user entry with random API key and returns the API key. +/// Creates a new user entry with random API key +/// and returns the API key as DBValue::String fn create_user(conn: Connection, rate_limit: i64, is_admin: bool) -> Result { let new_key = random_uuid(); let is_admin = if is_admin { "1" } else { "0" }; @@ -157,6 +171,7 @@ fn create_user(conn: Connection, rate_limit: i64, is_admin: bool) -> Result { } /// Looks up an URL by translating the short_code to its ID +/// short_code is simply the base36 version of the table id fn get_url(conn: Connection, short_code: &str) -> Result { let row_id = ShortCode::from_code(short_code)?.n; @@ -165,7 +180,7 @@ fn get_url(conn: Connection, short_code: &str) -> Result { &[row_id as i64], |row| row.get(0), ) - .map(|url| DBValue::String(url)) + .map(DBValue::String) .map_err(|src| { let msg = "Could not retrieve URL".to_owned(); Error::from(DBError::SqliteError { msg, src }) @@ -197,7 +212,7 @@ fn store_url(conn: Connection, data: &UrlPostData) -> Result { Ok(DBValue::String(short_code)) } -/// translates Queries to function calls and returns the result +/// translates Queries to function calls and returns the result as Future pub fn query( pool: &Pool, query: Queries, diff --git a/src/main.rs b/src/main.rs index 64b7e77..b39ced2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,15 +18,19 @@ use db::DBValue; // This includes the template code generated by ructe include!(concat!(env!("OUT_DIR"), "/templates.rs")); -fn prompt_or_exit(msg: &str) { +/// Prompts and stops process on negative response. +fn prompt_or_exit(msg: &str, err_msg: &str) { println!("{}", msg); let input: String = read!("{}\n"); if input != "y" && input != "Y" { - error!("DB not created. Exiting."); + error!("{}", err_msg); std::process::exit(exitcode::CANTCREAT); } } +/// Builds the database path with some heuristics, +/// for example /foo/ becomes /foo/k0r.db, +/// and checks for its existence. If non-existent, a prompt asks for a decision fn build_db_path(path_str: &str) -> PathBuf { let mut db_path = PathBuf::from(path_str); @@ -38,12 +42,14 @@ fn build_db_path(path_str: &str) -> PathBuf { if !db_path.is_file() { let msg = format!("Database file {} not found. Create it? [y/N]", path_str); - prompt_or_exit(&msg); + prompt_or_exit(&msg, "DB not created. Exiting."); } db_path } +/// Initializes the database connection and pool. +/// The database is configured for performance. async fn init_db_pool(path_str: String) -> db::Pool { let db_path = build_db_path(&path_str); diff --git a/src/response_types.rs b/src/response_types.rs index 5a4c97f..73b01f7 100644 --- a/src/response_types.rs +++ b/src/response_types.rs @@ -5,6 +5,8 @@ use actix_web::{web::HttpResponse, ResponseError}; use serde::Serialize; use serde_json::{json, to_string_pretty}; +/// Error http response with the status code and a generic message. +/// Implements everything necessary to be consumed by actix-web. #[derive(Debug, Serialize)] pub struct Error { pub status: u16, diff --git a/src/server.rs b/src/server.rs index 8050209..a25e21d 100644 --- a/src/server.rs +++ b/src/server.rs @@ -33,6 +33,8 @@ fn get_request_origin(req: &HttpRequest) -> String { /// Index page handler +/// `GET /` +/// returns the static template from templates/index.rs.html #[actix_web::get("/")] async fn index() -> HttpResponse { HttpResponse::Ok() @@ -41,7 +43,8 @@ async fn index() -> HttpResponse { } /// Handler for static files. -/// Create a response from the file data with a correct content type +/// `GET /static/favicon.ico` +/// Creates a response from the file data with a correct content type /// and a far expires header (or a 404 if the file does not exist). #[actix_web::get("/static/{filename}")] fn static_file(path: web::Path) -> HttpResponse { @@ -60,6 +63,7 @@ fn static_file(path: web::Path) -> HttpResponse { } /// Shortcode handler +/// `GET /1z5` /// Asks the database for the URL matching short_code and responds /// with a redirect or, if not found, a JSON error #[actix_web::get("/{short_code}")] @@ -96,6 +100,13 @@ async fn redirect(req: HttpRequest, db: DB) -> Result { } } +/// URL Post Handler +/// POST / -H 'Content-Type: application/json' -d $payload +/// where $payload is a JSON object with the keys: +/// url: the URL to shorten, +/// title: an optional title for the URL, defaults to empty string, +/// description: an optional description for the URL, defaults to empty string, +/// key: the API key #[actix_web::post("/")] async fn add_url(_req: HttpRequest, data: JSON, db: DB) -> Result { match Url::parse(&data.url) { @@ -136,6 +147,7 @@ async fn add_url(_req: HttpRequest, data: JSON, db: DB) -> Result std::io::Result<()> { println!("Server is listening on 127.0.0.1:8080"); diff --git a/src/short_code.rs b/src/short_code.rs index 8a55bfb..d8d0e8e 100644 --- a/src/short_code.rs +++ b/src/short_code.rs @@ -2,6 +2,9 @@ use radix_fmt::radix_36; use std::num::ParseIntError; use uuid::Uuid; +/// Describes a short code and its numerical value. +/// Can be created either directly from a number: `ShortCode::new(1234)` +/// or from a &str: `ShortCode::from_code("1z5")` pub struct ShortCode { pub code: String, pub n: usize, -- 2.45.2