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}