adaptive_pipeline_bootstrap/platform.rs
1// /////////////////////////////////////////////////////////////////////////////
2// Adaptive Pipeline
3// Copyright (c) 2025 Michael Gardner, A Bit of Help, Inc.
4// SPDX-License-Identifier: BSD-3-Clause
5// See LICENSE file in the project root.
6// /////////////////////////////////////////////////////////////////////////////
7
8//! # Platform Abstraction Module
9//!
10//! This module provides platform-specific abstractions for operating system
11//! functionality, following the pattern used in the Ada project.
12//!
13//! ## Architecture Pattern
14//!
15//! Following hexagonal architecture principles:
16//! - **Interface**: `Platform` trait defines the contract
17//! - **Implementations**:
18//! - `UnixPlatform`: POSIX implementation (Linux + macOS)
19//! - `WindowsPlatform`: Windows API implementation
20//! - **Selection**: Compile-time platform selection via `#[cfg]`
21//!
22//! ## Design Philosophy
23//!
24//! The bootstrap module sits OUTSIDE the enterprise application layers,
25//! so it can access platform-specific APIs directly. This abstraction:
26//!
27//! 1. **Isolates** OS-specific code to one module
28//! 2. **Enables** testing via trait mocking
29//! 3. **Provides** consistent API across platforms
30//! 4. **Avoids** scattered conditional compilation
31//!
32//! ## Usage
33//!
34//! ```rust
35//! use adaptive_pipeline_bootstrap::platform::create_platform;
36//!
37//! let platform = create_platform();
38//! println!("Running on: {}", platform.platform_name());
39//! println!("CPU cores: {}", platform.cpu_count());
40//! ```
41
42use async_trait::async_trait;
43use std::path::{Path, PathBuf};
44use thiserror::Error;
45
46#[cfg(unix)]
47mod unix;
48
49#[cfg(windows)]
50mod windows;
51
52// Re-export implementations
53#[cfg(unix)]
54pub use unix::UnixPlatform;
55
56#[cfg(windows)]
57pub use windows::WindowsPlatform;
58
59/// Platform-specific errors
60#[derive(Debug, Error)]
61pub enum PlatformError {
62 /// I/O error occurred
63 #[error("I/O error: {0}")]
64 Io(#[from] std::io::Error),
65
66 /// Feature not supported on this platform
67 #[error("Not supported on this platform: {0}")]
68 NotSupported(String),
69
70 /// Permission denied
71 #[error("Permission denied: {0}")]
72 PermissionDenied(String),
73
74 /// Generic platform error
75 #[error("Platform error: {0}")]
76 Other(String),
77}
78
79/// Platform abstraction trait for OS-specific operations
80///
81/// This trait provides a clean interface for platform-specific functionality,
82/// allowing the bootstrap layer to work with different operating systems
83/// without conditional compilation throughout the codebase.
84///
85/// ## Design Principles
86///
87/// - **Stateless**: All methods are stateless and thread-safe
88/// - **Async-aware**: File operations are async-compatible
89/// - **Error-handling**: All fallible operations return `Result`
90/// - **Cross-platform**: Same interface works on Unix and Windows
91///
92/// ## Implementation Notes
93///
94/// Implementations should use native platform APIs:
95/// - Unix: POSIX APIs via `libc`, `/proc`, `/sys`
96/// - Windows: Windows API via `winapi`
97/// - Fallbacks: Standard Rust APIs when platform APIs unavailable
98#[async_trait]
99pub trait Platform: Send + Sync {
100 // === System Information ===
101
102 /// Get the system page size for memory alignment
103 ///
104 /// Used for:
105 /// - Memory-mapped I/O alignment
106 /// - Buffer sizing optimizations
107 /// - Cache-friendly allocations
108 ///
109 /// # Returns
110 /// Page size in bytes (typically 4096 on most systems)
111 fn page_size(&self) -> usize;
112
113 /// Get the number of available CPU cores
114 ///
115 /// Returns the number of logical processors available to the process.
116 /// Used for determining optimal parallelism levels.
117 ///
118 /// # Returns
119 /// Number of CPU cores (at least 1)
120 fn cpu_count(&self) -> usize;
121
122 /// Get total system memory in bytes
123 ///
124 /// # Returns
125 /// Total physical memory in bytes
126 ///
127 /// # Errors
128 /// Returns error if system information cannot be retrieved
129 fn total_memory(&self) -> Result<u64, PlatformError>;
130
131 /// Get available system memory in bytes
132 ///
133 /// # Returns
134 /// Available (free) memory in bytes
135 ///
136 /// # Errors
137 /// Returns error if system information cannot be retrieved
138 fn available_memory(&self) -> Result<u64, PlatformError>;
139
140 // === Platform Constants ===
141
142 /// Get the platform-specific line separator
143 ///
144 /// # Returns
145 /// - Unix: `"\n"`
146 /// - Windows: `"\r\n"`
147 fn line_separator(&self) -> &'static str;
148
149 /// Get the platform-specific path separator for PATH environment variable
150 ///
151 /// # Returns
152 /// - Unix: `':'`
153 /// - Windows: `';'`
154 fn path_separator(&self) -> char;
155
156 /// Get the platform name
157 ///
158 /// # Returns
159 /// Platform identifier: "linux", "macos", "windows", etc.
160 fn platform_name(&self) -> &'static str;
161
162 /// Get the platform-specific temporary directory
163 ///
164 /// # Returns
165 /// Path to system temp directory
166 fn temp_dir(&self) -> PathBuf;
167
168 // === Security & Permissions ===
169
170 /// Check if running with elevated privileges
171 ///
172 /// # Returns
173 /// - Unix: `true` if effective UID is 0 (root)
174 /// - Windows: `true` if running as Administrator
175 fn is_elevated(&self) -> bool;
176
177 /// Set file permissions (Unix-specific, no-op on Windows)
178 ///
179 /// # Arguments
180 /// - `path`: Path to file
181 /// - `mode`: Unix permission bits (e.g., 0o644)
182 ///
183 /// # Errors
184 /// Returns error if permissions cannot be set
185 fn set_permissions(&self, path: &Path, mode: u32) -> Result<(), PlatformError>;
186
187 /// Check if a path points to an executable file
188 ///
189 /// # Arguments
190 /// - `path`: Path to check
191 ///
192 /// # Returns
193 /// - Unix: `true` if execute bit set
194 /// - Windows: `true` if extension is .exe, .bat, .cmd, .com
195 fn is_executable(&self, path: &Path) -> bool;
196
197 // === File Operations ===
198
199 /// Flush file buffers to disk
200 ///
201 /// Ensures all buffered data is written to physical storage.
202 ///
203 /// # Arguments
204 /// - `file`: File to sync
205 ///
206 /// # Errors
207 /// Returns error if sync operation fails
208 async fn sync_file(&self, file: &tokio::fs::File) -> Result<(), PlatformError>;
209}
210
211// === Platform Selection ===
212
213#[cfg(unix)]
214type PlatformImpl = UnixPlatform;
215
216#[cfg(windows)]
217type PlatformImpl = WindowsPlatform;
218
219/// Create the platform-specific implementation
220///
221/// This function returns the appropriate platform implementation
222/// for the current operating system, selected at compile time.
223///
224/// # Returns
225/// Boxed platform implementation
226///
227/// # Examples
228///
229/// ```rust
230/// use adaptive_pipeline_bootstrap::platform::create_platform;
231///
232/// let platform = create_platform();
233/// println!("Running on: {}", platform.platform_name());
234/// ```
235pub fn create_platform() -> Box<dyn Platform> {
236 Box::new(PlatformImpl::new())
237}
238
239#[cfg(test)]
240mod tests {
241 use super::*;
242
243 #[test]
244 fn test_create_platform() {
245 let platform = create_platform();
246
247 // Should have at least one CPU
248 assert!(platform.cpu_count() >= 1);
249
250 // Page size should be reasonable
251 let page_size = platform.page_size();
252 assert!(page_size >= 512);
253 assert!(page_size <= 65536);
254
255 // Platform name should not be empty
256 assert!(!platform.platform_name().is_empty());
257 }
258
259 #[test]
260 fn test_line_separator() {
261 let platform = create_platform();
262 let sep = platform.line_separator();
263
264 #[cfg(unix)]
265 assert_eq!(sep, "\n");
266
267 #[cfg(windows)]
268 assert_eq!(sep, "\r\n");
269 }
270
271 #[test]
272 fn test_path_separator() {
273 let platform = create_platform();
274 let sep = platform.path_separator();
275
276 #[cfg(unix)]
277 assert_eq!(sep, ':');
278
279 #[cfg(windows)]
280 assert_eq!(sep, ';');
281 }
282}