//! Remplacement minimal de `axum_embed` compatible avec axum 0.8. //! //! Implémente un service Tower qui sert des fichiers embarqués via `rust_embed`, //! avec support optionnel du mode SPA (fallback vers index.html). use axum::body::Body; use axum::http::{Request, StatusCode, header}; use axum::response::{IntoResponse, Response}; use rust_embed::RustEmbed; use std::convert::Infallible; use std::marker::PhantomData; use std::task::{Context, Poll}; use tower::Service; /// Service Tower servant des fichiers embarqués via `RustEmbed`. /// /// - Mode normal (`new`): retourne 404 si le fichier n'existe pas. /// - Mode SPA (`with_spa_fallback`): retourne le fichier de fallback (200) si le fichier n'existe pas. #[derive(Clone)] pub struct ServeEmbed { spa_fallback: Option, _phantom: PhantomData, } impl ServeEmbed { /// Mode normal : 404 pour les fichiers manquants. pub fn new() -> Self { Self { spa_fallback: None, _phantom: PhantomData, } } /// Mode SPA : sert `fallback_file` (avec 200) pour les fichiers manquants. pub fn with_spa_fallback(fallback_file: String) -> Self { Self { spa_fallback: Some(fallback_file), _phantom: PhantomData, } } fn serve(path: &str) -> Option { let path = path.trim_start_matches('/'); // Essaie le chemin exact, puis index.html pour les répertoires let candidates: &[&str] = if path.is_empty() || path.ends_with('/') { &[&format!("{}index.html", path), path] } else { &[path] }; for candidate in candidates { if let Some(content) = E::get(candidate) { // mime_guess ne connaît pas .webmanifest (trop récent) let mime = if candidate.ends_with(".webmanifest") { "application/manifest+json".to_string() } else { mime_guess::from_path(candidate) .first_or_octet_stream() .to_string() }; return Some( ( [(header::CONTENT_TYPE, mime.as_str())], content.data.into_owned(), ) .into_response(), ); } } None } } impl Service> for ServeEmbed where E: RustEmbed + Clone + Send + Sync + 'static, { type Response = Response; type Error = Infallible; type Future = std::future::Ready>; fn poll_ready(&mut self, _cx: &mut Context<'_>) -> Poll> { Poll::Ready(Ok(())) } fn call(&mut self, req: Request) -> Self::Future { let path = req.uri().path(); let response = Self::serve(path).unwrap_or_else(|| { if let Some(ref fallback) = self.spa_fallback { Self::serve(fallback).unwrap_or_else(|| StatusCode::NOT_FOUND.into_response()) } else { StatusCode::NOT_FOUND.into_response() } }); std::future::ready(Ok(response)) } }