credence_lib/middleware/
catch.rs

1use super::{constants::*, defer::*};
2
3use {
4    axum::{
5        extract::{Request, *},
6        http::*,
7        middleware::*,
8        response::{Response, *},
9    },
10    kutil_http::file::*,
11    std::{path::*, result::Result},
12};
13
14//
15// CatchMiddleware
16//
17
18/// Axum middleware that "catches" ...
19///
20/// * [DeferredResponse], which it finally handles, and
21/// * any non-success [StatusCode], for which it generates a response from a file if its exists
22#[derive(Clone, Debug)]
23pub struct CatchMiddleware {
24    /// Assets path.
25    pub assets_path: PathBuf,
26}
27
28impl CatchMiddleware {
29    /// Constructor.
30    pub fn new(assets_path: PathBuf) -> Self {
31        Self { assets_path }
32    }
33
34    /// To be used with [from_fn_with_state].
35    pub async fn function(
36        State(state_self): State<Self>,
37        request: Request,
38        next: Next,
39    ) -> Result<Response, StatusCode> {
40        if let Some(deferred_response) = DeferredResponse::get(&request) {
41            match deferred_response {
42                DeferredResponse::Hide => {
43                    if let Some(response) = state_self.response(StatusCode::NOT_FOUND).await {
44                        return Ok(response);
45                    }
46                }
47
48                DeferredResponse::Authenticate(authenticate) => {
49                    return Ok(DeferredResponse::authenticate(authenticate));
50                }
51
52                DeferredResponse::RedirectTo((uri_path, status_code)) => {
53                    return Ok(DeferredResponse::redirect_to(uri_path, *status_code));
54                }
55
56                DeferredResponse::Error(message) => {
57                    tracing::error!("{}", message);
58                    if let Some(response) = state_self.response(StatusCode::INTERNAL_SERVER_ERROR).await {
59                        return Ok(response);
60                    }
61                }
62
63                _ => {}
64            }
65        }
66
67        let response = next.run(request).await;
68
69        let status = response.status();
70        if !status.is_success() {
71            if let Some(response) = state_self.response(status).await {
72                return Ok(response);
73            }
74        }
75
76        Ok(response)
77    }
78
79    /// Generate a response for a [StatusCode].
80    pub async fn response(&self, status: StatusCode) -> Option<Response> {
81        let status = status.as_u16();
82        let file_path = self.assets_path.join(status.to_string() + HTML_SUFFIX);
83        if file_path.exists() {
84            tracing::debug!("status page: {}", status);
85            return Some(response_from_file(file_path).await.into_response());
86        }
87
88        None
89    }
90}