feat: port error component to tera
This commit is contained in:
parent
fe584190f5
commit
9a15e11c5d
2 changed files with 77 additions and 91 deletions
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2021 Aravinth Manivannan <realaravinth@batsense.net>
|
* Copyright (C) 2022 Aravinth Manivannan <realaravinth@batsense.net>
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU Affero General Public License as
|
* it under the terms of the GNU Affero General Public License as
|
||||||
|
@ -14,107 +14,93 @@
|
||||||
* You should have received a copy of the GNU Affero General Public License
|
* You should have received a copy of the GNU Affero General Public License
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
*/
|
*/
|
||||||
|
use std::fmt;
|
||||||
|
|
||||||
use actix_web::{web, HttpResponse, Responder};
|
use actix_web::{
|
||||||
use lazy_static::lazy_static;
|
error::ResponseError,
|
||||||
use sailfish::TemplateOnce;
|
http::{header::ContentType, StatusCode},
|
||||||
|
HttpResponse, HttpResponseBuilder,
|
||||||
|
};
|
||||||
|
use derive_more::Display;
|
||||||
|
use derive_more::Error;
|
||||||
|
use serde::*;
|
||||||
|
|
||||||
use crate::errors::PageError;
|
use super::TemplateFile;
|
||||||
|
use crate::errors::ServiceError;
|
||||||
|
|
||||||
#[derive(Clone, TemplateOnce)]
|
pub const ERROR_KEY: &str = "error";
|
||||||
#[template(path = "errors/index.html")]
|
|
||||||
pub struct ErrorPage<'a> {
|
pub const ERROR_TEMPLATE: TemplateFile =
|
||||||
pub title: &'a str,
|
TemplateFile::new("error_comp", "components/error/index.html");
|
||||||
pub message: &'a str,
|
pub fn register_templates(t: &mut tera::Tera) {
|
||||||
|
ERROR_TEMPLATE.register(t).expect(ERROR_TEMPLATE.name);
|
||||||
}
|
}
|
||||||
|
|
||||||
const PAGE: &str = "Error";
|
/// Render template with error context
|
||||||
|
pub trait CtxError {
|
||||||
|
fn with_error(&self, e: &ReadableError) -> String;
|
||||||
|
}
|
||||||
|
|
||||||
impl<'a> ErrorPage<'a> {
|
#[derive(Serialize, Debug, Display, Clone)]
|
||||||
pub fn new(title: &'a str, message: &'a str) -> Self {
|
#[display(fmt = "title: {} reason: {}", title, reason)]
|
||||||
ErrorPage { title, message }
|
pub struct ReadableError {
|
||||||
|
pub reason: String,
|
||||||
|
pub title: String,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ReadableError {
|
||||||
|
pub fn new(e: &ServiceError) -> Self {
|
||||||
|
let reason = format!("{}", e);
|
||||||
|
let title = format!("{}", e.status_code());
|
||||||
|
|
||||||
|
Self { reason, title }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
lazy_static! {
|
#[derive(Error, Display)]
|
||||||
static ref INTERNAL_SERVER_ERROR_BODY: String = ErrorPage::new(
|
#[display(fmt = "{}", readable)]
|
||||||
"Internal Server Error",
|
pub struct PageError<T> {
|
||||||
&format!("{}", PageError::InternalServerError),
|
#[error(not(source))]
|
||||||
)
|
template: T,
|
||||||
.render_once()
|
readable: ReadableError,
|
||||||
.unwrap();
|
#[error(not(source))]
|
||||||
static ref UNKNOWN_ERROR_BODY: String = ErrorPage::new(
|
error: ServiceError,
|
||||||
"Something went wrong",
|
|
||||||
&format!("{}", PageError::InternalServerError),
|
|
||||||
)
|
|
||||||
.render_once()
|
|
||||||
.unwrap();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const ERROR_ROUTE: &str = "/error/{id}";
|
impl<T> fmt::Debug for PageError<T> {
|
||||||
|
#[cfg(not(tarpaulin_include))]
|
||||||
#[my_codegen::get(path = "ERROR_ROUTE")]
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
async fn error(path: web::Path<usize>) -> impl Responder {
|
f.debug_struct("PageError")
|
||||||
let resp = match path.into_inner() {
|
.field("readable", &self.readable)
|
||||||
500 => HttpResponse::InternalServerError()
|
.finish()
|
||||||
.content_type("text/html; charset=utf-8")
|
|
||||||
.body(&*INTERNAL_SERVER_ERROR_BODY.as_str()),
|
|
||||||
|
|
||||||
_ => HttpResponse::InternalServerError()
|
|
||||||
.content_type("text/html; charset=utf-8")
|
|
||||||
.body(&*UNKNOWN_ERROR_BODY.as_str()),
|
|
||||||
};
|
|
||||||
|
|
||||||
resp
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn services(cfg: &mut web::ServiceConfig) {
|
|
||||||
cfg.service(error);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub mod routes {
|
|
||||||
pub struct Errors {
|
|
||||||
pub internal_server_error: &'static str,
|
|
||||||
pub unknown_error: &'static str,
|
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl Errors {
|
impl<T: CtxError> PageError<T> {
|
||||||
pub const fn new() -> Self {
|
/// create new instance of [PageError] from a template and an error
|
||||||
Errors {
|
pub fn new(template: T, error: ServiceError) -> Self {
|
||||||
internal_server_error: "/error/500",
|
let readable = ReadableError::new(&error);
|
||||||
unknown_error: "/error/007",
|
Self {
|
||||||
}
|
error,
|
||||||
|
template,
|
||||||
|
readable,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(not(tarpaulin_include))]
|
||||||
mod tests {
|
impl<T: CtxError> ResponseError for PageError<T> {
|
||||||
use actix_web::{http::StatusCode, test, App};
|
fn error_response(&self) -> HttpResponse {
|
||||||
|
HttpResponseBuilder::new(self.status_code())
|
||||||
|
.content_type(ContentType::html())
|
||||||
|
.body(self.template.with_error(&self.readable))
|
||||||
|
}
|
||||||
|
|
||||||
use super::*;
|
fn status_code(&self) -> StatusCode {
|
||||||
use crate::PAGES;
|
self.error.status_code()
|
||||||
|
|
||||||
#[actix_rt::test]
|
|
||||||
async fn error_pages_work() {
|
|
||||||
let app = test::init_service(App::new().configure(services)).await;
|
|
||||||
|
|
||||||
let resp = test::call_service(
|
|
||||||
&app,
|
|
||||||
test::TestRequest::get()
|
|
||||||
.uri(PAGES.errors.internal_server_error)
|
|
||||||
.to_request(),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
|
||||||
|
|
||||||
let resp = test::call_service(
|
|
||||||
&app,
|
|
||||||
test::TestRequest::get()
|
|
||||||
.uri(PAGES.errors.unknown_error)
|
|
||||||
.to_request(),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
assert_eq!(resp.status(), StatusCode::INTERNAL_SERVER_ERROR);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Generic result data structure
|
||||||
|
#[cfg(not(tarpaulin_include))]
|
||||||
|
pub type PageResult<V, T> = std::result::Result<V, PageError<T>>;
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<div class="error__container">
|
{% if error %}
|
||||||
<. if let Some(error) = error { .>
|
<div class="error__container">
|
||||||
<p class="error__title"><b><.= error.title .></b></p>
|
<h3 class="error__title">ERROR: {{ error.title }}</h3>
|
||||||
<p class="error__msg"><.= error.message .></p>
|
<p class="error__msg">{{ error.reason }}</p>
|
||||||
<. } .>
|
</div>
|
||||||
</div>
|
{% endif %}
|
||||||
|
|
Loading…
Add table
Reference in a new issue