use crate::DisplayConfig;
use crate::SwordServiceRegistrar;
use serde::{Deserialize, Serialize};
use thisconfig::{ByteConfig, ConfigItem};
use tower_http::services::{ServeDir, ServeFile};
pub type ServeDirLayer = ServeDir<ServeFile>;
#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(default)]
pub struct ServeDirConfig {
#[serde(rename = "static-dir")]
pub static_dir: String,
#[serde(rename = "router-path")]
pub router_path: String,
#[serde(rename = "compression-algorithm")]
pub compression_algorithm: Option<String>,
#[serde(rename = "chunk-size")]
pub chunk_size: Option<ByteConfig>,
#[serde(rename = "not-found-file")]
pub not_found_file: Option<String>,
pub display: bool,
}
impl From<ServeDirConfig> for ServeDirLayer {
fn from(config: ServeDirConfig) -> ServeDirLayer {
let mut fallback = ServeFile::new(format!("{}/404.html", config.static_dir));
if let Some(not_found_file) = &config.not_found_file {
fallback = ServeFile::new(format!("{}/{not_found_file}", config.static_dir));
}
let mut layer = ServeDir::new(&config.static_dir).fallback(fallback);
if let Some(algorithm) = &config.compression_algorithm {
match algorithm.as_str() {
"br" => {
layer = layer.precompressed_br();
}
"gzip" => {
layer = layer.precompressed_gzip();
}
"deflate" => {
layer = layer.precompressed_deflate();
}
"zstd" => {
layer = layer.precompressed_zstd();
}
_ => {}
}
}
if let Some(chunk_size) = &config.chunk_size {
layer = layer.with_buf_chunk_size(chunk_size.parsed);
}
layer
}
}
impl Default for ServeDirConfig {
fn default() -> Self {
ServeDirConfig {
static_dir: "public".to_string(),
router_path: "/static".to_string(),
compression_algorithm: None,
chunk_size: None,
not_found_file: None,
display: false,
}
}
}
impl DisplayConfig for ServeDirConfig {
fn display(&self) {
if !self.display {
return;
}
tracing::debug!(
target: "sword.layers.serve_dir",
static_dir = %self.static_dir,
router_path = %self.router_path,
compression_algorithm = ?self.compression_algorithm,
chunk_size = ?self.chunk_size.as_ref().map(|value| &value.raw),
not_found_file = ?self.not_found_file,
"ServeDir configuration"
);
}
}
impl ConfigItem for ServeDirConfig {
fn key() -> &'static str {
"serve-dir"
}
}
inventory::submit! {
SwordServiceRegistrar {
name: "serve-dir",
display: |config: &thisconfig::Config| {
let cfg: &ServeDirConfig = &config.get_or_default::<ServeDirConfig>();
cfg.display();
},
register: |config: &thisconfig::Config| {
let cfg: ServeDirConfig = config.get_or_default::<ServeDirConfig>();
let router_path = cfg.router_path.clone();
let service: ServeDirLayer = cfg.into();
Box::new(move |any: &mut dyn std::any::Any| {
let router: &mut axum::Router<sword_core::State> = any
.downcast_mut()
.expect("SwordServiceRegistrar: expected Router<sword_core::State>");
*router = std::mem::take(router).nest_service(&router_path, service.clone());
})
},
}
}