bobashare_web/views/
mod.rs1use std::path::Path;
4
5use askama::Template;
6use axum::{
7 http,
8 response::{IntoResponse, Response},
9 routing::get,
10 Router,
11};
12use chrono::TimeDelta;
13use http::header::{HeaderName, HeaderValue};
14use hyper::StatusCode;
15use tower_http::set_header::SetResponseHeaderLayer;
16use tracing::{event, Level};
17use url::Url;
18
19use crate::AppState;
20
21pub mod about;
22pub mod display;
23pub mod filters;
24pub mod upload;
25
26mod prelude {
27 pub use super::CurrentNavigation;
28}
29
30#[derive(Debug, Clone)]
33pub struct TemplateState<'s> {
34 version: &'static str,
35 base_url: &'s Url,
36 max_file_size: u64,
37 max_expiry: Option<TimeDelta>,
38 extra_footer_text: Option<&'s str>,
39 about_page: Option<&'s Path>,
40
41 current_navigation: Option<CurrentNavigation>,
43}
44impl<'s> From<&'s AppState> for TemplateState<'s> {
45 fn from(state: &'s AppState) -> Self {
46 Self {
47 version: env!("CARGO_PKG_VERSION"),
48 base_url: &state.base_url,
49 max_file_size: state.max_file_size,
50 max_expiry: state.max_expiry,
51 extra_footer_text: state.extra_footer_text.as_deref(),
52 about_page: state.about_page.as_deref(),
53 current_navigation: None, }
55 }
56}
57
58#[derive(Debug, Clone, Copy)]
60#[non_exhaustive]
61pub enum CurrentNavigation {
62 Upload,
63 Paste,
64 About,
65}
66
67#[derive(Template)]
68#[template(path = "error.html.jinja")]
69pub struct ErrorTemplate<'s> {
70 pub state: TemplateState<'s>,
71 pub code: StatusCode,
72 pub message: String,
73}
74
75pub struct ErrorResponse(Response);
76impl From<ErrorTemplate<'_>> for ErrorResponse {
77 fn from(tmpl: ErrorTemplate) -> Self {
78 let error_msg = &tmpl.message;
79 match tmpl.render() {
80 Ok(s) => {
81 let status_num = tmpl.code.as_u16();
82 if tmpl.code.is_server_error() {
83 event!(Level::ERROR, status = status_num, error_msg);
84 } else if tmpl.code.is_client_error() {
85 event!(Level::WARN, status = status_num, error_msg);
86 } else {
87 event!(Level::INFO, status = status_num, error_msg);
88 }
89 Self(
90 (
91 tmpl.code,
92 [(
93 http::header::CONTENT_TYPE,
94 http::header::HeaderValue::from_static(ErrorTemplate::MIME_TYPE),
95 )],
96 s,
97 )
98 .into_response(),
99 )
100 }
101 Err(e) => {
102 let status = tmpl.code.as_u16();
103 event!(Level::ERROR, status, error_msg, render_error = ?e, "error rendering error page template, so HTTP 500 returned:");
104 Self(
105 (
106 StatusCode::INTERNAL_SERVER_ERROR,
107 format!("internal error rendering error page template: {:?}", e),
108 )
109 .into_response(),
110 )
111 }
112 }
113 }
114}
115impl From<askama::Error> for ErrorResponse {
116 fn from(err: askama::Error) -> Self {
117 event!(Level::ERROR, render_error = ?err, "error rendering template");
118 Self(
119 (
120 StatusCode::INTERNAL_SERVER_ERROR,
121 format!("internal error rendering template: {:?}", err),
122 )
123 .into_response(),
124 )
125 }
126}
127impl IntoResponse for ErrorResponse {
128 fn into_response(self) -> axum::response::Response {
129 self.0
130 }
131}
132
133#[allow(clippy::result_large_err)]
139pub(crate) fn render_template<T: askama::Template>(tmpl: T) -> Result<Response, ErrorResponse> {
140 let rendered = tmpl.render()?;
141 Ok((
142 StatusCode::OK,
143 [(
144 http::header::CONTENT_TYPE,
145 http::header::HeaderValue::from_static(T::MIME_TYPE),
146 )],
147 rendered,
148 )
149 .into_response())
150}
151
152pub fn router() -> Router<&'static AppState> {
153 let x_robots_tag_no_index = SetResponseHeaderLayer::overriding(
154 HeaderName::from_static("x-robots-tag"),
155 HeaderValue::from_static("noindex"),
156 );
157 Router::new()
158 .route("/", get(upload::upload))
159 .route("/paste/", get(upload::paste))
160 .route("/about/", get(about::about))
161 .route(
162 "/{id}",
163 get(display::display).layer(x_robots_tag_no_index.clone()),
164 )
165 .route(
166 "/raw/{id}",
167 get(display::raw).layer(x_robots_tag_no_index.clone()),
168 )
169}