use crate::Config; use axum::{ extract::{Path, State}, http::StatusCode, response::{IntoResponse, Response}, routing::{get, post}, Json, Router, }; use serde::{Deserialize, Serialize}; use serde_json::Value as JsonValue; use serde_yaml::Value; use std::sync::Arc; /// Structure pour récupérer une valeur de configuration #[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)] pub struct ConfigValue { /// Chemin de la clé (ex: "host.http_port") pub path: String, /// Valeur au format JSON pub value: JsonValue, } /// Structure pour mettre à jour une valeur de configuration #[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)] pub struct UpdateConfigRequest { /// Chemin de la clé (ex: "host.http_port") pub path: String, /// Nouvelle valeur au format JSON pub value: JsonValue, } /// Structure pour la réponse d'une mise à jour #[derive(Debug, Serialize, Deserialize, utoipa::ToSchema)] pub struct UpdateConfigResponse { pub success: bool, pub message: String, } /// Erreur API #[derive(Debug)] pub struct ApiError(anyhow::Error); impl IntoResponse for ApiError { fn into_response(self) -> Response { ( StatusCode::INTERNAL_SERVER_ERROR, Json(serde_json::json!({ "error": self.0.to_string() })), ) .into_response() } } impl From for ApiError where E: Into, { fn from(err: E) -> Self { Self(err.into()) } } /// GET /api/config - Récupérer toute la configuration #[utoipa::path( get, path = "/api/config", tag = "config", responses( (status = 200, description = "Configuration complète", body = serde_json::Value) ) )] async fn get_full_config(State(config): State>) -> Result, ApiError> { let value = config.get_value(&[])?; let json_value = yaml_to_json(&value)?; Ok(Json(json_value)) } /// GET /api/config/{path} - Récupérer une valeur à un chemin spécifique #[utoipa::path( get, path = "/api/config/{path}", tag = "config", params( ("path" = String, Path, description = "Chemin de la configuration (séparé par des points, ex: host.http_port)") ), responses( (status = 200, description = "Valeur de configuration", body = ConfigValue), (status = 404, description = "Chemin non trouvé") ) )] async fn get_config_value( State(config): State>, Path(path): Path, ) -> Result, ApiError> { let path_parts: Vec<&str> = path.split('.').collect(); let value = config.get_value(&path_parts)?; let json_value = yaml_to_json(&value)?; Ok(Json(ConfigValue { path, value: json_value, })) } /// POST /api/config - Mettre à jour une valeur de configuration #[utoipa::path( post, path = "/api/config", tag = "config", request_body = UpdateConfigRequest, responses( (status = 200, description = "Configuration mise à jour", body = UpdateConfigResponse) ) )] async fn update_config_value( State(config): State>, Json(request): Json, ) -> Result, ApiError> { let path_parts: Vec<&str> = request.path.split('.').collect(); let yaml_value = json_to_yaml(&request.value)?; config.set_value(&path_parts, yaml_value)?; Ok(Json(UpdateConfigResponse { success: true, message: format!("Configuration updated at path: {}", request.path), })) } /// Convertit une valeur YAML en JSON fn yaml_to_json(yaml: &Value) -> Result { // Serialize YAML to string then parse as JSON let yaml_str = serde_yaml::to_string(yaml)?; Ok(serde_json::from_str(&yaml_str)?) } /// Convertit une valeur JSON en YAML fn json_to_yaml(json: &JsonValue) -> Result { // Serialize JSON to string then parse as YAML let json_str = serde_json::to_string(json)?; Ok(serde_yaml::from_str(&json_str)?) } /// Crée le router API pour la configuration pub fn create_router(config: Arc) -> Router { Router::new() .route("/api/config", get(get_full_config)) .route("/api/config", post(update_config_value)) .route("/api/config/:path", get(get_config_value)) .with_state(config) }