use std::path::PathBuf;
use axum::http::header::{HeaderValue, CACHE_CONTROL};
use axum::Router as AXRouter;
use serde::{Deserialize, Serialize};
use serde_json::json;
use tower_http::services::{ServeDir, ServeFile};
use tower_http::set_header::SetResponseHeaderLayer;
use crate::{app::AppContext, controller::middleware::MiddlewareLayer, Error, Result};
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct StaticAssets {
#[serde(default)]
pub enable: bool,
#[serde(default = "default_must_exist")]
pub must_exist: bool,
#[serde(default = "default_folder_config")]
pub folder: FolderConfig,
#[serde(default = "default_fallback")]
pub fallback: PathBuf,
#[serde(default = "default_precompressed")]
pub precompressed: bool,
pub cache_control: Option<String>,
}
impl Default for StaticAssets {
fn default() -> Self {
serde_json::from_value(json!({})).unwrap()
}
}
fn default_must_exist() -> bool {
true
}
fn default_precompressed() -> bool {
false
}
fn default_fallback() -> PathBuf {
PathBuf::from("assets").join("static").join("404.html")
}
fn default_folder_config() -> FolderConfig {
FolderConfig {
uri: "/static".to_string(),
path: PathBuf::from("assets/static"),
}
}
#[derive(Default, Debug, Clone, Deserialize, Serialize)]
pub struct FolderConfig {
pub uri: String,
pub path: PathBuf,
}
impl MiddlewareLayer for StaticAssets {
fn name(&self) -> &'static str {
"static"
}
fn is_enabled(&self) -> bool {
self.enable
}
fn config(&self) -> serde_json::Result<serde_json::Value> {
serde_json::to_value(self)
}
fn apply(&self, app: AXRouter<AppContext>) -> Result<AXRouter<AppContext>> {
if self.must_exist && (!&self.folder.path.exists() || !&self.fallback.exists()) {
return Err(Error::Message(format!(
"one of the static path are not found, Folder `{}` fallback: `{}`",
self.folder.path.display(),
self.fallback.display(),
)));
}
let serve_dir = ServeDir::new(&self.folder.path).fallback(ServeFile::new(&self.fallback));
let static_service = if let Some(cache_control) = &self.cache_control {
let cache_header_layer = SetResponseHeaderLayer::overriding(
CACHE_CONTROL,
HeaderValue::from_str(cache_control)
.unwrap_or_else(|_| HeaderValue::from_static("max-age=31536000")),
);
let base_service = if self.precompressed {
serve_dir.precompressed_gzip()
} else {
serve_dir
};
AXRouter::new()
.fallback_service(base_service)
.layer(cache_header_layer)
} else {
let base_service = if self.precompressed {
serve_dir.precompressed_gzip()
} else {
serve_dir
};
AXRouter::new().fallback_service(base_service)
};
if &self.folder.uri == "/" {
Ok(app.fallback_service(static_service))
} else {
Ok(app.nest_service(&self.folder.uri, static_service))
}
}
}