Skip to main content

vs_config/
lib.rs

1//! Home, config, and version resolution for `vs`.
2
3mod config_file;
4mod home;
5mod legacy;
6mod project;
7mod resolution;
8mod types;
9
10use std::path::PathBuf;
11
12use thiserror::Error;
13
14pub use config_file::{
15    app_config_to_value, flatten_app_config, read_app_config, read_tool_versions,
16    set_app_config_value, unset_app_config_value, write_app_config, write_tool_versions,
17};
18pub use home::{resolve_home, resolve_home_with};
19pub use legacy::{
20    find_legacy_file, parse_legacy_versions, read_legacy_versions, supported_legacy_files,
21};
22pub use project::{find_project_file, preferred_project_file};
23pub use resolution::{global_tools_file, resolve_tool_version, session_tools_file};
24pub use types::{
25    AppConfig, CacheConfig, HomeLayout, LegacyVersionFileConfig, ProxyConfig, RegistryConfig,
26    ResolvedToolVersion, Scope, StorageConfig, ToolVersions,
27};
28
29/// Errors returned by configuration services.
30#[derive(Debug, Error)]
31pub enum ConfigError {
32    /// The system home directory is unavailable.
33    #[error("failed to resolve the user home directory")]
34    HomeDirectoryUnavailable,
35    /// An I/O operation failed.
36    #[error(transparent)]
37    Io(#[from] std::io::Error),
38    /// A TOML file could not be parsed.
39    #[error("failed to parse TOML file at {path}: {message}")]
40    Toml { path: PathBuf, message: String },
41    /// A YAML file could not be parsed.
42    #[error("failed to parse YAML file at {path}: {message}")]
43    Yaml { path: PathBuf, message: String },
44    /// A requested configuration key is not supported.
45    #[error("unknown config key: {0}")]
46    UnknownKey(String),
47    /// A configuration value could not be parsed.
48    #[error("invalid value {value} for key {key}")]
49    InvalidValue { key: String, value: String },
50    /// A legacy file name is unsupported.
51    #[error("unsupported legacy file: {0}")]
52    UnsupportedLegacyFile(PathBuf),
53}
54
55#[cfg(test)]
56mod tests {
57    use std::error::Error;
58    use std::fs;
59
60    use tempfile::TempDir;
61
62    use super::*;
63
64    #[test]
65    fn resolve_home_should_prefer_vs_home() -> Result<(), Box<dyn Error>> {
66        let temp_dir = TempDir::new()?;
67        let explicit_home = temp_dir.path().join("explicit");
68        let layout = resolve_home_with(Some(explicit_home.clone()), temp_dir.path().join("user"))?;
69
70        assert_eq!(layout.active_home, explicit_home);
71        assert!(layout.migration_candidates.is_empty());
72        Ok(())
73    }
74
75    #[test]
76    fn resolve_tool_version_should_prefer_project_config_over_global() -> Result<(), Box<dyn Error>>
77    {
78        let temp_dir = TempDir::new()?;
79        let home = temp_dir.path().join("home");
80        fs::create_dir_all(home.join("global"))?;
81        write_tool_versions(
82            &global_tools_file(&home),
83            &ToolVersions {
84                tools: [("nodejs".to_string(), "18.19.0".to_string())]
85                    .into_iter()
86                    .collect(),
87            },
88        )?;
89
90        let project = temp_dir.path().join("project");
91        fs::create_dir_all(&project)?;
92        write_tool_versions(
93            &preferred_project_file(&project),
94            &ToolVersions {
95                tools: [("nodejs".to_string(), "20.11.1".to_string())]
96                    .into_iter()
97                    .collect(),
98            },
99        )?;
100
101        let resolved = resolve_tool_version(&home, &project, None, "nodejs")?
102            .ok_or_else(|| std::io::Error::other("missing resolved version"))?;
103
104        assert_eq!(resolved.version, "20.11.1");
105        assert_eq!(resolved.scope, Scope::Project);
106        Ok(())
107    }
108
109    #[test]
110    fn parse_legacy_versions_should_support_tool_versions() -> Result<(), Box<dyn Error>> {
111        let versions =
112            parse_legacy_versions(Some(".tool-versions"), "nodejs 20.11.1\njava 21-tem\n")
113                .ok_or_else(|| std::io::Error::other("missing parsed legacy versions"))?;
114
115        assert_eq!(versions.tools.get("nodejs"), Some(&String::from("20.11.1")));
116        assert_eq!(versions.tools.get("java"), Some(&String::from("21-tem")));
117        Ok(())
118    }
119}