Refactor template handler module for tighter interfaces
This commit is contained in:
parent
37a1259c0d
commit
b443116e56
2 changed files with 138 additions and 36 deletions
|
|
@ -47,15 +47,17 @@ fn make_body(
|
||||||
context.insert("message", out_message);
|
context.insert("message", out_message);
|
||||||
context.insert("status_code", &out_code.to_string());
|
context.insert("status_code", &out_code.to_string());
|
||||||
|
|
||||||
handlers::template::render(
|
match handlers::template::render(
|
||||||
"error",
|
"error",
|
||||||
&context,
|
&context,
|
||||||
Some(&format!(
|
Some(&format!(
|
||||||
"Failed to render template for Error {out_code}: {out_message}"
|
"Failed to render template for Error {out_code}: {out_message}"
|
||||||
))
|
))
|
||||||
.cloned(),
|
.cloned(),
|
||||||
)
|
) {
|
||||||
.0
|
Ok(rendered) => rendered.html,
|
||||||
|
Err(error) => error.template.html,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn not_found(State(state): State<GlobalState>) -> Response<Body> {
|
pub async fn not_found(State(state): State<GlobalState>) -> Response<Body> {
|
||||||
|
|
|
||||||
|
|
@ -31,39 +31,100 @@ pub(in crate::router::handlers) fn with_context(
|
||||||
error_message: Option<String>,
|
error_message: Option<String>,
|
||||||
is_error: bool,
|
is_error: bool,
|
||||||
) -> Response<Body> {
|
) -> Response<Body> {
|
||||||
let (body, render_status) = render(name, context, error_message);
|
match render(name, context, error_message) {
|
||||||
|
Ok(rendered) => {
|
||||||
let status_code = if is_error { error_code } else { render_status };
|
let status_code = if is_error { error_code } else { rendered.code };
|
||||||
|
make_response(
|
||||||
make_response(&body, status_code, &[(header::CONTENT_TYPE, "text/html")])
|
&rendered.html,
|
||||||
|
status_code,
|
||||||
|
&[(header::CONTENT_TYPE, "text/html")],
|
||||||
|
)
|
||||||
|
},
|
||||||
|
Err(error) => make_response(
|
||||||
|
&error.template.html,
|
||||||
|
error.template.code,
|
||||||
|
&[(header::CONTENT_TYPE, "text/html")],
|
||||||
|
),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Renderes a template into a String and error code.
|
#[derive(Debug)]
|
||||||
|
pub struct Rendered {
|
||||||
|
pub html: String,
|
||||||
|
pub code: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Rendered {
|
||||||
|
fn ok(html: &str) -> Rendered {
|
||||||
|
Rendered {
|
||||||
|
code: 200,
|
||||||
|
html: String::from(html),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct RenderingError {
|
||||||
|
pub message: String,
|
||||||
|
pub template: Rendered,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RenderingError {
|
||||||
|
fn new(message: &str, code: u16, error: &tera::Error) -> RenderingError {
|
||||||
|
RenderingError {
|
||||||
|
message: String::from(message),
|
||||||
|
template: Rendered {
|
||||||
|
html: emergency_wrap(error, message),
|
||||||
|
code,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_template(
|
||||||
|
message: &str,
|
||||||
|
code: u16,
|
||||||
|
template: &str,
|
||||||
|
) -> RenderingError {
|
||||||
|
RenderingError {
|
||||||
|
message: String::from(message),
|
||||||
|
template: Rendered {
|
||||||
|
html: String::from(template),
|
||||||
|
code,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl std::fmt::Display for RenderingError {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
||||||
|
write!(f, "Rendering Error: {}", self.message)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Renders a template into a String and error code.
|
||||||
///
|
///
|
||||||
/// The template name **must not** contain the extension (e.g. `.html`).
|
/// The template name **must not** contain the extension (e.g. `.html`).
|
||||||
pub(in crate::router::handlers) fn render(
|
pub(in crate::router::handlers) fn render(
|
||||||
template: &str,
|
template: &str,
|
||||||
// TODO take Option, skip context if None,
|
|
||||||
// then template_handler can replace static_template_handler
|
|
||||||
context: &tera::Context,
|
context: &tera::Context,
|
||||||
error_message: Option<String>,
|
error_message: Option<String>,
|
||||||
) -> (String, u16) {
|
) -> Result<Rendered, RenderingError> {
|
||||||
let instant = now();
|
let instant = now();
|
||||||
// TODO just return an Option/String> here
|
|
||||||
let tera = match tera::Tera::new("./templates/**/*") {
|
let tera = match tera::Tera::new("./templates/**/*") {
|
||||||
Ok(t) => t,
|
Ok(engine) => engine,
|
||||||
Err(e) => {
|
Err(error) => {
|
||||||
return (
|
return Err(RenderingError::new(
|
||||||
emergency_wrap(&e, "Failed instantiating template engine"),
|
"Failed instantiating template engine",
|
||||||
500,
|
500,
|
||||||
);
|
&error,
|
||||||
|
))
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
match tera.render(format!("{template}.html").as_str(), context) {
|
match tera.render(format!("{template}.html").as_str(), context) {
|
||||||
Ok(t) => {
|
Ok(html) => {
|
||||||
tlog!(&instant, "Rendered template {template}");
|
tlog!(&instant, "Rendered template {template}");
|
||||||
(t, 200)
|
Ok(Rendered::ok(&html))
|
||||||
},
|
},
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
let mut error_context = tera::Context::default();
|
let mut error_context = tera::Context::default();
|
||||||
|
|
@ -87,11 +148,21 @@ pub(in crate::router::handlers) fn render(
|
||||||
&StatusCode::INTERNAL_SERVER_ERROR.to_string(),
|
&StatusCode::INTERNAL_SERVER_ERROR.to_string(),
|
||||||
);
|
);
|
||||||
|
|
||||||
(
|
match tera.render("error.html", &error_context) {
|
||||||
tera.render("error.html", &error_context)
|
Ok(rendered_error) => Err(RenderingError::with_template(
|
||||||
.unwrap_or(out_error_message.clone()),
|
&out_error_message,
|
||||||
500,
|
500,
|
||||||
)
|
&rendered_error,
|
||||||
|
)),
|
||||||
|
Err(error_rendering_error) => Err(RenderingError::new(
|
||||||
|
&format!(
|
||||||
|
"Failed to render an error message template for \
|
||||||
|
\"{out_error_message}\""
|
||||||
|
),
|
||||||
|
500,
|
||||||
|
&error_rendering_error,
|
||||||
|
)),
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -192,38 +263,63 @@ mod tests {
|
||||||
context.insert("node", &node);
|
context.insert("node", &node);
|
||||||
context.insert("graph", &graph);
|
context.insert("graph", &graph);
|
||||||
context.insert("incoming", &graph.incoming.get(&node.id));
|
context.insert("incoming", &graph.incoming.get(&node.id));
|
||||||
let (body, status) = render("node", &context, None);
|
match render("node", &context, None) {
|
||||||
assert_eq!(status, 200);
|
Ok(rendered) => {
|
||||||
assert!(body.matches(payload).count() == 1);
|
assert_eq!(rendered.code, 200);
|
||||||
|
assert!(rendered.html.matches(payload).count() == 1);
|
||||||
|
},
|
||||||
|
Err(error) => {
|
||||||
|
panic!("Errored on template generation with {error:?}")
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn render_custom_error_message() {
|
fn render_custom_error_message() {
|
||||||
let payload = "dBgIw8DnNHxJojiXzu445qUC4UpxwZCy";
|
let payload = "dBgIw8DnNHxJojiXzu445qUC4UpxwZCy";
|
||||||
let (body, status) = render(
|
match render(
|
||||||
"ObH9jYUl4wMhUNcXnuqwVVzHoqx4ufyN",
|
"ObH9jYUl4wMhUNcXnuqwVVzHoqx4ufyN",
|
||||||
&tera::Context::default(),
|
&tera::Context::default(),
|
||||||
Some(payload.to_string()),
|
Some(payload.to_string()),
|
||||||
);
|
) {
|
||||||
assert_eq!(status, 500);
|
Ok(_) => panic!("Got Ok, expected Error"),
|
||||||
assert!(body.matches(payload).count() == 1);
|
Err(error) => {
|
||||||
|
assert_eq!(error.template.code, 500);
|
||||||
|
assert!(error.template.html.matches(payload).count() == 1);
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn render_empty() {
|
fn render_empty() {
|
||||||
let (body, status) = render(
|
match render(
|
||||||
"R8D1pxwHZDxcH5SMjR7rZEnIzmpkiHkH",
|
"R8D1pxwHZDxcH5SMjR7rZEnIzmpkiHkH",
|
||||||
&tera::Context::default(),
|
&tera::Context::default(),
|
||||||
None,
|
None,
|
||||||
);
|
) {
|
||||||
assert_eq!(status, 500);
|
Ok(_) => panic!("Got Ok, expected Error"),
|
||||||
assert!(body.matches("Template render failed").count() == 1);
|
Err(error) => {
|
||||||
|
assert_eq!(error.template.code, 500);
|
||||||
|
assert!(
|
||||||
|
error
|
||||||
|
.template
|
||||||
|
.html
|
||||||
|
.matches("Template render failed")
|
||||||
|
.count()
|
||||||
|
== 1
|
||||||
|
);
|
||||||
|
},
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn render_not_found() {
|
fn render_not_found() {
|
||||||
let payload = "OL6kb9qHe7Iwr7wFIRKUTeFhF34BRsQo";
|
let payload = "OL6kb9qHe7Iwr7wFIRKUTeFhF34BRsQo";
|
||||||
let (body, status) = render(payload, &tera::Context::default(), None);
|
let (body, status) =
|
||||||
|
match render(payload, &tera::Context::default(), None) {
|
||||||
|
Ok(_) => panic!("Got Ok, expected Error"),
|
||||||
|
Err(error) => (error.template.html, error.template.code),
|
||||||
|
};
|
||||||
|
|
||||||
assert!(body.matches("TemplateNotFound").count() > 0);
|
assert!(body.matches("TemplateNotFound").count() > 0);
|
||||||
assert!(body.matches(payload).count() > 0);
|
assert!(body.matches(payload).count() > 0);
|
||||||
|
|
@ -232,7 +328,11 @@ mod tests {
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn render_bad_context() {
|
fn render_bad_context() {
|
||||||
let (body, status) = render("node", &tera::Context::default(), None);
|
let (body, status) =
|
||||||
|
match render("node", &tera::Context::default(), None) {
|
||||||
|
Ok(rendered) => panic!("Got Ok, expected Error"),
|
||||||
|
Err(error) => (error.template.html, error.template.code),
|
||||||
|
};
|
||||||
assert!(body.matches("Template render failed.").count() > 0);
|
assert!(body.matches("Template render failed.").count() > 0);
|
||||||
assert_eq!(status, 500);
|
assert_eq!(status, 500);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue