1use serde::{Deserialize, Serialize};
8
9#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
13pub struct LoginRequest {
14 pub username: String,
15 pub password: String,
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
20pub struct LoginResponse {
21 pub session_id: i64,
22 pub username: String,
23 #[serde(default)]
24 pub expires_at: Option<u64>,
25 #[serde(default, skip_serializing_if = "Option::is_none")]
26 pub role: Option<String>,
27 #[serde(default, skip_serializing_if = "Option::is_none")]
28 pub display_role: Option<String>,
29 #[serde(default)]
30 pub roles: Vec<String>,
31 #[serde(default, skip_serializing_if = "Option::is_none")]
32 pub must_change_password: Option<bool>,
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
37pub struct AuthMeResponse {
38 pub username: String,
39 #[serde(default, skip_serializing_if = "Option::is_none")]
40 pub role: Option<String>,
41 #[serde(default, skip_serializing_if = "Option::is_none")]
42 pub display_role: Option<String>,
43 #[serde(default)]
44 pub roles: Vec<String>,
45}
46
47#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
49pub struct UserListItem {
50 pub username: String,
51 #[serde(default, skip_serializing_if = "Option::is_none")]
52 pub role: Option<String>,
53 #[serde(default, skip_serializing_if = "Option::is_none")]
54 pub status: Option<String>,
55 #[serde(default, skip_serializing_if = "Option::is_none")]
56 pub last_active: Option<String>,
57}
58
59#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
61pub struct UserListResponse {
62 #[serde(default)]
63 pub users: Vec<UserListItem>,
64}
65
66#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
68pub struct LogoutRequest {
69 pub session_id: i64,
70}
71
72#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
74pub struct CreateUserRequest {
75 pub username: String,
76 pub password: String,
77}
78
79#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
81pub struct ResetPasswordRequest {
82 pub password: String,
83}
84
85#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
87pub struct GrantRoleRequest {
88 pub role: String,
89 pub space: String,
90}
91
92#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
94pub struct RevokeRoleRequest {
95 pub space: String,
96}
97
98#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
102pub struct CreateSessionRequest {
103 pub username: String,
104 pub client_ip: String,
105 #[serde(default)]
108 pub password: Option<String>,
109}
110
111#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
113pub struct SessionResponse {
114 pub session_id: i64,
115 pub username: String,
116 #[serde(default)]
117 pub created_at: u64,
118}
119
120#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
124pub struct BeginTransactionRequest {
125 #[serde(default)]
126 pub read_only: bool,
127 #[serde(default)]
128 pub timeout_seconds: Option<u64>,
129 #[serde(default)]
130 pub query_timeout_seconds: Option<u64>,
131 #[serde(default)]
132 pub statement_timeout_seconds: Option<u64>,
133 #[serde(default)]
134 pub idle_timeout_seconds: Option<u64>,
135 #[serde(default)]
137 pub isolation_level: Option<String>,
138}
139
140#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
142pub struct TransactionResponse {
143 pub transaction_id: u64,
144 pub status: String,
145}
146
147#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
149pub struct TransactionActionRequest {
150 pub session_id: i64,
151}
152
153#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
157pub struct UpdateConfigRequest {
158 pub section: String,
159 pub key: String,
160 pub value: serde_json::Value,
161}
162
163#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
165pub struct ServerConfig {
166 pub version: String,
167 #[serde(default)]
168 pub sections: Vec<ConfigSection>,
169}
170
171#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
173pub struct ConfigSection {
174 pub name: String,
175 #[serde(default)]
176 pub description: Option<String>,
177 #[serde(default)]
178 pub items: Vec<ConfigItem>,
179}
180
181#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
183pub struct ConfigItem {
184 pub key: String,
185 pub value: serde_json::Value,
186 #[serde(default)]
187 pub default_value: Option<serde_json::Value>,
188 #[serde(default)]
189 pub description: Option<String>,
190 #[serde(default)]
191 pub mutable: bool,
192}
193
194#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
198pub struct SessionStatistics {
199 pub total_queries: u64,
200 pub total_changes: u64,
201 pub avg_execution_time_ms: f64,
202}
203
204#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
206pub struct QueryTypeStatistics {
207 pub match_queries: u64,
208 pub create_queries: u64,
209 pub update_queries: u64,
210 pub delete_queries: u64,
211 pub insert_queries: u64,
212 pub go_queries: u64,
213 pub fetch_queries: u64,
214 pub lookup_queries: u64,
215 pub show_queries: u64,
216}
217
218#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
220pub struct QueryStatistics {
221 pub total_queries: u64,
222 #[serde(default)]
223 pub slow_queries: Vec<SlowQueryInfo>,
224 pub query_types: QueryTypeStatistics,
225}
226
227#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
229pub struct SlowQueryInfo {
230 pub trace_id: String,
231 pub session_id: i64,
232 pub query: String,
233 pub duration_ms: f64,
234 pub status: String,
235 #[serde(default, skip_serializing_if = "Option::is_none")]
236 pub started_at_secs: Option<u64>,
237 #[serde(default, skip_serializing_if = "Option::is_none")]
238 pub stages: Option<crate::query::QueryStageTimings>,
239 #[serde(default, skip_serializing_if = "Option::is_none")]
240 pub plan_node_count: Option<usize>,
241}
242
243#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
245pub struct DatabaseStatistics {
246 pub space_count: i64,
247 pub total_vertices: i64,
248 pub total_edges: i64,
249 pub total_queries: u64,
250 pub active_queries: u64,
251 pub queries_per_second: f64,
252 pub avg_latency_ms: f64,
253}
254
255#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
257pub struct MemoryUsage {
258 pub used_bytes: u64,
259 pub total_bytes: u64,
260}
261
262#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
264pub struct ConnectionStats {
265 pub active: usize,
266 pub total: usize,
267 pub max: usize,
268}
269
270#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
272pub struct LatencyPercentilesUs {
273 pub avg: u64,
274 pub p50: u64,
275 pub p95: u64,
276 pub p99: u64,
277}
278
279#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
281pub struct SystemResourceResponse {
282 pub cpu_usage_percent: f64,
283 pub memory_usage: MemoryUsage,
284 pub connections: ConnectionStats,
285 #[serde(default, skip_serializing_if = "Option::is_none")]
286 pub process_memory_bytes: Option<u64>,
287 #[serde(default, skip_serializing_if = "Option::is_none")]
288 pub uptime_secs: Option<u64>,
289 #[serde(default, skip_serializing_if = "Option::is_none")]
290 pub data_dir_size_bytes: Option<u64>,
291 #[serde(default, skip_serializing_if = "Option::is_none")]
292 pub wal_dir_size_bytes: Option<u64>,
293 #[serde(default, skip_serializing_if = "Option::is_none")]
294 pub max_file_descriptors: Option<u64>,
295}
296
297#[derive(Debug, Clone, Serialize, Deserialize, Default, utoipa::ToSchema)]
299pub struct CheckpointSummary {
300 #[serde(default)]
301 pub success_count: u64,
302 #[serde(default)]
303 pub failure_count: u64,
304 #[serde(default)]
305 pub trigger_count: u64,
306 #[serde(default)]
307 pub avg_duration_us: f64,
308 #[serde(default)]
309 pub triggered_by_wal_size: u64,
310 #[serde(default)]
311 pub triggered_by_interval: u64,
312 #[serde(default)]
313 pub triggered_explicit: u64,
314}
315
316#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
318pub struct DatabaseOverviewResponse {
319 pub spaces: DatabaseSpaces,
320 pub storage: DatabaseStorage,
321 pub performance: DatabasePerformance,
322 pub search: DatabaseSearchSummary,
323}
324
325#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
326pub struct DatabaseSpaces {
327 pub count: usize,
328 pub total_vertices: usize,
329 pub total_edges: usize,
330}
331
332#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
333pub struct DatabaseStorage {
334 pub total_size_bytes: u64,
335 pub index_size_bytes: u64,
336 pub data_size_bytes: u64,
337 #[serde(default)]
338 pub fragmentation_permille: u64,
339 #[serde(default)]
340 pub wasted_bytes: u64,
341 #[serde(default)]
342 pub tombstone_count: u64,
343 #[serde(default)]
344 pub tombstone_memory_bytes: u64,
345 #[serde(default)]
346 pub dirty_pages: u64,
347 #[serde(default)]
348 pub dirty_pages_total: u64,
349 #[serde(default)]
350 pub checkpoint: CheckpointSummary,
351}
352
353#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
354pub struct DatabasePerformance {
355 pub total_queries: u64,
356 pub active_queries: u64,
357 pub query_cache_size: usize,
358 pub queries_per_second: f64,
359 pub avg_latency_ms: f64,
360 pub cache_hit_rate: f64,
361 #[serde(default)]
362 pub cache_hit_rate_source: String,
363 #[serde(default)]
364 pub error_total: u64,
365 #[serde(default)]
366 pub latency_percentiles_us: Option<LatencyPercentilesUs>,
367}
368
369#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
370pub struct DatabaseSearchSummary {
371 pub total_queries: u64,
372 pub total_errors: u64,
373 pub avg_latency_ms: f64,
374 pub total_index_operations: u64,
375 pub total_delete_operations: u64,
376 pub cache_hit_count: u64,
377 pub cache_miss_count: u64,
378 pub cache_hit_rate: f64,
379}
380
381#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
383pub struct QueryPatternEntry {
384 pub normalized_query: String,
385 pub query_type: String,
386 #[serde(default)]
387 pub labels: Vec<String>,
388 pub execution_count: u64,
389 pub avg_duration_ms: f64,
390 pub p95_duration_ms: f64,
391 pub p99_duration_ms: f64,
392 pub error_rate: f64,
393 #[serde(default)]
394 pub error_count: u64,
395}
396
397#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
399pub struct ExecutorSummaryEntry {
400 pub executor_type: String,
401 pub count: u64,
402 pub total_time_ms: u64,
403 pub total_rows: u64,
404 pub avg_time_ms: f64,
405}
406
407#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
409pub struct QueryStatsResponse {
410 pub total_queries: u64,
411 pub slow_queries: Vec<SlowQueryInfo>,
412 pub query_types: QueryTypeStatistics,
413 #[serde(default)]
414 pub errors_by_type: std::collections::HashMap<String, u64>,
415 #[serde(default)]
416 pub errors_by_phase: std::collections::HashMap<String, u64>,
417 #[serde(default)]
418 pub error_total: u64,
419 #[serde(default)]
420 pub top_patterns: Vec<QueryPatternEntry>,
421 #[serde(default)]
422 pub executor_summary: Vec<ExecutorSummaryEntry>,
423 #[serde(default)]
424 pub latency_percentiles_us: Option<LatencyPercentilesUs>,
425 #[serde(default)]
426 pub from: Option<String>,
427 #[serde(default)]
428 pub to: Option<String>,
429}
430
431#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
433pub struct SearchIndexEntry {
434 pub index: String,
435 pub search_queries: u64,
436 pub search_errors: u64,
437 pub search_latency_ms: u64,
438 pub avg_search_latency_ms: f64,
439 pub index_operations: u64,
440 pub index_errors: u64,
441 pub index_latency_ms: u64,
442}
443
444#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
446pub struct SearchStatsResponse {
447 pub search: SearchSection,
448 pub index: IndexSection,
449 pub delete: DeleteSection,
450 pub cache: CacheSection,
451 #[serde(default)]
452 pub by_index: Vec<SearchIndexEntry>,
453}
454
455#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
456pub struct SearchSection {
457 pub total_queries: u64,
458 pub total_errors: u64,
459 pub total_latency_ms: u64,
460 pub avg_latency_ms: f64,
461 pub total_results: u64,
462 pub latency_percentiles_us: LatencyPercentilesUs,
463}
464
465#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
466pub struct IndexSection {
467 pub total_operations: u64,
468 pub total_errors: u64,
469 pub total_latency_ms: u64,
470 pub avg_latency_ms: f64,
471}
472
473#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
474pub struct DeleteSection {
475 pub total_operations: u64,
476 pub total_errors: u64,
477 pub total_latency_ms: u64,
478 pub avg_latency_ms: f64,
479}
480
481#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
482pub struct CacheSection {
483 pub hit_count: u64,
484 pub miss_count: u64,
485 pub hit_rate: f64,
486}
487
488#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
490pub struct OverviewResponse {
491 pub system: SystemResourceResponse,
492 pub database: DatabaseOverviewResponse,
493 pub query_latency_us: LatencyPercentilesUs,
494 pub errors: OverviewErrors,
495 pub storage: OverviewStorage,
496 pub transaction: OverviewTransaction,
497 pub sync: OverviewSync,
498 pub timeseries: Vec<OverviewTimeseriesPoint>,
499}
500
501#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
503pub struct OverviewSync {
504 pub is_running: bool,
505 pub outbox_pending: usize,
506 pub outbox_retries: u64,
507 pub outbox_dead_lettered: usize,
508}
509
510#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
511pub struct OverviewErrors {
512 pub total: u64,
513}
514
515#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
516pub struct OverviewStorage {
517 pub read_ops: u64,
518 pub write_ops: u64,
519 pub fragmentation_permille: u64,
520 pub tombstone_count: u64,
521 pub checkpoint_success: u64,
522 pub checkpoint_failure: u64,
523}
524
525#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
526pub struct OverviewTransaction {
527 pub begun: u64,
528 pub committed: u64,
529 pub rolled_back: u64,
530 pub active: u64,
531}
532
533#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
534pub struct OverviewTimeseriesPoint {
535 pub second: u64,
536 pub queries: u64,
537 pub avg_latency_ms: f64,
538 pub errors: u64,
539}
540
541#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
543pub struct QueryProfileDetailResponse {
544 pub trace_id: String,
545 pub session_id: i64,
546 pub query: String,
547 pub duration_ms: f64,
548 pub status: String,
549 #[serde(default, skip_serializing_if = "Option::is_none")]
550 pub error: Option<String>,
551 #[serde(default, skip_serializing_if = "Option::is_none")]
552 pub stages: Option<crate::query::QueryStageTimings>,
553 #[serde(default)]
554 pub executors: Vec<QueryProfileExecutor>,
555 #[serde(default, skip_serializing_if = "Option::is_none")]
556 pub result_count: Option<usize>,
557 #[serde(default, skip_serializing_if = "Option::is_none")]
558 pub plan_node_count: Option<usize>,
559}
560
561#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
562pub struct QueryProfileExecutor {
563 pub executor_type: String,
564 pub duration_ms: f64,
565 pub rows: usize,
566 pub memory_bytes: usize,
567}
568
569#[cfg(test)]
570mod tests {
571 use super::*;
572
573 #[test]
574 fn login_roundtrip() {
575 let request = LoginRequest {
576 username: "root".to_string(),
577 password: "secret".to_string(),
578 };
579 let json = serde_json::to_string(&request).unwrap();
580 let back: LoginRequest = serde_json::from_str(&json).unwrap();
581 assert_eq!(back.username, "root");
582 }
583
584 #[test]
585 fn transaction_response_roundtrip() {
586 let response = TransactionResponse {
587 transaction_id: 42,
588 status: "Active".to_string(),
589 };
590 let json = serde_json::to_string(&response).unwrap();
591 let back: TransactionResponse = serde_json::from_str(&json).unwrap();
592 assert_eq!(back.transaction_id, 42);
593 assert_eq!(back.status, "Active");
594 }
595
596 #[test]
597 fn begin_transaction_request_defaults() {
598 let back: BeginTransactionRequest = serde_json::from_str(r#"{"read_only": true}"#).unwrap();
599 assert!(back.read_only);
600 assert!(back.timeout_seconds.is_none());
601 assert!(back.isolation_level.is_none());
602 }
603
604 #[test]
605 fn server_config_roundtrip() {
606 let config = ServerConfig {
607 version: "0.1.0".to_string(),
608 sections: vec![ConfigSection {
609 name: "database".to_string(),
610 description: None,
611 items: vec![ConfigItem {
612 key: "host".to_string(),
613 value: serde_json::json!("127.0.0.1"),
614 default_value: None,
615 description: None,
616 mutable: true,
617 }],
618 }],
619 };
620 let json = serde_json::to_string(&config).unwrap();
621 let back: ServerConfig = serde_json::from_str(&json).unwrap();
622 assert_eq!(back.version, "0.1.0");
623 assert_eq!(back.sections[0].items[0].key, "host");
624 }
625
626 #[test]
627 fn query_statistics_roundtrip() {
628 let stats = QueryStatistics {
629 total_queries: 10,
630 slow_queries: Vec::new(),
631 query_types: QueryTypeStatistics {
632 match_queries: 4,
633 create_queries: 0,
634 update_queries: 0,
635 delete_queries: 0,
636 insert_queries: 6,
637 go_queries: 0,
638 fetch_queries: 0,
639 lookup_queries: 0,
640 show_queries: 0,
641 },
642 };
643 let json = serde_json::to_string(&stats).unwrap();
644 let back: QueryStatistics = serde_json::from_str(&json).unwrap();
645 assert_eq!(back.total_queries, 10);
646 assert_eq!(back.query_types.match_queries, 4);
647 }
648
649 #[test]
650 fn update_config_request_roundtrip() {
651 let request = UpdateConfigRequest {
652 section: "database".to_string(),
653 key: "port".to_string(),
654 value: serde_json::json!(9090),
655 };
656 let json = serde_json::to_string(&request).unwrap();
657 let back: UpdateConfigRequest = serde_json::from_str(&json).unwrap();
658 assert_eq!(back.section, "database");
659 assert_eq!(back.key, "port");
660 assert_eq!(back.value, serde_json::json!(9090));
661 }
662}