use std::sync::Arc; #[cfg(feature = "server")] use axum::{ extract::{Extension, Path}, http::StatusCode, response::IntoResponse, Json, }; use serde_json::{json, Value}; use super::ApiResponse; use crate::core::VaultCore; /// Helper: Try reading with fallback path reconstruction #[cfg(feature = "server")] async fn try_fallback_read( vault: &Arc, full_path: &str, ) -> Option { for (mount_path, _) in vault.engines.iter() { let slash = if full_path.starts_with('/') { "" } else { "/" }; let reconstructed = format!("{}{}{}", mount_path, slash, full_path); let (_, relative_path) = vault.split_path(&reconstructed)?; let engine = vault.route_to_engine(&reconstructed)?; let engine_path = relative_path.trim_start_matches('/'); if let Ok(Some(data)) = engine.read(engine_path).await { let response = ApiResponse::success(data); return Some((StatusCode::OK, Json(response)).into_response()); } } None } /// GET /v1/* - Read a secret from any mounted engine #[cfg(feature = "server")] pub async fn read_secret( Extension(vault): Extension>, Path(path): Path, ) -> impl IntoResponse { let full_path = path; if let Some((_mount_path, relative_path)) = vault.split_path(&full_path) { if let Some(engine) = vault.route_to_engine(&full_path) { return match engine.read(&relative_path).await { Ok(Some(data)) => { let response = ApiResponse::success(data); (StatusCode::OK, Json(response)).into_response() } Ok(None) => { let response = ApiResponse::::error("Secret not found"); (StatusCode::NOT_FOUND, Json(response)).into_response() } Err(e) => { let response = ApiResponse::::error(format!("Failed to read: {}", e)); (StatusCode::INTERNAL_SERVER_ERROR, Json(response)).into_response() } }; } let response = ApiResponse::::error("No engine mounted at this path"); return (StatusCode::NOT_FOUND, Json(response)).into_response(); } // Try fallback path reconstruction if let Some(response) = try_fallback_read(&vault, &full_path).await { return response; } let response = ApiResponse::::error("Path not found"); (StatusCode::NOT_FOUND, Json(response)).into_response() } /// Helper: Try writing with fallback path reconstruction #[cfg(feature = "server")] async fn try_fallback_write( vault: &Arc, full_path: &str, payload: &Value, ) -> Option { for (mount_path, _) in vault.engines.iter() { let slash = if full_path.starts_with('/') { "" } else { "/" }; let reconstructed = format!("{}{}{}", mount_path, slash, full_path); let (_, relative_path) = vault.split_path(&reconstructed)?; let engine = vault.route_to_engine(&reconstructed)?; let engine_path = relative_path.trim_start_matches('/'); if engine.write(engine_path, payload).await.is_ok() { let response = ApiResponse::success(json!({"path": full_path})); return Some((StatusCode::OK, Json(response)).into_response()); } } None } /// POST /v1/* - Write a secret to any mounted engine #[cfg(feature = "server")] pub async fn write_secret( Extension(vault): Extension>, Path(path): Path, Json(payload): Json, ) -> impl IntoResponse { let full_path = path; if let Some((_mount_path, relative_path)) = vault.split_path(&full_path) { if let Some(engine) = vault.route_to_engine(&full_path) { return match engine.write(&relative_path, &payload).await { Ok(()) => { let response = ApiResponse::success(json!({"path": full_path})); (StatusCode::OK, Json(response)).into_response() } Err(e) => { let response = ApiResponse::::error(format!("Failed to write: {}", e)); (StatusCode::BAD_REQUEST, Json(response)).into_response() } }; } let response = ApiResponse::::error("No engine mounted at this path"); return (StatusCode::NOT_FOUND, Json(response)).into_response(); } // Try fallback path reconstruction if let Some(response) = try_fallback_write(&vault, &full_path, &payload).await { return response; } let response = ApiResponse::::error("Path not found"); (StatusCode::NOT_FOUND, Json(response)).into_response() } /// PUT /v1/* - Update a secret in any mounted engine #[cfg(feature = "server")] pub async fn update_secret( Extension(vault): Extension>, Path(path): Path, Json(payload): Json, ) -> impl IntoResponse { let full_path = path; match vault.split_path(&full_path) { Some((_mount_path, relative_path)) => match vault.route_to_engine(&full_path) { Some(engine) => match engine.write(&relative_path, &payload).await { Ok(()) => { let response = ApiResponse::success(json!({"path": full_path})); (StatusCode::OK, Json(response)).into_response() } Err(e) => { let response = ApiResponse::::error(format!("Failed to update: {}", e)); (StatusCode::BAD_REQUEST, Json(response)).into_response() } }, None => { let response = ApiResponse::::error("No engine mounted at this path"); (StatusCode::NOT_FOUND, Json(response)).into_response() } }, None => { let response = ApiResponse::::error("Path not found"); (StatusCode::NOT_FOUND, Json(response)).into_response() } } } /// DELETE /v1/* - Delete a secret from any mounted engine #[cfg(feature = "server")] pub async fn delete_secret( Extension(vault): Extension>, Path(path): Path, ) -> impl IntoResponse { let full_path = path; match vault.split_path(&full_path) { Some((_mount_path, relative_path)) => match vault.route_to_engine(&full_path) { Some(engine) => match engine.delete(&relative_path).await { Ok(()) => { let response: ApiResponse = ApiResponse::success(json!({})); (StatusCode::NO_CONTENT, Json(response)).into_response() } Err(e) => { let response = ApiResponse::::error(format!("Failed to delete: {}", e)); (StatusCode::BAD_REQUEST, Json(response)).into_response() } }, None => { let response = ApiResponse::::error("No engine mounted at this path"); (StatusCode::NOT_FOUND, Json(response)).into_response() } }, None => { let response = ApiResponse::::error("Path not found"); (StatusCode::NOT_FOUND, Json(response)).into_response() } } } /// LIST /v1/* - List secrets at a path prefix #[cfg(feature = "server")] pub async fn list_secrets( Extension(vault): Extension>, Path(path): Path, ) -> impl IntoResponse { let full_path = path; match vault.split_path(&full_path) { Some((_mount_path, relative_path)) => match vault.route_to_engine(&full_path) { Some(engine) => match engine.list(&relative_path).await { Ok(items) => { let response = ApiResponse::success(json!({"keys": items})); (StatusCode::OK, Json(response)).into_response() } Err(e) => { let response = ApiResponse::::error(format!("Failed to list: {}", e)); (StatusCode::BAD_REQUEST, Json(response)).into_response() } }, None => { let response = ApiResponse::::error("No engine mounted at this path"); (StatusCode::NOT_FOUND, Json(response)).into_response() } }, None => { let response = ApiResponse::::error("Path not found"); (StatusCode::NOT_FOUND, Json(response)).into_response() } } }