♻️ Move Markdown previewing to its own file

main
_ 2020-11-29 22:56:25 +00:00
parent ab95485d92
commit 6e6e062c51
4 changed files with 88 additions and 79 deletions

View File

@ -1,14 +1,5 @@
use thiserror::Error; use thiserror::Error;
#[derive (Debug, Error, PartialEq)]
pub enum MarkdownError {
#[error ("File is too big to process")]
TooBig,
#[error ("File is not UTF-8")]
NotUtf8,
}
#[derive (Debug, Error)] #[derive (Debug, Error)]
pub enum FileServerError { pub enum FileServerError {
#[error ("Handlebars render error")] #[error ("Handlebars render error")]
@ -29,8 +20,8 @@ pub enum FileServerError {
#[error ("File path is not UTF-8")] #[error ("File path is not UTF-8")]
FilePathNotUtf8, FilePathNotUtf8,
#[error ("Markdown error")] //#[error ("Markdown error")]
Markdown (#[from] MarkdownError), //Markdown (#[from] super::markdown::Error),
#[error ("Invalid URI")] #[error ("Invalid URI")]
InvalidUri (#[from] http::uri::InvalidUri), InvalidUri (#[from] http::uri::InvalidUri),

View File

@ -0,0 +1,56 @@
#[derive (Debug, thiserror::Error, PartialEq)]
pub enum Error {
#[error ("File is too big to preview as Markdown")]
TooBig,
#[error ("File is not UTF-8")]
NotUtf8,
}
fn render (bytes: &[u8], out: &mut String) -> Result <(), Error> {
use pulldown_cmark::{Parser, Options, html};
let markdown_input = match std::str::from_utf8 (bytes) {
Err (_) => return Err (Error::NotUtf8),
Ok (x) => x,
};
let mut options = Options::empty ();
options.insert (Options::ENABLE_STRIKETHROUGH);
let parser = Parser::new_ext (markdown_input, options);
html::push_html (out, parser);
Ok (())
}
pub fn render_styled (bytes: &[u8]) -> Result <String, Error> {
// Write to String buffer.
let mut out = String::new ();
out.push_str ("<body style=\"font-family: sans-serif;\">");
render (bytes, &mut out)?;
out.push_str ("</body>");
Ok (out)
}
#[cfg (test)]
mod tests {
#[test]
fn markdown () {
use super::*;
for (input, expected) in vec! [
("", ""),
(
"Hello world, this is a ~~complicated~~ *very simple* example.",
"<p>Hello world, this is a <del>complicated</del> <em>very simple</em> example.</p>\n"
),
].into_iter () {
let mut out = String::default ();
render (input.as_bytes (), &mut out).expect ("Markdown sample failed");
assert_eq! (expected, &out);
}
}
}

View File

@ -49,12 +49,11 @@ use ptth_core::{
}; };
pub mod errors; pub mod errors;
mod markdown;
mod range; mod range;
use errors::{ use errors::FileServerError;
FileServerError, use markdown::render_styled;
MarkdownError,
};
mod emoji { mod emoji {
pub const VIDEO: &str = "\u{1f39e}\u{fe0f}"; pub const VIDEO: &str = "\u{1f39e}\u{fe0f}";
@ -316,46 +315,6 @@ async fn serve_file (
Ok (response) Ok (response)
} }
fn serve_error (
status_code: StatusCode,
msg: &str
)
-> Response
{
let mut resp = Response::default ();
resp.status_code (status_code);
resp.body_bytes (msg.as_bytes ().to_vec ());
resp
}
fn render_markdown (bytes: &[u8], out: &mut String) -> Result <(), MarkdownError> {
use pulldown_cmark::{Parser, Options, html};
let markdown_input = match std::str::from_utf8 (bytes) {
Err (_) => return Err (MarkdownError::NotUtf8),
Ok (x) => x,
};
let mut options = Options::empty ();
options.insert (Options::ENABLE_STRIKETHROUGH);
let parser = Parser::new_ext (markdown_input, options);
html::push_html (out, parser);
Ok (())
}
fn render_markdown_styled (bytes: &[u8]) -> Result <String, MarkdownError> {
// Write to String buffer.
let mut out = String::new ();
out.push_str ("<body style=\"font-family: sans-serif;\">");
render_markdown (bytes, &mut out)?;
out.push_str ("</body>");
Ok (out)
}
// Sort of an internal API endpoint to make testing work better. // Sort of an internal API endpoint to make testing work better.
// Eventually we could expose this as JSON or Msgpack or whatever. For now // Eventually we could expose this as JSON or Msgpack or whatever. For now
// it's just a Rust struct that we can test on without caring about // it's just a Rust struct that we can test on without caring about
@ -387,7 +346,7 @@ enum InternalResponse {
ServeDir (ServeDirParams), ServeDir (ServeDirParams),
ServeFile (ServeFileParams), ServeFile (ServeFileParams),
MarkdownErr (MarkdownError), MarkdownErr (markdown::Error),
MarkdownPreview (String), MarkdownPreview (String),
} }
@ -451,14 +410,17 @@ async fn internal_serve_file (
} }
if file_len > MAX_BUF_SIZE.into () { if file_len > MAX_BUF_SIZE.into () {
InternalResponse::MarkdownErr (MarkdownError::TooBig) InternalResponse::MarkdownErr (markdown::Error::TooBig)
} }
else { else {
let mut buffer = vec! [0_u8; MAX_BUF_SIZE.try_into ().expect ("Couldn't fit u32 into usize")]; let mut buffer = vec! [0_u8; MAX_BUF_SIZE.try_into ().expect ("Couldn't fit u32 into usize")];
let bytes_read = file.read (&mut buffer).await?; let bytes_read = file.read (&mut buffer).await?;
buffer.truncate (bytes_read); buffer.truncate (bytes_read);
InternalResponse::MarkdownPreview (render_markdown_styled (&buffer)?) match render_styled (&buffer) {
Ok (x) => InternalResponse::MarkdownPreview (x),
Err (x) => InternalResponse::MarkdownErr (x),
}
} }
} }
else { else {
@ -565,6 +527,18 @@ pub async fn serve_all (
{ {
use InternalResponse::*; use InternalResponse::*;
fn serve_error <S: Into <Vec <u8>>> (
status_code: StatusCode,
msg: S
)
-> Response
{
let mut resp = Response::default ();
resp.status_code (status_code);
resp.body_bytes (msg.into ());
resp
}
Ok (match internal_serve_all (root, method, uri, headers, hidden_path).await? { Ok (match internal_serve_all (root, method, uri, headers, hidden_path).await? {
Favicon => serve_error (StatusCode::NotFound, ""), Favicon => serve_error (StatusCode::NotFound, ""),
Forbidden => serve_error (StatusCode::Forbidden, "403 Forbidden"), Forbidden => serve_error (StatusCode::Forbidden, "403 Forbidden"),
@ -594,10 +568,15 @@ pub async fn serve_all (
send_body, send_body,
range, range,
}) => serve_file (file.into_inner (), send_body, range).await?, }) => serve_file (file.into_inner (), send_body, range).await?,
MarkdownErr (e) => match e { MarkdownErr (e) => {
MarkdownError::TooBig => serve_error (StatusCode::InternalServerError, "File is too big to preview as Markdown"), use markdown::Error::*;
//MarkdownError::NotMarkdown => serve_error (StatusCode::BadRequest, "File is not Markdown"), let code = match &e {
MarkdownError::NotUtf8 => serve_error (StatusCode::BadRequest, "File is not UTF-8"), TooBig => StatusCode::InternalServerError,
//NotMarkdown => serve_error (StatusCode::BadRequest, "File is not Markdown"),
NotUtf8 => StatusCode::BadRequest,
};
serve_error (code, e.to_string ())
}, },
MarkdownPreview (s) => serve_html (s), MarkdownPreview (s) => serve_html (s),
}) })

View File

@ -181,20 +181,3 @@ fn file_server () {
fn parse_uri () { fn parse_uri () {
assert! (http::Uri::from_maybe_shared ("/").is_ok ()); assert! (http::Uri::from_maybe_shared ("/").is_ok ());
} }
#[test]
fn markdown () {
use super::*;
for (input, expected) in vec! [
("", ""),
(
"Hello world, this is a ~~complicated~~ *very simple* example.",
"<p>Hello world, this is a <del>complicated</del> <em>very simple</em> example.</p>\n"
),
].into_iter () {
let mut out = String::default ();
render_markdown (input.as_bytes (), &mut out).expect ("Markdown sample failed");
assert_eq! (expected, &out);
}
}