credence_lib/middleware/
facade.rs

1use super::{
2    super::{configuration::*, render::*, util::*},
3    constants::*,
4    defer::*,
5};
6
7use {::axum::extract::*, kutil_http::*};
8
9//
10// FacadeMiddleware
11//
12
13/// Axum middleware that handles the URI facade.
14///
15/// * Redirects trailing slashes (301)
16/// * Hides paths (404) that end with `.html`, `.r.*`, or other configured suffixes
17/// * Rewrites to `.html` or `.r.*` files if they exist
18/// * Otherwise if it's a directory rewrites to `index.html` or `index.r.*` if they exist in it
19///
20/// All of the above is handled by attaching a [DeferredResponse] to the *request* (not the
21/// response). We do this because request mapping middleware cannot return a normal response. Thus,
22/// we also need to install [CatchMiddleware], which "catches" the deferment and generates the
23/// actual response.
24///
25/// (Remember that axum runs through layers in the *reverse* order in which they are
26/// programatically added, so add [CatchMiddleware] first *and then* add [FacadeMiddleware].)
27///
28/// We referred to "normal" responses above, because actually request mapping can return "abnormal"
29/// responses: as a [Result::Err]. We can consider them abnormal because they circumvent the
30/// remaining middleware, thus making that codepath unsuitable for us.
31#[derive(Clone, Debug)]
32pub struct FacadeMiddleware {
33    /// Configuration.
34    pub configuration: CredenceConfiguration,
35}
36
37impl FacadeMiddleware {
38    /// Constructor.
39    pub fn new(configuration: CredenceConfiguration) -> Self {
40        Self { configuration }
41    }
42
43    /// To be used with [map_request_with_state].
44    pub async fn function(State(state_self): State<Self>, mut request: Request) -> Request {
45        let uri_path = match request.uri().decoded_path() {
46            Some(uri_path) => uri_path,
47            None => {
48                // Cannot decode path
49                return request;
50            }
51        };
52
53        let original_uri_path = uri_path.clone();
54        let mut uri_path = uri_path.as_ref();
55        let mut new_uri_path = None;
56
57        // Redirect
58
59        if let Some((uri_path, status_code)) = state_self.configuration.urls.redirect(uri_path) {
60            // Note that since RFC 7231 relative `Location` is allowed
61            tracing::info!("redirect to: {} {}", status_code.as_u16(), uri_path);
62            return request.with_deferred_redirect_to(uri_path.into(), status_code);
63        }
64
65        // Hide
66
67        if state_self.configuration.hide(uri_path) {
68            tracing::info!("hide: {}", uri_path);
69            return request.with_deferred_hide();
70        }
71
72        // Protect
73
74        if let Some(protect) = state_self.configuration.urls.protect(uri_path) {
75            if let Some(authenticate) = protect.authorized(request.headers()) {
76                return request.with_deferred_authenticate(authenticate);
77            }
78        }
79
80        // Rewrite
81
82        let mut asset_path = state_self.configuration.files.asset(uri_path);
83
84        // If it's a directory then switch to the index file in the directory
85        let mut _uri_path = String::default();
86        if asset_path.is_dir() {
87            asset_path = asset_path.join(INDEX);
88            _uri_path = uri_path_join(uri_path, INDEX);
89            uri_path = &_uri_path;
90        }
91
92        let html_file = asset_path.with_extension(HTML_EXTENSION);
93        if html_file.exists() {
94            // {path}.html
95            new_uri_path = Some(String::from(uri_path) + HTML_SUFFIX);
96        } else if let Some(rendered_page_uri_path) = match state_self.configuration.rendered_page_uri_path(uri_path) {
97            Ok(uri_path) => uri_path,
98            Err(_error) => {
99                return request.with_deferred_error("rendered page URI path".into());
100            }
101        } {
102            // {path}.r.*
103            new_uri_path = Some(rendered_page_uri_path);
104        }
105
106        if let Some(new_uri_path) = new_uri_path {
107            tracing::debug!("rewriting: {} to {}", original_uri_path, new_uri_path);
108            let original_uri_path = original_uri_path.into_owned().into();
109            if let Err(_error) = request.set_uri_path(&new_uri_path) {
110                return request.with_deferred_error("set URI path".into());
111            }
112            return request.with_deferred_rewrite_from(original_uri_path);
113        }
114
115        request
116    }
117}