1use crate::{Error, Result};
7use reqwest::Client;
8use serde::{Deserialize, Serialize};
9use std::collections::HashMap;
10
11pub struct AdminClient {
13 base_url: String,
14 client: Client,
15}
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
19pub struct MockConfig {
20 #[serde(skip_serializing_if = "String::is_empty")]
22 pub id: String,
23 pub name: String,
25 pub method: String,
27 pub path: String,
29 pub response: MockResponse,
31 #[serde(default = "default_true")]
33 pub enabled: bool,
34 #[serde(skip_serializing_if = "Option::is_none")]
36 pub latency_ms: Option<u64>,
37 #[serde(skip_serializing_if = "Option::is_none")]
39 pub status_code: Option<u16>,
40}
41
42fn default_true() -> bool {
43 true
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct MockResponse {
49 pub body: serde_json::Value,
51 #[serde(skip_serializing_if = "Option::is_none")]
53 pub headers: Option<HashMap<String, String>>,
54}
55
56#[derive(Debug, Clone, Serialize, Deserialize)]
58pub struct ServerStats {
59 pub uptime_seconds: u64,
61 pub total_requests: u64,
63 pub active_mocks: usize,
65 pub enabled_mocks: usize,
67 pub registered_routes: usize,
69}
70
71#[derive(Debug, Clone, Serialize, Deserialize)]
73pub struct ServerConfig {
74 pub version: String,
76 pub port: u16,
78 pub has_openapi_spec: bool,
80 #[serde(skip_serializing_if = "Option::is_none")]
82 pub spec_path: Option<String>,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct MockList {
88 pub mocks: Vec<MockConfig>,
90 pub total: usize,
92 pub enabled: usize,
94}
95
96impl AdminClient {
97 pub fn new(base_url: impl Into<String>) -> Self {
112 let mut url = base_url.into();
113
114 while url.ends_with('/') {
116 url.pop();
117 }
118
119 Self {
120 base_url: url,
121 client: Client::new(),
122 }
123 }
124
125 pub async fn list_mocks(&self) -> Result<MockList> {
127 let url = format!("{}/api/mocks", self.base_url);
128 let response = self
129 .client
130 .get(&url)
131 .send()
132 .await
133 .map_err(|e| Error::General(format!("Failed to list mocks: {}", e)))?;
134
135 if !response.status().is_success() {
136 return Err(Error::General(format!(
137 "Failed to list mocks: HTTP {}",
138 response.status()
139 )));
140 }
141
142 response
143 .json()
144 .await
145 .map_err(|e| Error::General(format!("Failed to parse response: {}", e)))
146 }
147
148 pub async fn get_mock(&self, id: &str) -> Result<MockConfig> {
150 let url = format!("{}/api/mocks/{}", self.base_url, id);
151 let response = self
152 .client
153 .get(&url)
154 .send()
155 .await
156 .map_err(|e| Error::General(format!("Failed to get mock: {}", e)))?;
157
158 if response.status() == reqwest::StatusCode::NOT_FOUND {
159 return Err(Error::General(format!("Mock not found: {}", id)));
160 }
161
162 if !response.status().is_success() {
163 return Err(Error::General(format!("Failed to get mock: HTTP {}", response.status())));
164 }
165
166 response
167 .json()
168 .await
169 .map_err(|e| Error::General(format!("Failed to parse response: {}", e)))
170 }
171
172 pub async fn create_mock(&self, mock: MockConfig) -> Result<MockConfig> {
174 let url = format!("{}/api/mocks", self.base_url);
175 let response = self
176 .client
177 .post(&url)
178 .json(&mock)
179 .send()
180 .await
181 .map_err(|e| Error::General(format!("Failed to create mock: {}", e)))?;
182
183 if response.status() == reqwest::StatusCode::CONFLICT {
184 return Err(Error::General(format!("Mock with ID {} already exists", mock.id)));
185 }
186
187 if !response.status().is_success() {
188 return Err(Error::General(format!(
189 "Failed to create mock: HTTP {}",
190 response.status()
191 )));
192 }
193
194 response
195 .json()
196 .await
197 .map_err(|e| Error::General(format!("Failed to parse response: {}", e)))
198 }
199
200 pub async fn update_mock(&self, id: &str, mock: MockConfig) -> Result<MockConfig> {
202 let url = format!("{}/api/mocks/{}", self.base_url, id);
203 let response = self
204 .client
205 .put(&url)
206 .json(&mock)
207 .send()
208 .await
209 .map_err(|e| Error::General(format!("Failed to update mock: {}", e)))?;
210
211 if response.status() == reqwest::StatusCode::NOT_FOUND {
212 return Err(Error::General(format!("Mock not found: {}", id)));
213 }
214
215 if !response.status().is_success() {
216 return Err(Error::General(format!(
217 "Failed to update mock: HTTP {}",
218 response.status()
219 )));
220 }
221
222 response
223 .json()
224 .await
225 .map_err(|e| Error::General(format!("Failed to parse response: {}", e)))
226 }
227
228 pub async fn delete_mock(&self, id: &str) -> Result<()> {
230 let url = format!("{}/api/mocks/{}", self.base_url, id);
231 let response = self
232 .client
233 .delete(&url)
234 .send()
235 .await
236 .map_err(|e| Error::General(format!("Failed to delete mock: {}", e)))?;
237
238 if response.status() == reqwest::StatusCode::NOT_FOUND {
239 return Err(Error::General(format!("Mock not found: {}", id)));
240 }
241
242 if !response.status().is_success() {
243 return Err(Error::General(format!(
244 "Failed to delete mock: HTTP {}",
245 response.status()
246 )));
247 }
248
249 Ok(())
250 }
251
252 pub async fn get_stats(&self) -> Result<ServerStats> {
254 let url = format!("{}/api/stats", self.base_url);
255 let response = self
256 .client
257 .get(&url)
258 .send()
259 .await
260 .map_err(|e| Error::General(format!("Failed to get stats: {}", e)))?;
261
262 if !response.status().is_success() {
263 return Err(Error::General(format!("Failed to get stats: HTTP {}", response.status())));
264 }
265
266 response
267 .json()
268 .await
269 .map_err(|e| Error::General(format!("Failed to parse response: {}", e)))
270 }
271
272 pub async fn get_config(&self) -> Result<ServerConfig> {
274 let url = format!("{}/api/config", self.base_url);
275 let response = self
276 .client
277 .get(&url)
278 .send()
279 .await
280 .map_err(|e| Error::General(format!("Failed to get config: {}", e)))?;
281
282 if !response.status().is_success() {
283 return Err(Error::General(format!(
284 "Failed to get config: HTTP {}",
285 response.status()
286 )));
287 }
288
289 response
290 .json()
291 .await
292 .map_err(|e| Error::General(format!("Failed to parse response: {}", e)))
293 }
294
295 pub async fn reset(&self) -> Result<()> {
297 let url = format!("{}/api/reset", self.base_url);
298 let response = self
299 .client
300 .post(&url)
301 .send()
302 .await
303 .map_err(|e| Error::General(format!("Failed to reset mocks: {}", e)))?;
304
305 if !response.status().is_success() {
306 return Err(Error::General(format!(
307 "Failed to reset mocks: HTTP {}",
308 response.status()
309 )));
310 }
311
312 Ok(())
313 }
314}
315
316pub struct MockConfigBuilder {
318 config: MockConfig,
319}
320
321impl MockConfigBuilder {
322 pub fn new(method: impl Into<String>, path: impl Into<String>) -> Self {
324 Self {
325 config: MockConfig {
326 id: String::new(),
327 name: String::new(),
328 method: method.into().to_uppercase(),
329 path: path.into(),
330 response: MockResponse {
331 body: serde_json::json!({}),
332 headers: None,
333 },
334 enabled: true,
335 latency_ms: None,
336 status_code: None,
337 },
338 }
339 }
340
341 pub fn id(mut self, id: impl Into<String>) -> Self {
343 self.config.id = id.into();
344 self
345 }
346
347 pub fn name(mut self, name: impl Into<String>) -> Self {
349 self.config.name = name.into();
350 self
351 }
352
353 pub fn body(mut self, body: serde_json::Value) -> Self {
355 self.config.response.body = body;
356 self
357 }
358
359 pub fn status(mut self, status: u16) -> Self {
361 self.config.status_code = Some(status);
362 self
363 }
364
365 pub fn headers(mut self, headers: HashMap<String, String>) -> Self {
367 self.config.response.headers = Some(headers);
368 self
369 }
370
371 pub fn header(mut self, key: impl Into<String>, value: impl Into<String>) -> Self {
373 let headers = self.config.response.headers.get_or_insert_with(HashMap::new);
374 headers.insert(key.into(), value.into());
375 self
376 }
377
378 pub fn latency_ms(mut self, ms: u64) -> Self {
380 self.config.latency_ms = Some(ms);
381 self
382 }
383
384 pub fn enabled(mut self, enabled: bool) -> Self {
386 self.config.enabled = enabled;
387 self
388 }
389
390 pub fn build(self) -> MockConfig {
392 self.config
393 }
394}
395
396#[cfg(test)]
397mod tests {
398 use super::*;
399
400 #[test]
401 fn test_mock_config_builder() {
402 let mock = MockConfigBuilder::new("GET", "/api/users")
403 .name("Get Users")
404 .status(200)
405 .body(serde_json::json!([{"id": 1, "name": "Alice"}]))
406 .latency_ms(100)
407 .header("Content-Type", "application/json")
408 .build();
409
410 assert_eq!(mock.method, "GET");
411 assert_eq!(mock.path, "/api/users");
412 assert_eq!(mock.name, "Get Users");
413 assert_eq!(mock.status_code, Some(200));
414 assert_eq!(mock.latency_ms, Some(100));
415 assert!(mock.enabled);
416 }
417}