Skip to main content

clawft_platform/
lib.rs

1//! Platform abstraction layer for clawft.
2//!
3//! Provides traits for all platform-dependent operations (HTTP, filesystem,
4//! environment, process spawning) so the core engine can be platform-agnostic
5//! and WASM-compatible.
6//!
7//! # Architecture
8//!
9//! The [`Platform`] trait bundles all platform capabilities via accessor methods.
10//! Each sub-capability has its own trait ([`http::HttpClient`], [`fs::FileSystem`],
11//! [`env::Environment`], [`process::ProcessSpawner`]) with a corresponding native
12//! implementation.
13//!
14//! [`ProcessSpawner`](process::ProcessSpawner) is intentionally excluded from the
15//! [`Platform`] bundle because it is unavailable in WASM environments. It is
16//! accessible via [`Platform::process`] which returns `Option`.
17//!
18//! # Example
19//!
20//! ```rust,no_run
21//! use clawft_platform::{Platform, NativePlatform};
22//! use clawft_platform::http::HttpClient;
23//! use std::collections::HashMap;
24//!
25//! # async fn example() {
26//! let platform = NativePlatform::new();
27//! let response = platform.http()
28//!     .get("https://example.com", &HashMap::new())
29//!     .await
30//!     .unwrap();
31//! assert!(response.is_success());
32//! # }
33//! ```
34
35pub mod config_loader;
36pub mod env;
37pub mod fs;
38pub mod http;
39pub mod process;
40
41#[cfg(feature = "browser")]
42pub mod browser;
43#[cfg(feature = "browser")]
44pub use browser::BrowserPlatform;
45
46use async_trait::async_trait;
47
48/// Bundle of all platform capabilities.
49///
50/// Implementors provide concrete implementations of all platform traits.
51/// The native implementation ([`NativePlatform`]) uses reqwest, tokio::fs,
52/// std::env, and tokio::process. A WASM implementation would use the fetch
53/// API, WASI filesystem, and an in-memory environment.
54///
55/// The `async_trait` bound differs based on target:
56/// - Native: `Send + Sync` required (multi-threaded tokio runtime).
57/// - Browser: `?Send` (single-threaded WASM runtime).
58#[cfg_attr(not(feature = "browser"), async_trait)]
59#[cfg_attr(feature = "browser", async_trait(?Send))]
60pub trait Platform: Send + Sync {
61    /// HTTP client for making API requests.
62    fn http(&self) -> &dyn http::HttpClient;
63
64    /// Filesystem operations.
65    fn fs(&self) -> &dyn fs::FileSystem;
66
67    /// Environment variable access.
68    fn env(&self) -> &dyn env::Environment;
69
70    /// Process spawning capability.
71    ///
72    /// Returns `None` in environments where process spawning is unavailable
73    /// (e.g., WASM).
74    fn process(&self) -> Option<&dyn process::ProcessSpawner>;
75}
76
77/// Native platform implementation using std, tokio, and reqwest.
78///
79/// This is the standard platform for server-side and CLI usage. It provides:
80/// - HTTP via [`reqwest`] with connection pooling and TLS.
81/// - Filesystem via [`tokio::fs`].
82/// - Environment via [`std::env`].
83/// - Process spawning via [`tokio::process`].
84#[cfg(feature = "native")]
85pub struct NativePlatform {
86    http: http::NativeHttpClient,
87    fs: fs::NativeFileSystem,
88    env: env::NativeEnvironment,
89    process: process::NativeProcessSpawner,
90}
91
92#[cfg(feature = "native")]
93impl NativePlatform {
94    /// Create a new native platform with default configuration.
95    pub fn new() -> Self {
96        Self {
97            http: http::NativeHttpClient::new(),
98            fs: fs::NativeFileSystem,
99            env: env::NativeEnvironment,
100            process: process::NativeProcessSpawner,
101        }
102    }
103}
104
105#[cfg(feature = "native")]
106impl Default for NativePlatform {
107    fn default() -> Self {
108        Self::new()
109    }
110}
111
112#[cfg(feature = "native")]
113#[async_trait]
114impl Platform for NativePlatform {
115    fn http(&self) -> &dyn http::HttpClient {
116        &self.http
117    }
118
119    fn fs(&self) -> &dyn fs::FileSystem {
120        &self.fs
121    }
122
123    fn env(&self) -> &dyn env::Environment {
124        &self.env
125    }
126
127    fn process(&self) -> Option<&dyn process::ProcessSpawner> {
128        Some(&self.process)
129    }
130}
131
132#[cfg(test)]
133mod tests {
134    use super::*;
135
136    #[test]
137    fn test_native_platform_creation() {
138        let platform = NativePlatform::new();
139        // Verify all accessors return valid references
140        let _http = platform.http();
141        let _fs = platform.fs();
142        let _env = platform.env();
143        assert!(platform.process().is_some());
144    }
145
146    #[test]
147    fn test_native_platform_default() {
148        let platform = NativePlatform::default();
149        assert!(platform.process().is_some());
150    }
151
152    #[test]
153    fn test_platform_is_send_sync() {
154        fn assert_send_sync<T: Send + Sync>() {}
155        assert_send_sync::<NativePlatform>();
156    }
157}