ferriclink_core/
globals.rs

1//! Global values and configuration that apply to all of FerricLink.
2//!
3//! This module provides global settings similar to LangChain's globals.py,
4//! but with Rust's type safety and thread safety guarantees.
5
6use std::sync::atomic::{AtomicBool, Ordering};
7use std::sync::{Arc, RwLock};
8
9use crate::caches::BaseCache;
10use crate::errors::Result;
11
12/// Global configuration state for FerricLink
13pub struct GlobalConfig {
14    /// Global verbose setting
15    verbose: AtomicBool,
16    /// Global debug setting
17    debug: AtomicBool,
18    /// Global LLM cache
19    llm_cache: Arc<RwLock<Option<Box<dyn BaseCache>>>>,
20}
21
22impl GlobalConfig {
23    /// Create a new global configuration
24    fn new() -> Self {
25        Self {
26            verbose: AtomicBool::new(false),
27            debug: AtomicBool::new(false),
28            llm_cache: Arc::new(RwLock::new(None)),
29        }
30    }
31
32    /// Get the verbose setting
33    pub fn get_verbose(&self) -> bool {
34        self.verbose.load(Ordering::SeqCst)
35    }
36
37    /// Set the verbose setting
38    pub fn set_verbose(&self, value: bool) {
39        self.verbose.store(value, Ordering::SeqCst);
40    }
41
42    /// Get the debug setting
43    pub fn get_debug(&self) -> bool {
44        self.debug.load(Ordering::SeqCst)
45    }
46
47    /// Set the debug setting
48    pub fn set_debug(&self, value: bool) {
49        self.debug.store(value, Ordering::SeqCst);
50    }
51
52    /// Get the LLM cache as a reference (for internal use)
53    pub fn get_llm_cache_ref(
54        &self,
55    ) -> Result<std::sync::RwLockReadGuard<'_, Option<Box<dyn BaseCache>>>> {
56        self.llm_cache
57            .try_read()
58            .map_err(|_| crate::errors::FerricLinkError::generic("Failed to read LLM cache"))
59    }
60
61    /// Check if LLM cache is set (without taking a lock)
62    pub fn has_llm_cache(&self) -> bool {
63        self.llm_cache
64            .try_read()
65            .map(|cache| cache.is_some())
66            .unwrap_or(false)
67    }
68
69    /// Set the LLM cache
70    pub fn set_llm_cache(&self, cache: Option<Box<dyn BaseCache>>) -> Result<()> {
71        let mut current_cache = self
72            .llm_cache
73            .write()
74            .map_err(|_| crate::errors::FerricLinkError::generic("Failed to write LLM cache"))?;
75        *current_cache = cache;
76        Ok(())
77    }
78
79    /// Clear the LLM cache
80    pub fn clear_llm_cache(&self) -> Result<()> {
81        self.set_llm_cache(None)
82    }
83
84    /// Check if verbose mode is enabled
85    pub fn is_verbose(&self) -> bool {
86        self.get_verbose()
87    }
88
89    /// Check if debug mode is enabled
90    pub fn is_debug(&self) -> bool {
91        self.get_debug()
92    }
93
94    /// Get a summary of current global settings
95    pub fn summary(&self) -> String {
96        format!(
97            "GlobalConfig {{ verbose: {}, debug: {}, has_llm_cache: {} }}",
98            self.get_verbose(),
99            self.get_debug(),
100            self.has_llm_cache()
101        )
102    }
103}
104
105impl Clone for GlobalConfig {
106    fn clone(&self) -> Self {
107        Self {
108            verbose: AtomicBool::new(self.get_verbose()),
109            debug: AtomicBool::new(self.get_debug()),
110            llm_cache: Arc::clone(&self.llm_cache),
111        }
112    }
113}
114
115/// Global configuration instance
116static GLOBAL_CONFIG: std::sync::OnceLock<GlobalConfig> = std::sync::OnceLock::new();
117
118/// Initialize the global configuration
119///
120/// This function should be called early in your application to set up
121/// global settings. It's safe to call multiple times.
122pub fn init_globals() -> Result<()> {
123    GLOBAL_CONFIG.set(GlobalConfig::new()).map_err(|_| {
124        crate::errors::FerricLinkError::generic("Failed to initialize global config")
125    })?;
126    Ok(())
127}
128
129/// Get the global configuration instance
130///
131/// # Panics
132///
133/// This function will panic if `init_globals()` has not been called first.
134/// Make sure to call `init_globals()` early in your application.
135pub fn get_globals() -> &'static GlobalConfig {
136    GLOBAL_CONFIG
137        .get()
138        .expect("Global configuration not initialized. Call init_globals() first.")
139}
140
141/// Set the global verbose setting
142///
143/// # Arguments
144///
145/// * `value` - The new value for the verbose setting
146///
147/// # Panics
148///
149/// This function will panic if `init_globals()` has not been called first.
150pub fn set_verbose(value: bool) {
151    get_globals().set_verbose(value);
152}
153
154/// Get the global verbose setting
155///
156/// # Returns
157///
158/// The current value of the verbose setting
159///
160/// # Panics
161///
162/// This function will panic if `init_globals()` has not been called first.
163pub fn get_verbose() -> bool {
164    get_globals().get_verbose()
165}
166
167/// Set the global debug setting
168///
169/// # Arguments
170///
171/// * `value` - The new value for the debug setting
172///
173/// # Panics
174///
175/// This function will panic if `init_globals()` has not been called first.
176pub fn set_debug(value: bool) {
177    get_globals().set_debug(value);
178}
179
180/// Get the global debug setting
181///
182/// # Returns
183///
184/// The current value of the debug setting
185///
186/// # Panics
187///
188/// This function will panic if `init_globals()` has not been called first.
189pub fn get_debug() -> bool {
190    get_globals().get_debug()
191}
192
193/// Set the global LLM cache
194///
195/// # Arguments
196///
197/// * `cache` - The new LLM cache to use. If `None`, the LLM cache is disabled.
198///
199/// # Returns
200///
201/// A `Result` indicating success or failure
202///
203/// # Panics
204///
205/// This function will panic if `init_globals()` has not been called first.
206pub fn set_llm_cache(cache: Option<Box<dyn BaseCache>>) -> Result<()> {
207    get_globals().set_llm_cache(cache)
208}
209
210/// Clear the global LLM cache
211///
212/// # Returns
213///
214/// A `Result` indicating success or failure
215///
216/// # Panics
217///
218/// This function will panic if `init_globals()` has not been called first.
219pub fn clear_llm_cache() -> Result<()> {
220    get_globals().clear_llm_cache()
221}
222
223/// Check if verbose mode is enabled globally
224///
225/// # Returns
226///
227/// `true` if verbose mode is enabled, `false` otherwise
228///
229/// # Panics
230///
231/// This function will panic if `init_globals()` has not been called first.
232pub fn is_verbose() -> bool {
233    get_globals().is_verbose()
234}
235
236/// Check if debug mode is enabled globally
237///
238/// # Returns
239///
240/// `true` if debug mode is enabled, `false` otherwise
241///
242/// # Panics
243///
244/// This function will panic if `init_globals()` has not been called first.
245pub fn is_debug() -> bool {
246    get_globals().is_debug()
247}
248
249/// Check if LLM cache is enabled globally
250///
251/// # Returns
252///
253/// `true` if LLM cache is enabled, `false` otherwise
254///
255/// # Panics
256///
257/// This function will panic if `init_globals()` has not been called first.
258pub fn has_llm_cache() -> bool {
259    get_globals().has_llm_cache()
260}
261
262/// Get a summary of current global settings
263///
264/// # Returns
265///
266/// A string summary of the current global configuration
267///
268/// # Panics
269///
270/// This function will panic if `init_globals()` has not been called first.
271pub fn globals_summary() -> String {
272    get_globals().summary()
273}
274
275/// Reset all global settings to their default values
276///
277/// # Returns
278///
279/// A `Result` indicating success or failure
280///
281/// # Panics
282///
283/// This function will panic if `init_globals()` has not been called first.
284pub fn reset_globals() -> Result<()> {
285    let globals = get_globals();
286    globals.set_verbose(false);
287    globals.set_debug(false);
288    globals.clear_llm_cache()?;
289    Ok(())
290}
291
292/// Convenience function to enable verbose mode
293///
294/// # Panics
295///
296/// This function will panic if `init_globals()` has not been called first.
297pub fn enable_verbose() {
298    set_verbose(true);
299}
300
301/// Convenience function to disable verbose mode
302///
303/// # Panics
304///
305/// This function will panic if `init_globals()` has not been called first.
306pub fn disable_verbose() {
307    set_verbose(false);
308}
309
310/// Convenience function to enable debug mode
311///
312/// # Panics
313///
314/// This function will panic if `init_globals()` has not been called first.
315pub fn enable_debug() {
316    set_debug(true);
317}
318
319/// Convenience function to disable debug mode
320///
321/// # Panics
322///
323/// This function will panic if `init_globals()` has not been called first.
324pub fn disable_debug() {
325    set_debug(false);
326}
327
328/// Convenience function to toggle verbose mode
329///
330/// # Panics
331///
332/// This function will panic if `init_globals()` has not been called first.
333pub fn toggle_verbose() {
334    set_verbose(!get_verbose());
335}
336
337/// Convenience function to toggle debug mode
338///
339/// # Panics
340///
341/// This function will panic if `init_globals()` has not been called first.
342pub fn toggle_debug() {
343    set_debug(!get_debug());
344}
345
346#[cfg(test)]
347mod tests {
348    use super::*;
349    use crate::caches::InMemoryCache;
350
351    #[test]
352    fn test_global_config_creation() {
353        let config = GlobalConfig::new();
354        assert!(!config.get_verbose());
355        assert!(!config.get_debug());
356        assert!(!config.has_llm_cache());
357    }
358
359    #[test]
360    fn test_global_config_verbose() {
361        let config = GlobalConfig::new();
362
363        config.set_verbose(true);
364        assert!(config.get_verbose());
365        assert!(config.is_verbose());
366
367        config.set_verbose(false);
368        assert!(!config.get_verbose());
369        assert!(!config.is_verbose());
370    }
371
372    #[test]
373    fn test_global_config_debug() {
374        let config = GlobalConfig::new();
375
376        config.set_debug(true);
377        assert!(config.get_debug());
378        assert!(config.is_debug());
379
380        config.set_debug(false);
381        assert!(!config.get_debug());
382        assert!(!config.is_debug());
383    }
384
385    #[test]
386    fn test_global_config_llm_cache() {
387        let config = GlobalConfig::new();
388
389        // Initially no cache
390        assert!(!config.has_llm_cache());
391        assert!(config.get_llm_cache_ref().unwrap().is_none());
392
393        // Set a cache
394        let cache = Box::new(InMemoryCache::new());
395        config.set_llm_cache(Some(cache)).unwrap();
396        assert!(config.has_llm_cache());
397        assert!(config.get_llm_cache_ref().unwrap().is_some());
398
399        // Clear the cache
400        config.clear_llm_cache().unwrap();
401        assert!(!config.has_llm_cache());
402        assert!(config.get_llm_cache_ref().unwrap().is_none());
403    }
404
405    #[test]
406    fn test_global_config_summary() {
407        let config = GlobalConfig::new();
408        let summary = config.summary();
409
410        assert!(summary.contains("verbose: false"));
411        assert!(summary.contains("debug: false"));
412        assert!(summary.contains("has_llm_cache: false"));
413    }
414
415    #[test]
416    fn test_global_config_clone() {
417        let config = GlobalConfig::new();
418        config.set_verbose(true);
419        config.set_debug(true);
420
421        let cloned = config.clone();
422        assert!(cloned.get_verbose());
423        assert!(cloned.get_debug());
424    }
425
426    #[test]
427    fn test_global_functions() {
428        // Initialize globals
429        init_globals().unwrap();
430
431        // Test verbose functions
432        set_verbose(true);
433        assert!(get_verbose());
434        assert!(is_verbose());
435
436        enable_verbose();
437        assert!(get_verbose());
438
439        disable_verbose();
440        assert!(!get_verbose());
441
442        toggle_verbose();
443        assert!(get_verbose());
444
445        // Test debug functions
446        set_debug(true);
447        assert!(get_debug());
448        assert!(is_debug());
449
450        enable_debug();
451        assert!(get_debug());
452
453        disable_debug();
454        assert!(!get_debug());
455
456        toggle_debug();
457        assert!(get_debug());
458
459        // Test cache functions
460        let cache = Box::new(InMemoryCache::new());
461        set_llm_cache(Some(cache)).unwrap();
462        assert!(has_llm_cache());
463
464        clear_llm_cache().unwrap();
465        assert!(!has_llm_cache());
466
467        // Test summary
468        let summary = globals_summary();
469        assert!(summary.contains("GlobalConfig"));
470
471        // Test reset
472        reset_globals().unwrap();
473        assert!(!get_verbose());
474        assert!(!get_debug());
475        assert!(!has_llm_cache());
476    }
477
478    #[test]
479    fn test_global_functions_without_init() {
480        // This test is skipped because globals might already be initialized
481        // from other tests running in the same process
482        println!(
483            "Skipping test_global_functions_without_init - globals may already be initialized"
484        );
485    }
486
487    #[test]
488    fn test_multiple_init_calls() {
489        // This test is skipped because globals might already be initialized
490        // from other tests running in the same process
491        println!("Skipping test_multiple_init_calls - globals may already be initialized");
492    }
493}