bobashare_web/views/
mod.rs

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
147
148
149
150
151
//! Frontend views (as opposed to REST API)

use std::path::Path;

use askama::Template;
use axum::{
    http,
    response::{IntoResponse, Response},
    routing::get,
    Router,
};
use chrono::Duration;
use hyper::StatusCode;
use tracing::{event, Level};
use url::Url;

use crate::AppState;

pub mod about;
pub mod display;
pub mod filters;
pub mod upload;

mod prelude {
    pub use super::CurrentNavigation;
}

// 's is for &AppState
// TODO: should this be Copy
#[derive(Debug, Clone)]
pub struct TemplateState<'s> {
    version: &'static str,
    base_url: &'s Url,
    max_file_size: u64,
    max_expiry: Option<Duration>,
    extra_footer_text: Option<&'s str>,
    about_page: Option<&'s Path>,

    // None if the current page is not a navbar item
    current_navigation: Option<CurrentNavigation>,
}
impl<'s> From<&'s AppState> for TemplateState<'s> {
    fn from(state: &'s AppState) -> Self {
        Self {
            version: env!("CARGO_PKG_VERSION"),
            base_url: &state.base_url,
            max_file_size: state.max_file_size,
            max_expiry: state.max_expiry,
            extra_footer_text: state.extra_footer_text.as_deref(),
            about_page: state.about_page.as_deref(),
            current_navigation: None, // will be set to Some in individual handlers
        }
    }
}

// which page is current navigated to, for navbar formatting
#[derive(Debug, Clone, Copy)]
#[non_exhaustive]
pub enum CurrentNavigation {
    Upload,
    Paste,
    About,
}

#[derive(Template)]
#[template(path = "error.html.jinja")]
pub struct ErrorTemplate<'s> {
    pub state: TemplateState<'s>,
    pub code: StatusCode,
    pub message: String,
}

pub struct ErrorResponse(Response);
impl From<ErrorTemplate<'_>> for ErrorResponse {
    fn from(tmpl: ErrorTemplate) -> Self {
        let error_msg = &tmpl.message;
        match tmpl.render() {
            Ok(s) => {
                let status_num = tmpl.code.as_u16();
                if tmpl.code.is_server_error() {
                    event!(Level::ERROR, status = status_num, error_msg);
                } else if tmpl.code.is_client_error() {
                    event!(Level::WARN, status = status_num, error_msg);
                } else {
                    event!(Level::INFO, status = status_num, error_msg);
                }
                Self(
                    (
                        tmpl.code,
                        [(
                            http::header::CONTENT_TYPE,
                            http::header::HeaderValue::from_static(ErrorTemplate::MIME_TYPE),
                        )],
                        s,
                    )
                        .into_response(),
                )
            }
            Err(e) => {
                let status = tmpl.code.as_u16();
                event!(Level::ERROR, status, error_msg, render_error = ?e, "error rendering error page template, so HTTP 500 returned:");
                Self(
                    (
                        StatusCode::INTERNAL_SERVER_ERROR,
                        format!("internal error rendering error page template: {:?}", e),
                    )
                        .into_response(),
                )
            }
        }
    }
}
impl From<askama::Error> for ErrorResponse {
    fn from(err: askama::Error) -> Self {
        event!(Level::ERROR, render_error = ?err, "error rendering template");
        Self(
            (
                StatusCode::INTERNAL_SERVER_ERROR,
                format!("internal error rendering template: {:?}", err),
            )
                .into_response(),
        )
    }
}
impl IntoResponse for ErrorResponse {
    fn into_response(self) -> axum::response::Response {
        self.0
    }
}

pub(crate) fn render_template<T: askama::Template>(tmpl: T) -> Result<Response, ErrorResponse> {
    let rendered = tmpl.render()?;
    Ok((
        StatusCode::OK,
        [(
            http::header::CONTENT_TYPE,
            http::header::HeaderValue::from_static(T::MIME_TYPE),
        )],
        rendered,
    )
        .into_response())
}

pub fn router() -> Router<&'static AppState> {
    Router::new()
        .route("/", get(upload::upload))
        .route("/paste/", get(upload::paste))
        .route("/about/", get(about::about))
        .route("/{id}", get(display::display))
        .route("/raw/{id}", get(display::raw))
}