macroblog.rs @ 4fb323f69c11557a51c7da0b2031029f63edf789

feat: Handle 404 result

Now gracefully handle 404, so instead of just panic now it will return a
proper http 404 response.
diff --git a/README.md b/README.md
index 59cf13b70688f24817edbebcbc7ad01a9706d9be..2a9d334be198dd7701555aba08efc1b1f009dec1 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 0000000000000000000000000000000000000000..2c39d1b71ae6e763aeb0c338f6e729173c2dee39
--- /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<date>[\d]{4}-[\d]{2}-[\d]{2})(?P<title>[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 3f00f360f48bf245b9f02814685bf6a687e7d6f5..101fe2e4dc8116e33656ba2f00ad23c5dec0d138 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 @@
 
 #[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 c87730374672bbcc6fc8158177b0229644dc5fd8..eaa314abab08acb9c96c1cf4b2e274c4da566819 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>,
-}
+    entries.sort_by(|a, b| b.datetime.cmp(&a.datetime));
 
-#[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"], "_", " ");
-
-        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 {
@@ -67,6 +23,7 @@     let mut html_output = &mut String::new();
     html::push_html(&mut html_output, parser);
     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_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 0c698884bb717efdd6fb15fbe9d622e170a3d0c7..90ddff22d138f459fba27b64fd79bb5c9951a5e8 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 35fdf3e8bed96a76bda385f0242e4757bfa06553..c196ab8419c1cf6fa71737e2083a8672ceb2251b 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>.+)";
@@ -6,6 +9,10 @@ pub enum Router {
     NotFound,
     Index,
     Post { page: String },
+}
+
+pub fn blog_post_exists(name: &str) -> bool {
+    PostAsset::iter().any(|x| name.eq(&x.to_string()))
 }
 
 impl Router {
@@ -16,6 +23,13 @@         let action = match caps {
             Some(ref value) => &value["action"],
             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 {
diff --git a/tests/test_blog.rs b/tests/test_blog.rs
index b72f80014b9514a502450953c506968c5c53360c..6cd32495f611123f689f10cf5a47aac0290b8a69 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 @@
 #[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 7ebe0196cbedddd7831d3a0ded8e62282997c056..cfd4c32bc8b356bfad745090c76a31d304614cdf 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"),
     };
 }