Files
pmomusic/pmoconfig/src/api.rs

153 lines
4.3 KiB
Rust
Raw Normal View History

2025-10-21 18:37:56 +02:00
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<E> From<E> for ApiError
where
E: Into<anyhow::Error>,
{
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<Arc<Config>>) -> Result<Json<JsonValue>, ApiError> {
2025-10-21 18:37:56 +02:00
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<Arc<Config>>,
Path(path): Path<String>,
) -> Result<Json<ConfigValue>, 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<Arc<Config>>,
Json(request): Json<UpdateConfigRequest>,
) -> Result<Json<UpdateConfigResponse>, 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<JsonValue, ApiError> {
// 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<Value, ApiError> {
// 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<Config>) -> 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)
}