mockforge_http/management/
health.rs1use axum::{
2 extract::State,
3 http::StatusCode,
4 response::{IntoResponse, Json},
5};
6use serde::Deserialize;
7
8use super::{ManagementState, ServerConfig, ServerStats};
9
10#[derive(Debug, Deserialize)]
12pub struct ValidateConfigRequest {
13 pub config: serde_json::Value,
15 #[serde(default = "default_format")]
17 pub format: String,
18}
19
20fn default_format() -> String {
21 "json".to_string()
22}
23
24pub(crate) async fn validate_config(
26 Json(request): Json<ValidateConfigRequest>,
27) -> impl IntoResponse {
28 use mockforge_core::config::ServerConfig;
29
30 let config_result: Result<ServerConfig, String> = match request.format.as_str() {
31 "yaml" | "yml" => {
32 let yaml_str = match serde_json::to_string(&request.config) {
33 Ok(s) => s,
34 Err(e) => {
35 return (
36 StatusCode::BAD_REQUEST,
37 Json(serde_json::json!({
38 "valid": false,
39 "error": format!("Failed to convert to string: {}", e),
40 "message": "Configuration validation failed"
41 })),
42 )
43 .into_response();
44 }
45 };
46 serde_yaml::from_str(&yaml_str).map_err(|e| format!("YAML parse error: {}", e))
47 }
48 _ => serde_json::from_value(request.config).map_err(|e| format!("JSON parse error: {}", e)),
49 };
50
51 match config_result {
52 Ok(_) => Json(serde_json::json!({
53 "valid": true,
54 "message": "Configuration is valid"
55 }))
56 .into_response(),
57 Err(e) => (
58 StatusCode::BAD_REQUEST,
59 Json(serde_json::json!({
60 "valid": false,
61 "error": format!("Invalid configuration: {}", e),
62 "message": "Configuration validation failed"
63 })),
64 )
65 .into_response(),
66 }
67}
68
69#[derive(Debug, Deserialize)]
71pub struct BulkConfigUpdateRequest {
72 pub updates: serde_json::Value,
74}
75
76pub(crate) async fn bulk_update_config(
84 State(state): State<ManagementState>,
85 Json(request): Json<BulkConfigUpdateRequest>,
86) -> impl IntoResponse {
87 if !request.updates.is_object() {
89 return (
90 StatusCode::BAD_REQUEST,
91 Json(serde_json::json!({
92 "error": "Invalid request",
93 "message": "Updates must be a JSON object"
94 })),
95 )
96 .into_response();
97 }
98
99 use mockforge_core::config::ServerConfig;
101
102 let base_config = ServerConfig::default();
104 let base_json = match serde_json::to_value(&base_config) {
105 Ok(v) => v,
106 Err(e) => {
107 return (
108 StatusCode::INTERNAL_SERVER_ERROR,
109 Json(serde_json::json!({
110 "error": "Internal error",
111 "message": format!("Failed to serialize base config: {}", e)
112 })),
113 )
114 .into_response();
115 }
116 };
117
118 let mut merged = base_json.clone();
120 if let (Some(merged_obj), Some(updates_obj)) =
121 (merged.as_object_mut(), request.updates.as_object())
122 {
123 for (key, value) in updates_obj {
124 merged_obj.insert(key.clone(), value.clone());
125 }
126 }
127
128 match serde_json::from_value::<ServerConfig>(merged) {
130 Ok(validated_config) => {
131 if let Some(ref config_lock) = state.server_config {
133 let mut config = config_lock.write().await;
134 *config = validated_config;
135 Json(serde_json::json!({
136 "success": true,
137 "message": "Bulk configuration update applied successfully",
138 "updates_received": request.updates,
139 "validated": true,
140 "applied": true
141 }))
142 .into_response()
143 } else {
144 Json(serde_json::json!({
145 "success": true,
146 "message": "Bulk configuration update validated but not applied (no server config in state). Use .with_server_config() when building ManagementState.",
147 "updates_received": request.updates,
148 "validated": true,
149 "applied": false
150 }))
151 .into_response()
152 }
153 }
154 Err(e) => (
155 StatusCode::BAD_REQUEST,
156 Json(serde_json::json!({
157 "error": "Invalid configuration",
158 "message": format!("Configuration validation failed: {}", e),
159 "validated": false
160 })),
161 )
162 .into_response(),
163 }
164}
165
166pub(crate) async fn get_stats(State(state): State<ManagementState>) -> Json<ServerStats> {
168 let mocks = state.mocks.read().await;
169 let request_count = *state.request_counter.read().await;
170
171 Json(ServerStats {
172 uptime_seconds: state.start_time.elapsed().as_secs(),
173 total_requests: request_count,
174 active_mocks: mocks.len(),
175 enabled_mocks: mocks.iter().filter(|m| m.enabled).count(),
176 registered_routes: mocks.len(), })
178}
179
180pub(crate) async fn get_config(State(state): State<ManagementState>) -> Json<super::ServerConfig> {
182 Json(super::ServerConfig {
183 version: env!("CARGO_PKG_VERSION").to_string(),
184 port: state.port,
185 has_openapi_spec: state.spec.is_some(),
186 spec_path: state.spec_path.clone(),
187 })
188}
189
190pub(crate) async fn get_openapi_spec(
192 State(state): State<ManagementState>,
193) -> Result<Json<serde_json::Value>, StatusCode> {
194 match &state.spec {
195 Some(spec) => match &spec.raw_document {
196 Some(doc) => Ok(Json(doc.clone())),
197 None => {
198 match serde_json::to_value(&spec.spec) {
200 Ok(val) => Ok(Json(val)),
201 Err(_) => Err(StatusCode::INTERNAL_SERVER_ERROR),
202 }
203 }
204 },
205 None => Err(StatusCode::NOT_FOUND),
206 }
207}
208
209pub(crate) async fn health_check() -> Json<serde_json::Value> {
211 Json(serde_json::json!({
212 "status": "healthy",
213 "service": "mockforge-management",
214 "timestamp": chrono::Utc::now().to_rfc3339()
215 }))
216}
217
218pub(crate) async fn get_capabilities() -> Json<serde_json::Value> {
223 let mut features = vec!["core", "http", "management", "mocks", "proxy", "ai"];
224
225 #[cfg(feature = "smtp")]
226 features.push("smtp");
227 #[cfg(feature = "mqtt")]
228 features.push("mqtt");
229 #[cfg(feature = "kafka")]
230 features.push("kafka");
231 #[cfg(feature = "conformance")]
232 features.push("conformance");
233 #[cfg(feature = "behavioral-cloning")]
234 features.push("behavioral-cloning");
235
236 features.extend_from_slice(&[
238 "chaos",
239 "network-profiles",
240 "state-machines",
241 "migration",
242 "snapshot-diff",
243 "mockai",
244 ]);
245
246 Json(serde_json::json!({
247 "features": features,
248 "version": env!("CARGO_PKG_VERSION"),
249 }))
250}