1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
/*
 * Copyright (C) 2022  Aravinth Manivannan <realaravinth@batsense.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Affero General Public License as
 * published by the Free Software Foundation, either version 3 of the
 * License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU Affero General Public License for more details.
 *
 * 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/>.
 */
use std::fmt;

use actix_web::{
    error::ResponseError,
    http::{header::ContentType, StatusCode},
    HttpResponse, HttpResponseBuilder,
};
use derive_more::Display;
use derive_more::Error;
use serde::*;

use super::TemplateFile;
use crate::errors::ServiceError;

pub const ERROR_KEY: &str = "error";

pub const ERROR_TEMPLATE: TemplateFile = TemplateFile::new("error_comp", "components/error.html");
pub fn register_templates(t: &mut tera::Tera) {
    ERROR_TEMPLATE.register(t).expect(ERROR_TEMPLATE.name);
}

/// Render template with error context
pub trait CtxError {
    fn with_error(&self, e: &ReadableError) -> String;
}

#[derive(Serialize, Debug, Display, Clone)]
#[display(fmt = "title: {} reason: {}", title, reason)]
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 }
    }
}

#[derive(Error, Display)]
#[display(fmt = "{}", readable)]
pub struct PageError<T> {
    #[error(not(source))]
    template: T,
    readable: ReadableError,
    #[error(not(source))]
    error: ServiceError,
}

impl<T> fmt::Debug for PageError<T> {
    #[cfg(not(tarpaulin_include))]
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        f.debug_struct("PageError")
            .field("readable", &self.readable)
            .finish()
    }
}

impl<T: CtxError> PageError<T> {
    /// create new instance of [PageError] from a template and an error
    pub fn new(template: T, error: ServiceError) -> Self {
        let readable = ReadableError::new(&error);
        Self {
            error,
            template,
            readable,
        }
    }
}

#[cfg(not(tarpaulin_include))]
impl<T: CtxError> ResponseError for PageError<T> {
    fn error_response(&self) -> HttpResponse {
        HttpResponseBuilder::new(self.status_code())
            .content_type(ContentType::html())
            .body(self.template.with_error(&self.readable))
    }

    fn status_code(&self) -> StatusCode {
        self.error.status_code()
    }
}

/// Generic result data structure
#[cfg(not(tarpaulin_include))]
pub type PageResult<V, T> = std::result::Result<V, PageError<T>>;