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
106 data,
107
108 sender,
109
110 timestamp:std::time::SystemTime::now()
111 .duration_since(std::time::UNIX_EPOCH)
112 .unwrap_or_default()
113 .as_millis() as u64,
114 }
115 }
116
117 /// Check if message is from a specific sender
118 pub fn is_from(&self, sender:&str) -> bool { self.sender.as_deref() == Some(sender) }
119
120 /// Get message age in milliseconds
121 pub fn age_ms(&self) -> u64 {
122 let now = std::time::SystemTime::now()
123 .duration_since(std::time::UNIX_EPOCH)
124 .unwrap_or_default()
125 .as_millis() as u64;
126
127 now.saturating_sub(self.timestamp)
128 }
129}
130
131/// Connection status message for health monitoring
132///
133/// This structure is used to report the IPC connection status between Wind
134/// and Mountain, enabling the frontend to display connection state to users.
135///
136/// ## Status Reporting Flow
137///
138/// ```text
139/// Mounntain IPC Server
140/// |
141/// | 1. Detect connection change
142/// v
143/// ConnectionStatus
144/// |
145/// | 2. Emit via IPC
146/// v
147/// Wind Frontend
148/// |
149/// | 3. Update UI
150/// v
151/// User (see connection status)
152/// ```
153/// Simple connection status message for health monitoring
154///
155/// This structure is used to report the IPC connection status between Wind
156/// and Mountain, enabling the frontend to display connection state to users.
157#[derive(Debug, Clone, Serialize, Deserialize)]
158pub struct SimpleConnectionStatus {
159 /// Whether the IPC connection is currently active
160 pub connected:bool,
161}
162
163impl SimpleConnectionStatus {
164 /// Create a new connection status
165 pub fn new(connected:bool) -> Self { Self { connected } }
166
167 /// Get human-readable status description
168 pub fn description(&self) -> &'static str {
169 if self.connected {
170 "Connected to Mountain"
171 } else {
172 "Disconnected from Mountain"
173 }
174 }
175}
176
177/// Listener callback type for handling incoming IPC messages
178///
179/// This type defines the signature for callbacks that can be registered
180/// to handle messages on specific IPC channels.
181///
182/// ## Callback Signature
183///
184/// ```rust,ignore
185/// pub type ListenerCallback = Box<dyn Fn(serde_json::Value) -> Result<(), String> + Send + Sync>;
186/// ```
187///
188/// ## Example Usage
189///
190/// ```rust,ignore
191/// // Register a listener for file operations
192/// ipc_server.on("mountain_file_read", Box::new(|data| {
193/// // Handle file read request
194/// Ok(())
195/// }))?;
196/// ```
197pub type ListenerCallback = Box<dyn Fn(serde_json::Value) -> Result<(), String> + Send + Sync>;
198
199#[cfg(test)]
200mod tests {
201
202 use super::*;
203
204 #[test]
205 fn test_message_creation() {
206 let message = TauriIPCMessage::new(
207 "test_channel".to_string(),
208 serde_json::json!({ "key": "value" }),
209 Some("test_sender".to_string()),
210 );
211
212 assert_eq!(message.channel, "test_channel");
213
214 assert!(message.is_from("test_sender"));
215 }
216
217 #[test]
218 fn test_message_age() {
219 let message = TauriIPCMessage::new("test_channel".to_string(), serde_json::json!({}), None);
220
221 // Age should be small (less than 100ms)
222 assert!(message.age_ms() < 100);
223 }
224
225 #[test]
226 fn test_connection_status() {
227 let status = SimpleConnectionStatus::new(true);
228
229 assert!(status.connected);
230
231 assert_eq!(status.description(), "Connected to Mountain");
232
233 let status = SimpleConnectionStatus::new(false);
234
235 assert!(!status.connected);
236
237 assert_eq!(status.description(), "Disconnected from Mountain");
238 }
239}