ferriclink_core/
env.rs

1//! Utilities for getting information about the runtime environment.
2//!
3//! This module provides functionality to gather and report information about
4//! the FerricLink runtime environment, similar to LangChain's env.py.
5
6use serde::{Deserialize, Serialize};
7use std::collections::HashMap;
8use std::sync::OnceLock;
9
10use crate::impl_serializable;
11
12/// Information about the FerricLink runtime environment
13#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
14pub struct RuntimeEnvironment {
15    /// Version of the FerricLink Core library
16    pub library_version: String,
17    /// Name of the library
18    pub library: String,
19    /// Platform information
20    pub platform: String,
21    /// Runtime language
22    pub runtime: String,
23    /// Runtime version
24    pub runtime_version: String,
25    /// Architecture information
26    pub architecture: String,
27    /// Operating system information
28    pub os: String,
29    /// Compiler information
30    pub compiler: String,
31    /// Target triple
32    pub target: String,
33    /// Additional environment variables
34    pub env_vars: HashMap<String, String>,
35    /// Features enabled at compile time
36    pub features: Vec<String>,
37}
38
39impl RuntimeEnvironment {
40    /// Create a new runtime environment instance
41    pub fn new() -> Self {
42        Self {
43            library_version: crate::VERSION.to_string(),
44            library: "ferriclink-core".to_string(),
45            platform: get_platform_info(),
46            runtime: "rust".to_string(),
47            runtime_version: get_rust_version(),
48            architecture: get_architecture(),
49            os: get_os_info(),
50            compiler: get_compiler_info(),
51            target: get_target_triple(),
52            env_vars: get_relevant_env_vars(),
53            features: get_enabled_features(),
54        }
55    }
56
57    /// Get a summary of the runtime environment
58    pub fn summary(&self) -> String {
59        format!(
60            "FerricLink {} on {} {} ({})",
61            self.library_version, self.os, self.architecture, self.runtime_version
62        )
63    }
64
65    /// Check if a specific feature is enabled
66    pub fn has_feature(&self, feature: &str) -> bool {
67        self.features.contains(&feature.to_string())
68    }
69
70    /// Get environment variable value
71    pub fn get_env_var(&self, key: &str) -> Option<&String> {
72        self.env_vars.get(key)
73    }
74
75    /// Check if running in debug mode
76    pub fn is_debug(&self) -> bool {
77        cfg!(debug_assertions)
78    }
79
80    /// Check if running in release mode
81    pub fn is_release(&self) -> bool {
82        !self.is_debug()
83    }
84
85    /// Get memory information (if available)
86    pub fn memory_info(&self) -> Option<MemoryInfo> {
87        get_memory_info()
88    }
89}
90
91impl Default for RuntimeEnvironment {
92    fn default() -> Self {
93        Self::new()
94    }
95}
96
97impl_serializable!(
98    RuntimeEnvironment,
99    ["ferriclink", "env", "runtime_environment"]
100);
101
102/// Memory information about the system
103#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
104pub struct MemoryInfo {
105    /// Total system memory in bytes
106    pub total_memory: Option<u64>,
107    /// Available memory in bytes
108    pub available_memory: Option<u64>,
109    /// Memory used by the process in bytes
110    pub process_memory: Option<u64>,
111}
112
113impl_serializable!(MemoryInfo, ["ferriclink", "env", "memory_info"]);
114
115/// Global runtime environment instance (cached)
116static RUNTIME_ENV: OnceLock<RuntimeEnvironment> = OnceLock::new();
117
118/// Get information about the FerricLink runtime environment.
119///
120/// This function returns a cached instance of the runtime environment
121/// information, similar to LangChain's `get_runtime_environment()`.
122///
123/// # Returns
124///
125/// A `RuntimeEnvironment` struct with information about the runtime environment.
126pub fn get_runtime_environment() -> &'static RuntimeEnvironment {
127    RUNTIME_ENV.get_or_init(RuntimeEnvironment::new)
128}
129
130/// Get a fresh runtime environment instance (not cached)
131pub fn get_fresh_runtime_environment() -> RuntimeEnvironment {
132    RuntimeEnvironment::new()
133}
134
135/// Get platform information
136fn get_platform_info() -> String {
137    format!("{} {}", get_os_info(), get_architecture())
138}
139
140/// Get operating system information
141fn get_os_info() -> String {
142    #[cfg(target_os = "linux")]
143    {
144        "Linux".to_string()
145    }
146    #[cfg(target_os = "macos")]
147    {
148        "macOS".to_string()
149    }
150    #[cfg(target_os = "windows")]
151    {
152        "Windows".to_string()
153    }
154    #[cfg(target_os = "freebsd")]
155    {
156        "FreeBSD".to_string()
157    }
158    #[cfg(target_os = "openbsd")]
159    {
160        "OpenBSD".to_string()
161    }
162    #[cfg(target_os = "netbsd")]
163    {
164        "NetBSD".to_string()
165    }
166    #[cfg(not(any(
167        target_os = "linux",
168        target_os = "macos",
169        target_os = "windows",
170        target_os = "freebsd",
171        target_os = "openbsd",
172        target_os = "netbsd"
173    )))]
174    {
175        "Unknown".to_string()
176    }
177}
178
179/// Get architecture information
180fn get_architecture() -> String {
181    #[cfg(target_arch = "x86_64")]
182    {
183        "x86_64".to_string()
184    }
185    #[cfg(target_arch = "x86")]
186    {
187        "x86".to_string()
188    }
189    #[cfg(target_arch = "aarch64")]
190    {
191        "aarch64".to_string()
192    }
193    #[cfg(target_arch = "arm")]
194    {
195        "arm".to_string()
196    }
197    #[cfg(target_arch = "riscv64")]
198    {
199        "riscv64".to_string()
200    }
201    #[cfg(target_arch = "powerpc64")]
202    {
203        "powerpc64".to_string()
204    }
205    #[cfg(not(any(
206        target_arch = "x86_64",
207        target_arch = "x86",
208        target_arch = "aarch64",
209        target_arch = "arm",
210        target_arch = "riscv64",
211        target_arch = "powerpc64"
212    )))]
213    {
214        "unknown".to_string()
215    }
216}
217
218/// Get Rust version information
219fn get_rust_version() -> String {
220    std::env::var("RUSTC_VERSION")
221        .or_else(|_| std::env::var("RUSTC_VERSION_MAJOR").map(|v| format!("{v}.0.0")))
222        .unwrap_or_else(|_| "unknown".to_string())
223}
224
225/// Get compiler information
226fn get_compiler_info() -> String {
227    let rustc_version = get_rust_version();
228    if rustc_version == "unknown" {
229        "rustc (version unknown)".to_string()
230    } else {
231        format!("rustc {rustc_version}")
232    }
233}
234
235/// Get target triple
236fn get_target_triple() -> String {
237    std::env::var("TARGET").unwrap_or_else(|_| "unknown".to_string())
238}
239
240/// Get relevant environment variables
241fn get_relevant_env_vars() -> HashMap<String, String> {
242    let mut env_vars = HashMap::new();
243
244    // Common environment variables that might be relevant
245    let relevant_vars = [
246        "RUST_LOG",
247        "RUST_BACKTRACE",
248        "CARGO_PKG_NAME",
249        "CARGO_PKG_VERSION",
250        "PATH",
251        "HOME",
252        "USER",
253        "SHELL",
254        "TERM",
255        "LANG",
256        "LC_ALL",
257        "TZ",
258    ];
259
260    for var in &relevant_vars {
261        if let Ok(value) = std::env::var(var) {
262            env_vars.insert(var.to_string(), value);
263        }
264    }
265
266    // Add FerricLink-specific environment variables
267    for (key, value) in std::env::vars() {
268        if key.starts_with("FERRICLINK_") || key.starts_with("FERRIC_") {
269            env_vars.insert(key, value);
270        }
271    }
272
273    env_vars
274}
275
276/// Get enabled features at compile time
277fn get_enabled_features() -> Vec<String> {
278    let mut features = Vec::new();
279
280    // Check for common features
281    #[cfg(feature = "http")]
282    features.push("http".to_string());
283
284    #[cfg(feature = "validation")]
285    features.push("validation".to_string());
286
287    #[cfg(feature = "all")]
288    features.push("all".to_string());
289
290    // Add debug/release info
291    if cfg!(debug_assertions) {
292        features.push("debug".to_string());
293    } else {
294        features.push("release".to_string());
295    }
296
297    // Add target info
298    if cfg!(target_pointer_width = "64") {
299        features.push("64bit".to_string());
300    } else if cfg!(target_pointer_width = "32") {
301        features.push("32bit".to_string());
302    }
303
304    features
305}
306
307/// Get memory information (if available)
308fn get_memory_info() -> Option<MemoryInfo> {
309    #[cfg(target_os = "linux")]
310    {
311        get_linux_memory_info()
312    }
313    #[cfg(target_os = "macos")]
314    {
315        get_macos_memory_info()
316    }
317    #[cfg(target_os = "windows")]
318    {
319        get_windows_memory_info()
320    }
321    #[cfg(not(any(target_os = "linux", target_os = "macos", target_os = "windows")))]
322    {
323        None
324    }
325}
326
327#[cfg(target_os = "linux")]
328fn get_linux_memory_info() -> Option<MemoryInfo> {
329    use std::fs;
330
331    // Read /proc/meminfo
332    let meminfo = fs::read_to_string("/proc/meminfo").ok()?;
333    let mut total_memory = None;
334    let mut available_memory = None;
335
336    for line in meminfo.lines() {
337        if line.starts_with("MemTotal:") {
338            if let Some(value) = line.split_whitespace().nth(1) {
339                total_memory = value.parse::<u64>().ok().map(|kb| kb * 1024);
340            }
341        } else if line.starts_with("MemAvailable:") {
342            if let Some(value) = line.split_whitespace().nth(1) {
343                available_memory = value.parse::<u64>().ok().map(|kb| kb * 1024);
344            }
345        }
346    }
347
348    // Get process memory usage
349    let process_memory = get_process_memory_usage();
350
351    Some(MemoryInfo {
352        total_memory,
353        available_memory,
354        process_memory,
355    })
356}
357
358#[cfg(target_os = "macos")]
359fn get_macos_memory_info() -> Option<MemoryInfo> {
360    // On macOS, we can use system_profiler or sysctl
361    // This is a simplified implementation
362    let process_memory = get_process_memory_usage();
363
364    Some(MemoryInfo {
365        total_memory: None,
366        available_memory: None,
367        process_memory,
368    })
369}
370
371#[cfg(target_os = "windows")]
372fn get_windows_memory_info() -> Option<MemoryInfo> {
373    // On Windows, we would use Windows API calls
374    // This is a simplified implementation
375    let process_memory = get_process_memory_usage();
376
377    Some(MemoryInfo {
378        total_memory: None,
379        available_memory: None,
380        process_memory,
381    })
382}
383
384/// Get process memory usage (simplified implementation)
385fn get_process_memory_usage() -> Option<u64> {
386    // This is a placeholder - in a real implementation, you would
387    // use platform-specific APIs to get actual memory usage
388    None
389}
390
391#[cfg(test)]
392mod tests {
393    use super::*;
394
395    #[test]
396    fn test_runtime_environment_creation() {
397        let env = RuntimeEnvironment::new();
398        assert_eq!(env.library, "ferriclink-core");
399        assert_eq!(env.runtime, "rust");
400        assert!(!env.library_version.is_empty());
401        assert!(!env.platform.is_empty());
402    }
403
404    #[test]
405    fn test_runtime_environment_caching() {
406        let env1 = get_runtime_environment();
407        let env2 = get_runtime_environment();
408        assert!(std::ptr::eq(env1, env2));
409    }
410
411    #[test]
412    fn test_runtime_environment_summary() {
413        let env = RuntimeEnvironment::new();
414        let summary = env.summary();
415        assert!(summary.contains("FerricLink"));
416        assert!(summary.contains(&env.library_version));
417    }
418
419    #[test]
420    fn test_feature_detection() {
421        let env = RuntimeEnvironment::new();
422        // These features should be present based on our Cargo.toml
423        assert!(env.has_feature("debug") || env.has_feature("release"));
424    }
425
426    #[test]
427    fn test_environment_variables() {
428        let env = RuntimeEnvironment::new();
429        // Should have some environment variables
430        assert!(!env.env_vars.is_empty());
431    }
432
433    #[test]
434    fn test_serialization() {
435        let env = RuntimeEnvironment::new();
436        let serialized = serde_json::to_string(&env).unwrap();
437        let deserialized: RuntimeEnvironment = serde_json::from_str(&serialized).unwrap();
438        assert_eq!(env, deserialized);
439    }
440}