Skip to main content

ordinary_config/app/assets/
mod.rs

1// Copyright (C) 2026 The Ordinary Authors.
2//
3// SPDX-License-Identifier: BSD-3-Clause
4
5use crate::{CompressionAlgorithm, CompressionAlgorithms, HttpCache, HttpCsp};
6use arrayvec::ArrayVec;
7use serde::{Deserialize, Serialize};
8
9#[cfg_attr(feature = "utoipa", derive(utoipa::ToSchema))]
10#[cfg_attr(feature = "docs", derive(schemars::JsonSchema))]
11#[derive(Deserialize, Serialize, Debug, Clone, Default)]
12pub struct AssetsConfig {
13    /// Relative path to assets directory.
14    #[serde(skip_serializing_if = "Option::is_none")]
15    #[serde(default)]
16    pub dir_path: Option<String>,
17
18    /// Note: must start with a `/` and cannot end with a `/` unless
19    /// `/` is the entire route.
20    #[serde(default = "AssetsConfig::default_base_route")]
21    pub base_route: String,
22
23    /// whether to add `index.html` to the base route
24    /// and the end of routes having a trailing slash
25    /// or missing an extension.
26    ///
27    /// i.e. `https://example.com/static`, `https://example.com/static/`,
28    /// `https://example.com/static/{*path}/` and `https://example.com/static/{*path}`
29    /// (with no extension) would return the contents of
30    /// `/static/index.html` or `/static/{*path}/index.html`.
31    ///
32    /// primarily useful when serving a generated static site.
33    ///
34    /// Note: cannot be used with `append_index_ext`
35    #[serde(skip_serializing_if = "Option::is_none")]
36    #[serde(default)]
37    pub append_index_html: Option<bool>,
38
39    /// whether the `{base_route}` and `{base_route}/` routes should
40    /// skip returning the `index.html` at the root of the static dir.
41    ///
42    /// useful when the static dir `base_route` is set to `/` but you'd
43    /// like to have a template use the `/` route.
44    #[serde(skip_serializing_if = "Option::is_none")]
45    #[serde(default)]
46    pub skip_base_route_index_html: Option<bool>,
47
48    /// whether to append `.html` to routes that do not
49    /// include an extension.
50    ///
51    /// i.e. `https://example.com/static/about` would return the contents of
52    /// `/static/about.html`.
53    ///
54    /// primarily useful when serving a generated static site.
55    ///
56    /// Note: cannot be used with `append_index_html`
57    #[serde(skip_serializing_if = "Option::is_none")]
58    #[serde(default)]
59    pub append_html_ext: Option<bool>,
60
61    /// will not strip the exif data from images.
62    ///
63    /// Important: only use if you want all metadata (including
64    /// location data) on your photos.
65    #[serde(skip_serializing_if = "Option::is_none")]
66    #[serde(default)]
67    pub preserve_exif: Option<bool>,
68
69    /// content security policy for HTML assets
70    #[serde(skip_serializing_if = "Option::is_none")]
71    #[serde(default)]
72    pub html_csp: Option<HttpCsp>,
73
74    /// HTTP cache configuration.
75    ///
76    /// TODO: provide a way to pattern match files for which to apply
77    /// TODO: specific cache controls.
78    #[serde(skip_serializing_if = "Option::is_none")]
79    #[serde(default)]
80    pub http: Option<HttpCache>,
81
82    /// Which encodings to use when precompressing assets.
83    ///
84    /// Serving priority is dictated by specified order.
85    #[serde(skip_serializing_if = "Option::is_none")]
86    #[serde(default)]
87    pub precompression: Option<CompressionAlgorithms>,
88
89    #[serde(skip)]
90    #[serde(default)]
91    pub internal_precompression: Option<ArrayVec<CompressionAlgorithm, 5>>,
92
93    /// Determines whether CSS files should be minified,
94    /// prior to write.
95    #[serde(skip_serializing_if = "Option::is_none")]
96    #[serde(default)]
97    pub minify_css: Option<bool>,
98
99    /// Determines whether JS files should be minified,
100    /// prior to write.
101    #[serde(skip_serializing_if = "Option::is_none")]
102    #[serde(default)]
103    pub minify_js: Option<bool>,
104
105    /// Determines whether HTML files should be minified,
106    /// prior to write.
107    #[serde(skip_serializing_if = "Option::is_none")]
108    #[serde(default)]
109    pub minify_html: Option<bool>,
110
111    #[serde(skip)]
112    #[serde(default)]
113    pub internal_cache_control_header_value: Option<String>,
114
115    /// list of names of middleware to include for the assets
116    #[serde(skip_serializing_if = "Option::is_none")]
117    #[serde(default)]
118    pub middlewares: Option<Vec<String>>,
119}
120
121impl AssetsConfig {
122    fn default_base_route() -> String {
123        "/assets".to_string()
124    }
125}
126
127impl AssetsConfig {
128    pub fn init(&mut self, default_cache_control_header_value: &str) -> anyhow::Result<()> {
129        if let Some(http_cache) = &self.http
130            && let Some(http_cache_control) = &http_cache.cache_control
131        {
132            let mut header = String::new();
133            http_cache_control.header_value(&mut header, default_cache_control_header_value)?;
134
135            self.internal_cache_control_header_value = Some(header);
136        }
137
138        Ok(())
139    }
140}