Mountain/IPC/Message/Types.rs
1//! # Message Types (IPC)
2//!
3//! ## RESPONSIBILITIES
4//! This module defines the core data structures used for IPC communication
5//! between Wind (frontend) and Mountain (backend). It provides type-safe
6//! message formats that are serialized/deserialized for transport across the
7//! IPC boundary.
8//!
9//! ## ARCHITECTURAL ROLE
10//! This module defines the contract for all IPC messages. It's the foundation
11//! of the IPC communication layer, ensuring type safety and consistency across
12//! the Wind-Mountain bridge.
13//!
14//! ## KEY COMPONENTS
15//!
16//! - **TauriIPCMessage**: Standard message format for all IPC communication
17//! - **ConnectionStatus**: Connection health status reporting
18//! - **ListenerCallback**: Type definition for message event listeners
19//!
20//! ## ERROR HANDLING
21//! Message types use serde for serialization/deserialization. Invalid messages
22//! will fail to parse with descriptive error messages.
23//!
24//! ## LOGGING
25//! Debug-level logging for message metadata, trace for detailed message
26//! inspection.
27//!
28//! ## PERFORMANCE CONSIDERATIONS
29//! - Messages use efficient serde_json::Value for flexible data payloads
30//! - Timestamp uses u64 for compact representation
31//! - Option<> used for optional fields to minimize serialization overhead
32//!
33//! ## TODO
34//! - Add message payload size limits
35//! - Implement message versioning for compatibility
36//! - Add message priority field
37
38use serde::{Deserialize, Serialize};
39
40/// IPC message structure matching Wind's ITauriIPCMessage interface
41///
42/// This is the standard message format for all communication between Wind
43/// (TypeScript frontend) and Mountain (Rust backend).
44///
45/// ## Message Flow
46///
47/// ```text
48/// Wind Frontend
49/// |
50/// | 2. Serialize to JSON
51/// v
52/// Tauri Bridge (Webview)
53/// |
54/// | 1. Create TauriIPCMessage
55/// v
56/// TauriIPCServer (Rust)
57/// |
58/// | 3. Deserialize and route
59/// v
60/// Mountain Services
61/// ```
62///
63/// ## Example Usage
64///
65/// ```rust,ignore
66/// let message = TauriIPCMessage {
67/// channel: "mountain_file_read".to_string(),
68/// data: serde_json::json!({ "path": "/path/to/file" }),
69/// sender: Some("wind-frontend".to_string()),
70/// timestamp: SystemTime::now()
71/// .duration_since(UNIX_EPOCH)
72/// .unwrap()
73/// .as_millis() as u64,
74/// };
75/// ```
76#[derive(Debug, Clone, Serialize, Deserialize)]
77pub struct TauriIPCMessage {
78 /// IPC channel identifier that determines which handler processes the
79 /// message
80 pub channel:String,
81
82 /// Message payload data in flexible JSON format
83 pub data:serde_json::Value,
84
85 /// Optional sender identifier for tracking message origin
86 pub sender:Option<String>,
87
88 /// Unix timestamp in milliseconds for message ordering and debugging
89 pub timestamp:u64,
90}
91
92impl TauriIPCMessage {
93 /// Create a new IPC message
94 ///
95 /// ## Parameters
96 /// - `channel`: The IPC channel name
97 /// - `data`: The message payload
98 /// - `sender`: Optional sender identifier
99 ///
100 /// ## Returns
101 /// A new TauriIPCMessage with current timestamp
102 pub fn new(channel:String, data:serde_json::Value, sender:Option<String>) -> Self {
103 Self {
104 channel,
105 data,
106 sender,
107 timestamp:std::time::SystemTime::now()
108 .duration_since(std::time::UNIX_EPOCH)
109 .unwrap_or_default()
110 .as_millis() as u64,
111 }
112 }
113
114 /// Check if message is from a specific sender
115 pub fn is_from(&self, sender:&str) -> bool { self.sender.as_deref() == Some(sender) }
116
117 /// Get message age in milliseconds
118 pub fn age_ms(&self) -> u64 {
119 let now = std::time::SystemTime::now()
120 .duration_since(std::time::UNIX_EPOCH)
121 .unwrap_or_default()
122 .as_millis() as u64;
123 now.saturating_sub(self.timestamp)
124 }
125}
126
127/// Connection status message for health monitoring
128///
129/// This structure is used to report the IPC connection status between Wind
130/// and Mountain, enabling the frontend to display connection state to users.
131///
132/// ## Status Reporting Flow
133///
134/// ```text
135/// Mounntain IPC Server
136/// |
137/// | 1. Detect connection change
138/// v
139/// ConnectionStatus
140/// |
141/// | 2. Emit via IPC
142/// v
143/// Wind Frontend
144/// |
145/// | 3. Update UI
146/// v
147/// User (see connection status)
148/// ```
149/// Simple connection status message for health monitoring
150///
151/// This structure is used to report the IPC connection status between Wind
152/// and Mountain, enabling the frontend to display connection state to users.
153#[derive(Debug, Clone, Serialize, Deserialize)]
154pub struct SimpleConnectionStatus {
155 /// Whether the IPC connection is currently active
156 pub connected:bool,
157}
158
159impl SimpleConnectionStatus {
160 /// Create a new connection status
161 pub fn new(connected:bool) -> Self { Self { connected } }
162
163 /// Get human-readable status description
164 pub fn description(&self) -> &'static str {
165 if self.connected {
166 "Connected to Mountain"
167 } else {
168 "Disconnected from Mountain"
169 }
170 }
171}
172
173/// Listener callback type for handling incoming IPC messages
174///
175/// This type defines the signature for callbacks that can be registered
176/// to handle messages on specific IPC channels.
177///
178/// ## Callback Signature
179///
180/// ```rust,ignore
181/// pub type ListenerCallback = Box<dyn Fn(serde_json::Value) -> Result<(), String> + Send + Sync>;
182/// ```
183///
184/// ## Example Usage
185///
186/// ```rust,ignore
187/// // Register a listener for file operations
188/// ipc_server.on("mountain_file_read", Box::new(|data| {
189/// // Handle file read request
190/// Ok(())
191/// }))?;
192/// ```
193pub type ListenerCallback = Box<dyn Fn(serde_json::Value) -> Result<(), String> + Send + Sync>;
194
195#[cfg(test)]
196mod tests {
197 use super::*;
198
199 #[test]
200 fn test_message_creation() {
201 let message = TauriIPCMessage::new(
202 "test_channel".to_string(),
203 serde_json::json!({ "key": "value" }),
204 Some("test_sender".to_string()),
205 );
206
207 assert_eq!(message.channel, "test_channel");
208 assert!(message.is_from("test_sender"));
209 }
210
211 #[test]
212 fn test_message_age() {
213 let message = TauriIPCMessage::new("test_channel".to_string(), serde_json::json!({}), None);
214
215 // Age should be small (less than 100ms)
216 assert!(message.age_ms() < 100);
217 }
218
219 #[test]
220 fn test_connection_status() {
221 let status = SimpleConnectionStatus::new(true);
222 assert!(status.connected);
223 assert_eq!(status.description(), "Connected to Mountain");
224
225 let status = SimpleConnectionStatus::new(false);
226 assert!(!status.connected);
227 assert_eq!(status.description(), "Disconnected from Mountain");
228 }
229}