From 4fb323f69c11557a51c7da0b2031029f63edf789 Mon Sep 17 00:00:00 2001 From: "Gabriel A. Giovanini" Date: Sat, 11 Jun 2022 00:00:27 +0200 Subject: feat: Handle 404 result Now gracefully handle 404, so instead of just panic now it will return a proper http 404 response. --- README.md | 2 +- src/assets.rs | 49 ++++++++++++++++++++++++++++++++++++++++ src/bin/actix.rs | 9 +++++++- src/blog.rs | 63 +++++++++------------------------------------------- src/lib.rs | 1 + src/router.rs | 14 ++++++++++++ tests/test_blog.rs | 3 ++- tests/test_router.rs | 19 +++++++++++----- 8 files changed, 99 insertions(+), 61 deletions(-) create mode 100644 src/assets.rs diff --git a/README.md b/README.md index 59cf13b..2a9d334 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ After reading [this article](https://www.andreinc.net/2022/04/10/a-blog-that-is-a-single-executable-binary) by Andrei Ciobanu it sparkled in me to do the same thing but with rust. -It is going to be a bit bigger than micro though ;) +It is going to be a bit bigger than micro ;) To achieve that I'll be using the following: diff --git a/src/assets.rs b/src/assets.rs new file mode 100644 index 0000000..2c39d1b --- /dev/null +++ b/src/assets.rs @@ -0,0 +1,49 @@ +use chrono::NaiveDate; +use regex::Regex; +use rust_embed::RustEmbed; +use sailfish::TemplateOnce; +use std::cmp::{Eq, Ord, PartialEq, PartialOrd}; +use std::str; + +pub const BLOG_REGEX: &str = r"(?P[\d]{4}-[\d]{2}-[\d]{2})(?P[a-zA-Z0-9-_]*)"; + +#[derive(RustEmbed)] +#[folder = "content/posts/"] +pub struct PostAsset; + +#[derive(TemplateOnce)] +#[template(path = "index.html")] +pub struct IndexTemplate { + pub posts: Vec<BlogEntry>, +} + +#[derive(TemplateOnce)] +#[template(path = "post.html")] +pub struct PostTemplate { + pub content: String, + pub title: String, + pub date: String, +} + +#[derive(PartialEq, Eq, PartialOrd, Ord)] +pub struct BlogEntry { + pub title: String, + pub datetime: NaiveDate, + pub file: String, +} + +impl BlogEntry { + pub fn new(path: &String) -> BlogEntry { + let re = Regex::new(BLOG_REGEX).unwrap(); + let caps = re.captures(path).unwrap(); + let date = &caps["date"]; + let title = str::replace(&caps["title"], "_", " "); + + BlogEntry { + title: String::from(title), + file: String::from(path), + datetime: NaiveDate::parse_from_str(date, "%Y-%m-%d").unwrap(), + } + } + +} diff --git a/src/bin/actix.rs b/src/bin/actix.rs index 3f00f36..101fe2e 100644 --- a/src/bin/actix.rs +++ b/src/bin/actix.rs @@ -1,6 +1,7 @@ use actix_web::{get, web, middleware, App, HttpResponse, HttpServer, Responder, http::header::ContentType}; use macroblog::blog::{render_index_page, render_post_page}; -use std::{env}; +use macroblog::router::blog_post_exists; +use std::env; #[get("/")] async fn index() -> impl Responder { @@ -14,6 +15,12 @@ async fn index() -> impl Responder { #[get("/posts/{name}")] async fn posts(name: web::Path<String>) -> impl Responder { + + if !blog_post_exists(&name) { + return HttpResponse::NotFound() + .body("Not Found".to_string()); + } + let body = render_post_page(&name); HttpResponse::Ok() diff --git a/src/blog.rs b/src/blog.rs index c877303..eaa314a 100644 --- a/src/blog.rs +++ b/src/blog.rs @@ -1,62 +1,18 @@ -use chrono::NaiveDate; use pulldown_cmark::{html, Options, Parser}; -use regex::Regex; -use rust_embed::RustEmbed; use sailfish::TemplateOnce; -use std::cmp::{Eq, Ord, PartialEq, PartialOrd}; use std::str; +use crate::assets::{BlogEntry, PostAsset, IndexTemplate, PostTemplate}; -const BLOG_REGEX: &str = r"(?P<date>[\d]{4}-[\d]{2}-[\d]{2})(?P<title>[a-zA-Z0-9-_]*)"; -#[derive(RustEmbed)] -#[folder = "content/posts/"] -struct PostAsset; +pub fn read_assets() -> Vec<BlogEntry> { + let mut entries: Vec<BlogEntry> = PostAsset::iter() + .map(|e| format!("{}", e)) + .map(|e| BlogEntry::new(&e)) + .collect(); -#[derive(TemplateOnce)] -#[template(path = "index.html")] -struct IndexTemplate { - posts: Vec<BlogEntry>, -} - -#[derive(TemplateOnce)] -#[template(path = "post.html")] -struct PostTemplate { - content: String, - title: String, - date: String, -} - -#[derive(PartialEq, Eq, PartialOrd, Ord)] -pub struct BlogEntry { - pub title: String, - pub datetime: NaiveDate, - pub file: String, -} - -impl BlogEntry { - pub fn new(path: &String) -> BlogEntry { - let re = Regex::new(BLOG_REGEX).unwrap(); - let caps = re.captures(path).unwrap(); - let date = &caps["date"]; - let title = str::replace(&caps["title"], "_", " "); + entries.sort_by(|a, b| b.datetime.cmp(&a.datetime)); - BlogEntry { - title: String::from(title), - file: String::from(path), - datetime: NaiveDate::parse_from_str(date, "%Y-%m-%d").unwrap(), - } - } - - pub fn read_assets() -> Vec<BlogEntry> { - let mut entries: Vec<BlogEntry> = PostAsset::iter() - .map(|e| format!("{}", e)) - .map(|e| BlogEntry::new(&e)) - .collect(); - - entries.sort_by(|a, b| b.datetime.cmp(&a.datetime)); - - entries - } + entries } fn get_file_content(path: &str) -> String { @@ -68,6 +24,7 @@ fn get_file_content(path: &str) -> String { return html_output.to_string(); } + pub fn render_post_page(path: &String) -> String { let blog = BlogEntry::new(path); @@ -82,7 +39,7 @@ pub fn render_post_page(path: &String) -> String { pub fn render_index_page() -> String { IndexTemplate { - posts: BlogEntry::read_assets(), + posts: read_assets(), } .render_once() .unwrap() diff --git a/src/lib.rs b/src/lib.rs index 0c69888..90ddff2 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,2 +1,3 @@ pub mod blog; pub mod router; +pub mod assets; diff --git a/src/router.rs b/src/router.rs index 35fdf3e..c196ab8 100644 --- a/src/router.rs +++ b/src/router.rs @@ -1,3 +1,6 @@ +use std::borrow::Borrow; + +use crate::assets::PostAsset; use regex::Regex; const ACTION_REGEX: &str = r"/{0,1}(?P<action>\w*)/(?P<id>.+)"; @@ -8,6 +11,10 @@ pub enum Router { Post { page: String }, } +pub fn blog_post_exists(name: &str) -> bool { + PostAsset::iter().any(|x| name.eq(&x.to_string())) +} + impl Router { pub fn new(path: &str) -> Router { let re = Regex::new(ACTION_REGEX).unwrap(); @@ -17,6 +24,13 @@ impl Router { None => "index", }; + + // this 7 means the "/posts/" from the full path + let trimmed_path: String = path.chars().skip(7).collect(); + if action.eq("posts") && !blog_post_exists(&trimmed_path) { + return Router::NotFound; + } + match action { "posts" => Router::Post { page: caps.unwrap()["id"].to_string(), diff --git a/tests/test_blog.rs b/tests/test_blog.rs index b72f800..6cd3249 100644 --- a/tests/test_blog.rs +++ b/tests/test_blog.rs @@ -1,4 +1,5 @@ use macroblog::blog::*; +use macroblog::assets::*; use chrono::NaiveDate; @@ -17,7 +18,7 @@ fn test_create_blog_entry() { #[test] fn test_read_assets() { // This test meant to test if all files are parsed correctly - let assets = BlogEntry::read_assets(); + let assets = read_assets(); assert!(assets.iter().count() > 1) } diff --git a/tests/test_router.rs b/tests/test_router.rs index 7ebe019..cfd4c32 100644 --- a/tests/test_router.rs +++ b/tests/test_router.rs @@ -1,11 +1,11 @@ -use macroblog::router::{Router}; +use macroblog::router::Router; #[test] fn test_router_new_posts() { - match Router::new("/posts/k8s.html") { + match Router::new("/posts/2021-12-26Enable_NFS_on_K3S.md") { Router::NotFound => assert!(false, "Wrong type parse"), Router::Index => assert!(false, "Wrong type parse"), - Router::Post { page } => assert_eq!(page, "k8s.html".to_string()) + Router::Post { page } => assert_eq!(page, "2021-12-26Enable_NFS_on_K3S.md".to_string()), }; } @@ -14,7 +14,7 @@ fn test_router_new_index() { match Router::new("/") { Router::Index => assert!(true), Router::NotFound => assert!(false, "Wrong type parse"), - Router::Post { page: _ } => assert!(false, "Wrong type parse") + Router::Post { page: _ } => assert!(false, "Wrong type parse"), }; } @@ -23,6 +23,15 @@ fn test_router_new_not_found() { match Router::new("/not_found") { Router::NotFound => assert!(true), Router::Index => assert!(false, "Wrong type parse"), - Router::Post { page: _ } => assert!(false, "Wrong type parse") + Router::Post { page: _ } => assert!(false, "Wrong type parse"), + }; +} + +#[test] +fn test_router_new_not_found_matching_regex() { + match Router::new("/posts/2021-12-03Enable_NFS_on_K3S.html") { + Router::NotFound => assert!(true), + Router::Index => assert!(false, "Wrong type parse"), + Router::Post { page: _ } => assert!(false, "Wrong type parse"), }; } -- cgit v1.2.3