aboutsummaryrefslogtreecommitdiff
path: root/server/src/routes/ui/error.rs
blob: b8a132dfd82bbbb0f24404cc85c6e14c5cff689d (plain)
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
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
/*
    This file is part of jellything (https://codeberg.org/metamuffin/jellything)
    which is licensed under the GNU Affero General Public License (version 3); see /COPYING.
    Copyright (C) 2024 metamuffin <metamuffin.org>
*/
use super::layout::{DynLayoutPage, LayoutPage};
use crate::{routes::ui::account::rocket_uri_macro_r_account_login, uri};
use jellybase::CONF;
use log::info;
use rocket::{
    catch,
    http::{ContentType, Status},
    response::{self, Responder},
    Request,
};
use serde_json::{json, Value};
use std::{fmt::Display, fs::File, io::Read, sync::LazyLock};

static ERROR_IMAGE: LazyLock<Vec<u8>> = LazyLock::new(|| {
    info!("loading error image");
    let mut f = File::open(CONF.asset_path.join("error.avif"))
        .expect("please create error.avif in the asset dir");
    let mut o = Vec::new();
    f.read_to_end(&mut o).unwrap();
    o
});

#[catch(default)]
pub fn r_catch<'a>(status: Status, _request: &Request) -> DynLayoutPage<'a> {
    LayoutPage {
        title: "Not found".to_string(),
        content: markup::new! {
            h2 { "Error" }
            p { @format!("{status}") }
            @if status == Status::NotFound {
                p { "You might need to " a[href=uri!(r_account_login())] { "log in" } ", to see this page" }
            }
        },
        ..Default::default()
    }
}

#[catch(default)]
pub fn r_api_catch<'a>(status: Status, _request: &Request) -> Value {
    json!({ "error": format!("{status}") })
}

pub type MyResult<T> = Result<T, MyError>;

// TODO an actual error enum would be useful for status codes

pub struct MyError(pub anyhow::Error);

impl<'r> Responder<'r, 'static> for MyError {
    fn respond_to(self, req: &'r Request<'_>) -> response::Result<'static> {
        match req.accept().map(|a| a.preferred()) {
            Some(x) if x.is_json() => json!({ "error": format!("{}", self.0) }).respond_to(req),
            Some(x) if x.is_avif() || x.is_png() || x.is_jpeg() => {
                (ContentType::AVIF, ERROR_IMAGE.as_slice()).respond_to(req)
            }
            _ => LayoutPage {
                title: "Error".to_string(),
                content: markup::new! {
                    h2 { "An error occured. Nobody is sorry"}
                    pre.error { @format!("{:?}", self.0) }
                },
                ..Default::default()
            }
            .respond_to(req),
        }
    }
}

impl std::fmt::Debug for MyError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        f.write_fmt(format_args!("{:?}", self.0))
    }
}

impl Display for MyError {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        self.0.fmt(f)
    }
}
impl From<anyhow::Error> for MyError {
    fn from(err: anyhow::Error) -> MyError {
        MyError(err)
    }
}
impl From<std::fmt::Error> for MyError {
    fn from(err: std::fmt::Error) -> MyError {
        MyError(anyhow::anyhow!("{err}"))
    }
}
impl From<std::io::Error> for MyError {
    fn from(err: std::io::Error) -> Self {
        MyError(anyhow::anyhow!("{err}"))
    }
}
impl From<serde_json::Error> for MyError {
    fn from(err: serde_json::Error) -> Self {
        MyError(anyhow::anyhow!("{err}"))
    }
}
impl From<jellybase::database::redb::CommitError> for MyError {
    fn from(err: jellybase::database::redb::CommitError) -> Self {
        MyError(anyhow::anyhow!("database oopsie during commit: {err}"))
    }
}
impl From<jellybase::database::redb::CompactionError> for MyError {
    fn from(err: jellybase::database::redb::CompactionError) -> Self {
        MyError(anyhow::anyhow!("database oopsie during compaction: {err}"))
    }
}
impl From<jellybase::database::redb::DatabaseError> for MyError {
    fn from(err: jellybase::database::redb::DatabaseError) -> Self {
        MyError(anyhow::anyhow!("generic database oopsie: {err}"))
    }
}
impl From<jellybase::database::redb::SavepointError> for MyError {
    fn from(err: jellybase::database::redb::SavepointError) -> Self {
        MyError(anyhow::anyhow!(
            "database oopsie during savepointing: {err}"
        ))
    }
}
impl From<jellybase::database::redb::StorageError> for MyError {
    fn from(err: jellybase::database::redb::StorageError) -> Self {
        MyError(anyhow::anyhow!("database oopsie, storage error: {err}"))
    }
}
impl From<jellybase::database::redb::TableError> for MyError {
    fn from(err: jellybase::database::redb::TableError) -> Self {
        MyError(anyhow::anyhow!("database oopsie, table error: {err}"))
    }
}
impl From<jellybase::database::redb::TransactionError> for MyError {
    fn from(err: jellybase::database::redb::TransactionError) -> Self {
        MyError(anyhow::anyhow!("database oopsie during transaction: {err}"))
    }
}
impl From<jellybase::database::tantivy::TantivyError> for MyError {
    fn from(err: jellybase::database::tantivy::TantivyError) -> Self {
        MyError(anyhow::anyhow!("database during search: {err}"))
    }
}