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}