Updated error handling and made more idiomatic

+ Most unwraps removed
+ Better use of Rust constructs
This commit is contained in:
Joey Hines 2020-05-30 13:54:33 -05:00
parent e88d9d3179
commit 6733076068
3 changed files with 135 additions and 75 deletions

View File

@ -1,22 +1,26 @@
#![feature(proc_macro_hygiene, decl_macro)] #![feature(proc_macro_hygiene, decl_macro)]
#[macro_use] extern crate rocket; #[macro_use]
#[macro_use] extern crate serde_derive; extern crate rocket;
#[macro_use]
extern crate serde_derive;
mod tests;
mod rst_parser; mod rst_parser;
mod tests;
use regex::Regex;
use rocket::Request;
use rocket_contrib::serve::StaticFiles;
use rocket_contrib::templates::Template;
use rst_parser::parse_links; use rst_parser::parse_links;
use std::collections::HashMap; use std::collections::HashMap;
use rocket::Request;
use rocket_contrib::templates::Template;
use rocket_contrib::serve::StaticFiles;
use std::{fs, io};
use std::path::PathBuf;
use std::error; use std::error;
use std::fmt; use std::fmt;
use std::path::{PathBuf, Path};
use std::{fs, io};
use std::io::Error;
type PageResult<T> = std::result::Result<T, PageNotFoundError>; type PageResult<T> = std::result::Result<T, JSiteError>;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
struct PageNotFoundError; struct PageNotFoundError;
@ -33,8 +37,27 @@ impl error::Error for PageNotFoundError {
} }
} }
#[derive(Clone, Debug)]
enum JSiteError {
PageNotFound(PageNotFoundError),
IOError,
}
impl std::convert::From<PageNotFoundError> for JSiteError {
fn from(e: PageNotFoundError) -> Self {
JSiteError::PageNotFound(e)
}
}
impl std::convert::From<std::io::Error> for JSiteError {
fn from(_: Error) -> Self {
JSiteError::IOError
}
}
#[derive(Serialize)] #[derive(Serialize)]
struct SiteFile { struct SiteFile {
rank: u32,
file_name: String, file_name: String,
link_name: String, link_name: String,
path: PathBuf, path: PathBuf,
@ -63,41 +86,56 @@ fn index() -> Template {
Template::render("index", &map) Template::render("index", &map)
} }
/// Gets all the raw rst pages contained in static/raw_rst/ /// Gets all the raw rst pages contained in a directory
/// ///
/// The rst page can start with a number /// The order of the vector is determined by OS. Ordering can be set by prepending the file name
/// with a number. Files that start with lower numbers are placed earlier in the list.
/// ///
/// # Arguments /// # Arguments
/// /// * `path` - the path to look for pages in
/// * `links` - A reference to a vector of string to insert the links into /// * `pages` - A vector where found pages will be inserted
fn get_pages(path: &str, links: &mut Vec<SiteFile>) -> io::Result<()> { fn get_pages(path: &str, pages: &mut Vec<SiteFile>) -> io::Result<()> {
// Gather all of the rst files in static/raw_rst/ let re = Regex::new(r"(?P<rank>^\d*)(?P<link_name>.+)").unwrap();
let mut entries: Vec<PathBuf> = fs::read_dir(path)?
.map(|res| res.map(|e| e.path()))
.collect::<Result<Vec<_>, io::Error>>()?;
// Sort so they are always in the same order
entries.sort();
// Find all files in the directory // Find all files in the directory
for entry in entries { for entry in fs::read_dir(path)? {
let file_name = entry.file_stem().unwrap().to_str().unwrap(); let entry = entry?;
let link_name; let path = entry.path();
if file_name.chars().next().unwrap().is_numeric() { let file_name = match path.file_stem() {
link_name = &file_name[1..]; Some(name) => name,
} None => continue,
else {
link_name = file_name;
}
let rst_file = SiteFile {
file_name: String::from(file_name),
link_name: String::from(link_name),
path: entry.to_owned()
}; };
links.push(rst_file); let file_name = match file_name.to_str() {
Some(name) => name,
None => continue,
};
if let Some(caps) = re.captures(file_name) {
let link_name = &caps["link_name"];
let rank = &caps["rank"];
let rank: u32 = if rank.is_empty() {
std::u32::MAX
} else {
match rank.parse() {
Ok(r) => r,
Err(_) => std::u32::MAX
} }
};
let site_file = SiteFile {
rank,
file_name: file_name.to_string(),
link_name: link_name.to_string(),
path: entry.path(),
};
pages.push(site_file);
}
}
pages.sort_by(|a, b| a.rank.cmp(&b.rank));
Ok(()) Ok(())
} }
@ -108,39 +146,62 @@ fn get_pages(path: &str, links: &mut Vec<SiteFile>) -> io::Result<()> {
/// ///
/// * `path` - path to search in /// * `path` - path to search in
/// * `page_name` - file to look for /// * `page_name` - file to look for
fn get_page(path: &str, page_name: &str) -> Result<SiteFile, PageNotFoundError> { fn get_page(path: &Path) -> PageResult<SiteFile> {
let mut pages: Vec<SiteFile> = Vec::new(); let file_name = path.file_name().ok_or(PageNotFoundError)?;
let file_name = file_name.to_str().ok_or(PageNotFoundError)?.to_string();
if path.exists() {
return Ok(SiteFile {
rank: 0,
file_name: file_name.clone(),
link_name: file_name.clone(),
path: path.to_path_buf()
})
}
else {
let mut dir_path = path.to_path_buf();
dir_path.pop();
// Get pages for entry in dir_path.read_dir()? {
match get_pages(path, &mut pages) { let entry = entry?;
Err(_) => return Err(PageNotFoundError), let entry_name = entry.file_name().into_string().unwrap();
Ok(_) => (),
};
// Look for the page in the directory if entry_name.contains(&file_name) {
for page in pages { return Ok(SiteFile {
if page.link_name.eq_ignore_ascii_case(page_name) { rank: 0,
return Ok(page) file_name: entry_name,
link_name: file_name,
path: entry.path()
})
}
} }
} }
Err(PageNotFoundError) Err(JSiteError::from(PageNotFoundError))
}
fn error_page(page: &str) -> Template {
let mut map = HashMap::new();
map.insert("error_page", page);
return Template::render("404", map);
} }
/// Returns a rendered template of a raw rst page if it exists /// Returns a rendered template of a raw rst page if it exists
/// ///
/// # Arguments /// # Arguments
/// ///
/// * `page` - a string containing the name of the rst file to look for /// * `page` - path to page
#[get("/about/<page..>")] #[get("/about/<page..>")]
fn rst_page(page: PathBuf) -> Template { fn rst_page(page: PathBuf) -> Template {
let mut path = PathBuf::from("static/raw_rst");
path.push(page);
// Try and get the page // Try and get the page
let site_page = match get_page(format!("static/raw_rst/{}", page.parent().unwrap().to_str().unwrap()).as_str(), &page.file_name().unwrap().to_str().unwrap()) { let site_page = match get_page(
path.as_path()
) {
Ok(site_page) => site_page, Ok(site_page) => site_page,
Err(_) => { Err(_) => {
let mut map = HashMap::new(); return error_page(path.to_str().unwrap());
map.insert("error_page", page);
return Template::render("404", map)
} }
}; };
@ -149,28 +210,27 @@ fn rst_page(page: PathBuf) -> Template {
let mut map = HashMap::new(); let mut map = HashMap::new();
let mut sub_files: Vec<SiteFile> = Vec::new(); let mut sub_files: Vec<SiteFile> = Vec::new();
match get_pages(site_page.path.to_str().unwrap(), &mut sub_files) { match get_pages(site_page.path.to_str().unwrap(), &mut sub_files) {
Err(_) => (),
Ok(_) => (), Ok(_) => (),
}; Err(_) => return error_page(&site_page.link_name)
}
let page_data = PageData { let page_data = PageData {
links: sub_files, links: sub_files,
site_file: site_page site_file: site_page,
}; };
map.insert("page_data", page_data); map.insert("page_data", page_data);
return Template::render("listing", &map); return Template::render("listing", &map);
} } else {
else {
// Else, render the RST page // Else, render the RST page
let mut map = HashMap::new(); let mut map = HashMap::new();
let contents = match fs::read_to_string(site_page.path) { let contents = match fs::read_to_string(site_page.path) {
Ok(contents) => contents, Ok(contents) => contents,
Err(_) => { Err(_) => {
let mut map = HashMap::new(); let mut map = HashMap::new();
map.insert("error_page", page); map.insert("error_page", site_page.link_name);
return Template::render("404", map) return Template::render("404", map);
}, }
}; };
// Render links // Render links
@ -184,10 +244,8 @@ fn rst_page(page: PathBuf) -> Template {
map.insert("content", contents); map.insert("content", contents);
Template::render("rst_page", &map) Template::render("rst_page", &map)
} }
} }
/// Catches 404 errors and displays an error message /// Catches 404 errors and displays an error message
/// ///
/// #Arguments /// #Arguments
@ -202,11 +260,10 @@ fn not_found(req: &Request<'_>) -> Template {
Template::render("404", &map) Template::render("404", &map)
} }
/// Launches website /// Launches website
fn rocket() -> rocket::Rocket { fn rocket() -> rocket::Rocket {
rocket::ignite() rocket::ignite()
.mount("/", routes![index, rst_page], ) .mount("/", routes![index, rst_page])
.mount("/static", StaticFiles::from("static")) .mount("/static", StaticFiles::from("static"))
.attach(Template::fairing()) .attach(Template::fairing())
.register(catchers![not_found]) .register(catchers![not_found])

View File

@ -7,9 +7,9 @@ use std::collections::HashMap;
/// ///
/// * `string` - input RST string /// * `string` - input RST string
/// ///
pub fn parse_links(string: & String) -> String { pub fn parse_links(string: &String) -> String {
let re_link_ref = Regex::new(r"\n?.. _(.*): (.*)\n").unwrap(); let re_link_ref = Regex::new(r"\n?.. _(.*): (.*)\n").unwrap();
let mut link_map: HashMap::<String, String> = HashMap::new(); let mut link_map: HashMap<String, String> = HashMap::new();
for cap in re_link_ref.captures_iter(string.as_str()) { for cap in re_link_ref.captures_iter(string.as_str()) {
link_map.insert(String::from(&cap[1]), String::from(&cap[2])); link_map.insert(String::from(&cap[1]), String::from(&cap[2]));
@ -21,12 +21,13 @@ pub fn parse_links(string: & String) -> String {
for cap in re_link.captures_iter(output.clone().as_ref()) { for cap in re_link.captures_iter(output.clone().as_ref()) {
let link = match link_map.get(&cap[1]) { let link = match link_map.get(&cap[1]) {
None => String::from(""), None => String::from(""),
Some(link) => link.to_owned() Some(link) => link.to_owned(),
}; };
output = output.replace(&cap[0], output = output.replace(
format!("<a class=\"link\" href=\"{}\">{}</a>", &cap[0],
link, &cap[1]).as_str()); format!("<a class=\"link\" href=\"{}\">{}</a>", link, &cap[1]).as_str(),
);
} }
output output

View File

@ -3,9 +3,11 @@ use super::*;
#[test] #[test]
fn test_link_parser() { fn test_link_parser() {
let mut input = String::from("This is a paragraph that contains `a link`_. let mut input = String::from(
"This is a paragraph that contains `a link`_.
.. _a link: https://domain.invalida\n"); .. _a link: https://domain.invalida\n",
);
let output = parse_links(&mut input); let output = parse_links(&mut input);