use std::{
fs::create_dir_all,
path::{Path, PathBuf},
sync::Arc,
time::Duration,
};
use http::{HeaderValue, header::CACHE_CONTROL};
use tower::ServiceBuilder;
use tower_http::{services::ServeDir, set_header::SetResponseHeaderLayer};
use crate::{
cache_control::CacheControlValue, error::ImageEndpointError, image_app_state::ImageAppState,
};
pub const ORIGINAL: &str = "original";
pub const MODIFIED: &str = "modified";
#[derive(Debug)]
pub struct ImageAppOptions {
pub(crate) request_body_limit: usize,
pub(crate) api_route: String,
pub(crate) img_route: String,
pub(crate) image_path: PathBuf,
pub(crate) cache_time: Duration,
}
impl ImageAppOptions {
#[must_use]
pub fn new(
request_body_limit: usize,
api_route: String,
img_route: String,
image_path: PathBuf,
cache_time: Duration,
) -> Self {
Self {
request_body_limit,
api_route,
img_route,
image_path,
cache_time,
}
}
}
impl ImageAppOptions {
pub(crate) fn original_image_path(&self) -> PathBuf {
Path::join(&self.image_path, ORIGINAL)
}
pub(crate) fn modified_image_path(&self) -> PathBuf {
Path::join(&self.image_path, MODIFIED)
}
pub(crate) fn api_route(&self) -> String {
format!("{}{}", &self.api_route, "{*image_path}")
}
pub(crate) fn image_app_state(&self) -> Arc<ImageAppState> {
Arc::new(ImageAppState::from(self))
}
pub(crate) fn serve_dir(
&self,
) -> Result<tower_http::set_header::SetResponseHeader<ServeDir, HeaderValue>, ImageEndpointError>
{
let serve_dir = ServeDir::new(&self.image_path);
let cache_layer = self.cache_layer()?;
let serve_dir = ServiceBuilder::new().layer(cache_layer).service(serve_dir);
Ok(serve_dir)
}
pub(crate) fn cache_layer(
&self,
) -> Result<SetResponseHeaderLayer<HeaderValue>, ImageEndpointError> {
let cache_control = CacheControlValue::new()
.max_age(self.cache_time)
.must_revalidate()
.public();
let cache_layer = SetResponseHeaderLayer::if_not_present(
CACHE_CONTROL,
HeaderValue::from_str(&cache_control.to_string()).map_err(|_| {
ImageEndpointError::Generic("Was not able to parse cache control header".into())
})?,
);
Ok(cache_layer)
}
pub(crate) fn create_image_folders(&self) -> Result<(), ImageEndpointError> {
create_dir_all(self.original_image_path())?;
create_dir_all(self.modified_image_path())?;
Ok(())
}
}
impl Default for ImageAppOptions {
fn default() -> Self {
let image_path = PathBuf::from("./public/api/img/");
Self {
request_body_limit: 10 * 1024 * 1024, api_route: "/api/img/".into(),
image_path,
img_route: "/public/api/img/".into(),
cache_time: Duration::from_secs(60),
}
}
}