gglib_core/paths/
resolver.rs

1//! Pure path resolver for testing and CLI introspection.
2//!
3//! This module provides a single struct that captures all resolved paths
4//! in one call, making it easy to compare path resolution across adapters
5//! and expose via `gglib paths` CLI command.
6
7use std::path::PathBuf;
8
9use super::{
10    ModelsDirSource, PathError, data_root, database_path, llama_server_path, resolve_models_dir,
11    resource_root,
12};
13
14/// All resolved paths captured in a single struct.
15///
16/// This is the "golden truth" for path resolution - use it for:
17/// - Integration tests comparing adapter parity
18/// - CLI `gglib paths` command output
19/// - Debugging path resolution issues
20#[derive(Debug, Clone, PartialEq, Eq)]
21pub struct ResolvedPaths {
22    /// Root directory for application data (database, logs, etc.)
23    pub data_root: PathBuf,
24    /// Root directory for application resources (binaries, assets)
25    pub resource_root: PathBuf,
26    /// Path to the `SQLite` database file
27    pub database_path: PathBuf,
28    /// Path to the llama-server binary
29    pub llama_server_path: PathBuf,
30    /// Path to the models directory
31    pub models_dir: PathBuf,
32    /// How the models directory was resolved
33    pub models_source: ModelsDirSource,
34}
35
36impl ResolvedPaths {
37    /// Resolve all paths using the current environment.
38    ///
39    /// This calls each path resolver once and captures the results.
40    /// Use this instead of calling individual resolvers when you need
41    /// multiple paths - it's more efficient and guarantees consistency.
42    pub fn resolve() -> Result<Self, PathError> {
43        let data_root = data_root()?;
44        let resource_root = resource_root()?;
45        let database_path = database_path()?;
46        let llama_server_path = llama_server_path()?;
47        let models_resolution = resolve_models_dir(None)?;
48
49        Ok(Self {
50            data_root,
51            resource_root,
52            database_path,
53            llama_server_path,
54            models_dir: models_resolution.path,
55            models_source: models_resolution.source,
56        })
57    }
58
59    /// Resolve with an explicit models directory override.
60    ///
61    /// Use this to test behavior when `--models-dir` is passed.
62    pub fn resolve_with_models_dir(models_dir: Option<&str>) -> Result<Self, PathError> {
63        let data_root = data_root()?;
64        let resource_root = resource_root()?;
65        let database_path = database_path()?;
66        let llama_server_path = llama_server_path()?;
67        let models_resolution = resolve_models_dir(models_dir)?;
68
69        Ok(Self {
70            data_root,
71            resource_root,
72            database_path,
73            llama_server_path,
74            models_dir: models_resolution.path,
75            models_source: models_resolution.source,
76        })
77    }
78}
79
80impl std::fmt::Display for ResolvedPaths {
81    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
82        writeln!(f, "data_root = {}", self.data_root.display())?;
83        writeln!(f, "resource_root = {}", self.resource_root.display())?;
84        writeln!(f, "database_path = {}", self.database_path.display())?;
85        writeln!(
86            f,
87            "llama_server_path = {}",
88            self.llama_server_path.display()
89        )?;
90        writeln!(f, "models_dir = {}", self.models_dir.display())?;
91        write!(f, "models_source = {:?}", self.models_source)
92    }
93}
94
95#[cfg(test)]
96mod tests {
97    use super::*;
98    use crate::paths::test_utils::ENV_LOCK;
99
100    #[test]
101    fn resolve_returns_consistent_paths() {
102        // Lock ensures this test doesn't run concurrently with config.rs tests
103        // that modify GGLIB_DATA_DIR, preventing non-deterministic results
104        let _guard = ENV_LOCK.lock().unwrap();
105
106        let first = ResolvedPaths::resolve().expect("first resolve");
107        let second = ResolvedPaths::resolve().expect("second resolve");
108
109        assert_eq!(first, second, "path resolution should be deterministic");
110    }
111
112    #[test]
113    fn display_format_is_parseable() {
114        let paths = ResolvedPaths::resolve().expect("resolve");
115        let output = paths.to_string();
116
117        // Should contain key = value pairs
118        assert!(output.contains("data_root = "));
119        assert!(output.contains("resource_root = "));
120        assert!(output.contains("database_path = "));
121        assert!(output.contains("llama_server_path = "));
122        assert!(output.contains("models_dir = "));
123        assert!(output.contains("models_source = "));
124    }
125}