Skip to main content

linkrs_wire/
meta.rs

1//! Meta contract DTOs: auth, session, transaction, config, statistics and
2//! cold-snapshot endpoints.
3//!
4//! The CLI-side mirrors (`cli/client/{config_types,stats,snapshot,
5//! transaction}.rs` and parts of `request_types.rs`) are consolidated here.
6
7use serde::{Deserialize, Serialize};
8
9// ── Auth ──────────────────────────────────────────────────────────────────
10
11/// Login request
12#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
13pub struct LoginRequest {
14    pub username: String,
15    pub password: String,
16}
17
18/// Login response
19#[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/// Current user response for role-aware clients.
36#[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/// User list entry for the management view.
48#[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/// User list response for the management view.
60#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
61pub struct UserListResponse {
62    #[serde(default)]
63    pub users: Vec<UserListItem>,
64}
65
66/// Logout request
67#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
68pub struct LogoutRequest {
69    pub session_id: i64,
70}
71
72/// Create user request for the admin channel.
73#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
74pub struct CreateUserRequest {
75    pub username: String,
76    pub password: String,
77}
78
79/// Reset password request for the admin channel.
80#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
81pub struct ResetPasswordRequest {
82    pub password: String,
83}
84
85/// Grant role request for the admin channel.
86#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
87pub struct GrantRoleRequest {
88    pub role: String,
89    pub space: String,
90}
91
92/// Revoke role request for the admin channel.
93#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
94pub struct RevokeRoleRequest {
95    pub space: String,
96}
97
98// ── Session ───────────────────────────────────────────────────────────────
99
100/// Create session request
101#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
102pub struct CreateSessionRequest {
103    pub username: String,
104    pub client_ip: String,
105    /// Optional password. Required when authentication is enabled;
106    /// omitted or left empty when the server has disabled auth.
107    #[serde(default)]
108    pub password: Option<String>,
109}
110
111/// Session response
112#[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// ── Transaction ───────────────────────────────────────────────────────────
121
122/// Begin transaction request
123#[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    /// `repeatable_read` or `read_committed`.
136    #[serde(default)]
137    pub isolation_level: Option<String>,
138}
139
140/// Transaction response
141#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
142pub struct TransactionResponse {
143    pub transaction_id: u64,
144    pub status: String,
145}
146
147/// Transaction action request (commit/rollback body).
148#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
149pub struct TransactionActionRequest {
150    pub session_id: i64,
151}
152
153// ── Config ────────────────────────────────────────────────────────────────
154
155/// Update config request
156#[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/// Server configuration
164#[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/// Configuration section
172#[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/// Configuration item
182#[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// ── Statistics ────────────────────────────────────────────────────────────
195
196/// Statistics for a session
197#[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/// Query type statistics
205#[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/// Query statistics
219#[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/// Information about a slow query
228#[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/// Database statistics
244#[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/// Memory usage block shared by system responses.
256#[derive(Debug, Clone, Serialize, Deserialize, utoipa::ToSchema)]
257pub struct MemoryUsage {
258    pub used_bytes: u64,
259    pub total_bytes: u64,
260}
261
262/// Connection counters shared by system responses.
263#[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/// Latency percentiles in microseconds.
271#[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/// System resource response with stable keys plus process and disk detail.
280#[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/// Checkpoint summary embedded in database storage sections.
298#[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/// Database overview response mirroring the handler JSON keys.
317#[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/// Aggregated query pattern entry.
382#[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/// Executor rollup entry.
398#[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/// Query statistics response with time-window filtering and error breakdowns.
408#[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/// Per-index search breakdown entry.
432#[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/// Search statistics response with per-index detail.
445#[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/// Cross-subsystem overview for the monitoring first screen.
489#[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/// Sync summary embedded in the overview; mirrors the sync status shape.
502#[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/// Single query portrait detail for console jumps.
542#[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}