Skip to main content

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}