Correction de ChatGPT

This commit is contained in:
2025-10-07 15:03:52 +02:00
parent c409c6eb1b
commit 115658bf6a
5 changed files with 114 additions and 227 deletions

View File

@@ -16,13 +16,15 @@
use crate::logs::{LogState, LoggingOptions, init_logging, log_dump, log_sse};
use axum::handler::Handler;
use axum::response::Redirect;
use axum::routing::get;
use axum::routing::{get, post};
use axum::{Json, Router};
use axum_embed::ServeEmbed;
use pmoconfig::get_config;
use rust_embed::RustEmbed;
use serde::Serialize;
use std::{net::SocketAddr, sync::Arc};
use std::future::Future;
use std::net::SocketAddr;
use std::sync::Arc;
use tokio::{signal, sync::RwLock, task::JoinHandle};
use tracing::info;
use utoipa_swagger_ui::SwaggerUi;
@@ -30,11 +32,8 @@ use utoipa_swagger_ui::SwaggerUi;
/// Info serveur sérialisable
#[derive(Clone, Serialize, utoipa::ToSchema)]
pub struct ServerInfo {
/// Nom du serveur
pub name: String,
/// URL de base
pub base_url: String,
/// Port HTTP
pub http_port: u16,
}
@@ -80,8 +79,7 @@ impl Server {
let config = get_config();
let url = config.get_base_url();
let port = config.get_http_port();
return Self::new("PMO-Music-Server", url, port);
Self::new("PMO-Music-Server", url, port)
}
/// Ajoute une route JSON dynamique
@@ -112,11 +110,10 @@ impl Server {
pub async fn add_route<F, Fut, T>(&mut self, path: &str, f: F)
where
F: Fn() -> Fut + Send + Sync + 'static,
Fut: std::future::Future<Output = T> + Send + 'static,
Fut: Future<Output = T> + Send + 'static,
T: Serialize + Send + 'static,
{
let f = Arc::new(f);
let handler = {
let f = f.clone();
move || {
@@ -128,76 +125,81 @@ impl Server {
let route = Router::new().route("/", get(handler));
let mut r = self.router.write().await;
*r = std::mem::take(&mut *r).nest(path, route);
}
/// Add a new router safely:
/// - If `path` starts with '/', it is merged at root level.
/// - Otherwise, it is nested under the given subpath.
pub async fn add_router(&mut self, path: &str, route: Router) {
let mut r = self.router.write().await;
// Take current router without losing content
let current = std::mem::take(&mut *r);
let combined = if path.starts_with('/') {
// Absolute path => merge directly at root
tracing::debug!("Merging router at root path: {}", path);
current.merge(route)
*r = if path == "/" {
std::mem::take(&mut *r).merge(route)
} else {
// Relative path => nest under the given path
let normalized = format!("/{}", path.trim_start_matches('/'));
tracing::debug!("Nesting router under: {}", normalized);
current.nest(&normalized, route)
std::mem::take(&mut *r).nest(path, route)
};
*r = combined;
}
/// Ajoute un répertoire de fichiers statiques
///
/// Sert des fichiers embarqués via `RustEmbed`. Les fichiers sont compilés
/// dans le binaire à la compilation.
///
/// # Arguments
///
/// * `path` - Chemin où monter les fichiers statiques
///
/// # Type Parameter
///
/// * `E` - Type RustEmbed définissant le répertoire à servir
///
/// # Exemple
///
/// ```ignore
/// use pmoupnp::server::Server;
/// use rust_embed::RustEmbed;
///
/// #[derive(RustEmbed, Clone)]
/// #[folder = "static/"]
/// struct Assets;
///
/// # #[tokio::main]
/// # async fn main() {
/// let mut server = Server::new("Test", "http://localhost:3000", 3000);
/// server.add_dir::<Assets>("/assets").await;
/// // Les fichiers de static/ sont accessibles via /assets/*
/// # }
/// ```
/// Ajoute un handler Axum standard
pub async fn add_handler<H, T>(&mut self, path: &str, handler: H)
where
H: Handler<T, ()> + Clone + 'static,
T: 'static,
{
let route = Router::new().route("/", get(handler.clone()));
let mut r = self.router.write().await;
*r = if path == "/" {
std::mem::take(&mut *r).merge(route)
} else {
std::mem::take(&mut *r).nest(path, route)
};
}
/// Ajoute un handler POST avec état
pub async fn add_post_handler_with_state<H, T, S>(&mut self, path: &str, handler: H, state: S)
where
H: Handler<T, S> + Clone + 'static,
T: 'static,
S: Clone + Send + Sync + 'static,
{
let route = Router::new()
.route("/", post(handler.clone()))
.with_state(state.clone());
let mut r = self.router.write().await;
*r = if path == "/" {
std::mem::take(&mut *r).merge(route)
} else {
std::mem::take(&mut *r).nest(path, route)
};
}
/// Ajoute un handler avec état
pub async fn add_handler_with_state<H, T, S>(&mut self, path: &str, handler: H, state: S)
where
H: Handler<T, S> + Clone + 'static,
T: 'static,
S: Clone + Send + Sync + 'static,
{
let route = Router::new()
.route("/", get(handler.clone()))
.with_state(state.clone());
let mut r = self.router.write().await;
*r = if path == "/" {
std::mem::take(&mut *r).merge(route)
} else {
std::mem::take(&mut *r).nest(path, route)
};
}
/// Ajoute un répertoire statique
pub async fn add_dir<E>(&mut self, path: &str)
where
E: RustEmbed + Clone + Send + Sync + 'static,
{
let serve = ServeEmbed::<E>::new();
let mut r = self.router.write().await;
if path == "/" {
*r = std::mem::take(&mut *r).fallback_service(serve);
let route = Router::new().fallback_service(serve);
*r = if path == "/" {
std::mem::take(&mut *r).merge(route)
} else {
let route = Router::new().fallback_service(serve);
*r = std::mem::take(&mut *r).nest(path, route);
}
std::mem::take(&mut *r).nest(path, route)
};
}
/// Ajoute une Single Page Application (SPA)
@@ -252,125 +254,12 @@ impl Server {
let mut r = self.router.write().await;
if path == "/" {
*r = std::mem::take(&mut *r).fallback_service(serve);
let route = Router::new().fallback_service(serve);
*r = if path == "/" {
std::mem::take(&mut *r).merge(route)
} else {
let route = Router::new().fallback_service(serve);
*r = std::mem::take(&mut *r).nest(path, route);
}
}
/// Ajoute un handler Axum personnalisé
///
/// Pour des cas d'usage avancés nécessitant un contrôle complet sur le handler.
///
/// # Arguments
///
/// * `path` - Chemin de la route
/// * `handler` - Handler Axum
///
/// # Exemple
///
/// ```rust,no_run
/// # use pmoupnp::server::Server;
/// # use axum::response::Html;
/// # #[tokio::main]
/// # async fn main() {
/// # let mut server = Server::new("Test", "http://localhost:3000", 3000);
/// async fn custom_handler() -> Html<&'static str> {
/// Html("<h1>Custom Response</h1>")
/// }
///
/// server.add_handler("/custom", custom_handler).await;
/// # }
/// ```
pub async fn add_handler<H, T>(&mut self, path: &str, handler: H)
where
H: Handler<T, ()>,
T: 'static,
{
let route = Router::new().route("/", get(handler));
let mut r = self.router.write().await;
*r = std::mem::take(&mut *r).nest(path, route);
}
/// Ajoute un handler avec state (pour SSE, extracteurs, etc.)
///
/// Permet d'utiliser des extracteurs Axum comme `State`, `Query`, etc.
/// Idéal pour Server-Sent Events (SSE), WebSockets ou tout handler nécessitant un état partagé.
///
/// # Arguments
///
/// * `path` - Chemin de la route
/// * `handler` - Handler Axum avec extracteurs
/// * `state` - État partagé (doit être Clone + Send + Sync)
///
/// # Exemple avec SSE
///
/// ```ignore
/// use pmoupnp::server::Server;
/// use axum::extract::State;
/// use axum::response::sse::{Event, Sse, KeepAlive};
/// use tokio::sync::broadcast;
///
/// #[derive(Clone)]
/// struct LogState {
/// tx: broadcast::Sender<String>
/// }
///
/// impl LogState {
/// fn subscribe(&self) -> broadcast::Receiver<String> {
/// self.tx.subscribe()
/// }
/// }
///
/// async fn log_sse(State(state): State<LogState>) -> Sse<impl futures::Stream<Item = Result<Event, std::convert::Infallible>>> {
/// let mut rx = state.subscribe();
/// let stream = async_stream::stream! {
/// while let Ok(msg) = rx.recv().await {
/// yield Ok(Event::default().data(msg));
/// }
/// };
/// Sse::new(stream).keep_alive(KeepAlive::default())
/// }
///
/// let log_state = LogState { tx: broadcast::channel(100).0 };
/// server.add_handler_with_state("/logs", log_sse, log_state).await;
/// ```
pub async fn add_handler_with_state<H, T, S>(&mut self, path: &str, handler: H, state: S)
where
H: Handler<T, S>,
T: 'static,
S: Clone + Send + Sync + 'static,
{
let route = Router::new().route("/", get(handler)).with_state(state);
let mut r = self.router.write().await;
*r = std::mem::take(&mut *r).nest(path, route);
}
/// Ajoute un handler POST avec state
///
/// Similaire à `add_handler_with_state` mais pour les requêtes POST.
///
/// # Arguments
///
/// * `path` - Chemin de la route
/// * `handler` - Handler Axum pour POST
/// * `state` - État partagé
pub async fn add_post_handler_with_state<H, T, S>(&mut self, path: &str, handler: H, state: S)
where
H: Handler<T, S>,
T: 'static,
S: Clone + Send + Sync + 'static,
{
let route = Router::new()
.route("/", axum::routing::post(handler))
.with_state(state);
let mut r = self.router.write().await;
*r = std::mem::take(&mut *r).nest(path, route);
std::mem::take(&mut *r).nest(path, route)
};
}
/// Ajoute une redirection HTTP
@@ -393,23 +282,20 @@ impl Server {
/// server.add_redirect("/", "/app").await;
/// # }
/// ```
pub async fn add_redirect(&mut self, from: &str, to: &str) {
let to = to.to_string();
let handler = move || {
let to = to.clone();
async move { Redirect::permanent(&to) }
let make_handler = || {
let target = to.clone();
get(move || async move { Redirect::permanent(&target) })
};
let mut r = self.router.write().await;
if from == "/" {
// Pour la racine, utiliser merge au lieu de nest
let route = Router::new().route("/", get(handler));
*r = std::mem::take(&mut *r).merge(route);
*r = if from == "/" {
std::mem::take(&mut *r).merge(Router::new().route("/", make_handler()))
} else {
let route = Router::new().route("/", get(handler));
*r = std::mem::take(&mut *r).nest(from, route);
}
std::mem::take(&mut *r).nest(from, Router::new().route("/", make_handler()))
};
}
/// Ajoute une API documentée avec OpenAPI et Swagger UI
@@ -481,21 +367,16 @@ impl Server {
/// - `/api/api1/users` et `/api/api2/products` sont accessibles via Axum.
/// - `/swagger-ui/api1` et `/swagger-ui/api2` affichent la documentation Swagger correspondante.
/// - `/api-docs/api1.json` et `/api-docs/api2.json` fournissent les spécifications OpenAPI respectives.
pub async fn add_openapi(
&mut self,
api_router: Router,
openapi: utoipa::openapi::OpenApi,
name: &str, // nom unique pour différencier Swagger et OpenAPI
name: &str,
) {
use axum::routing::get;
use utoipa_swagger_ui::SwaggerUi;
// Stocker le router API dans self.api_router si tu veux y accéder plus tard
let mut api_r = self.api_router.write().await;
*api_r = Some(api_router.clone());
drop(api_r);
// Générer des chemins uniques pour Swagger UI et OpenAPI JSON
let swagger_path = format!("/swagger-ui/{}", name);
let swagger_path_static: &'static str = Box::leak(swagger_path.into_boxed_str());
@@ -504,16 +385,31 @@ impl Server {
let swagger = SwaggerUi::new(swagger_path_static).url(openapi_json_path_static, openapi);
// Nester le router API sous /api/{name}
let base_path = format!("/api/{}", name);
let nested_router = Router::new().nest(&base_path, api_router);
// Fusionner avec le router principal
let mut r = self.router.write().await;
let mut combined = std::mem::take(&mut *r);
combined = combined.merge(nested_router).merge(swagger);
*r = std::mem::take(&mut *r).merge(nested_router).merge(swagger);
}
/// Ajoute un sous-router au serveur
///
/// - Si `path` est "/", merge directement au router principal
/// - Sinon, nest le router sous le chemin donné
pub async fn add_router(&mut self, path: &str, sub_router: Router) {
let mut r = self.router.write().await;
let combined = if path == "/" {
// Merge directement à la racine
r.clone().merge(sub_router)
} else {
// Sous-chemin => nest
let normalized = format!("/{}", path.trim_start_matches('/'));
r.clone().nest(&normalized, sub_router)
};
*r = combined;
}
/// Démarre le serveur HTTP
///
/// Lance le serveur sur le port configuré et met en place la gestion
@@ -537,16 +433,7 @@ impl Server {
self.name, self.base_url, self.http_port, self.base_url, self.http_port
);
// Merger le routeur API si présent
let api_router = self.api_router.read().await;
if let Some(api_r) = api_router.as_ref() {
let mut r = self.router.write().await;
*r = std::mem::take(&mut *r).nest("/api", api_r.clone());
}
drop(api_router);
let router = self.router.clone();
let server_task = tokio::spawn(async move {
let r = router.read().await.clone();
let listener = tokio::net::TcpListener::bind(addr).await.unwrap();