Skip to main content

guild_cli/config/
workspace.rs

1use std::path::{Path, PathBuf};
2
3use serde::Deserialize;
4
5use crate::error::ConfigError;
6
7/// Raw deserialization target for the root `guild.toml`.
8#[derive(Debug, Deserialize)]
9struct WorkspaceToml {
10    workspace: WorkspaceSection,
11}
12
13#[derive(Debug, Deserialize)]
14struct WorkspaceSection {
15    name: String,
16    projects: Vec<String>,
17}
18
19/// A validated workspace configuration parsed from the root `guild.toml`.
20#[derive(Debug, Clone)]
21pub struct WorkspaceConfig {
22    /// Workspace name.
23    name: String,
24    /// Glob patterns for discovering project directories.
25    project_patterns: Vec<String>,
26    /// Absolute path to the workspace root directory.
27    root: PathBuf,
28}
29
30impl WorkspaceConfig {
31    /// Parse a workspace configuration from the given `guild.toml` path.
32    pub fn from_file(path: &Path) -> Result<Self, ConfigError> {
33        let content = std::fs::read_to_string(path).map_err(|e| ConfigError::ReadFile {
34            path: path.to_path_buf(),
35            source: e,
36        })?;
37        let raw: WorkspaceToml = toml::from_str(&content).map_err(|e| ConfigError::ParseToml {
38            path: path.to_path_buf(),
39            source: e,
40        })?;
41        let root = path
42            .parent()
43            .expect("guild.toml must have a parent directory")
44            .to_path_buf();
45        Ok(Self {
46            name: raw.workspace.name,
47            project_patterns: raw.workspace.projects,
48            root,
49        })
50    }
51
52    /// Parse a workspace configuration from a TOML string (for testing).
53    pub fn from_str(content: &str, root: PathBuf) -> Result<Self, ConfigError> {
54        let raw: WorkspaceToml = toml::from_str(content).map_err(|e| ConfigError::ParseToml {
55            path: root.join("guild.toml"),
56            source: e,
57        })?;
58        Ok(Self {
59            name: raw.workspace.name,
60            project_patterns: raw.workspace.projects,
61            root,
62        })
63    }
64
65    pub fn name(&self) -> &str {
66        &self.name
67    }
68
69    pub fn project_patterns(&self) -> &[String] {
70        &self.project_patterns
71    }
72
73    pub fn root(&self) -> &Path {
74        &self.root
75    }
76}
77
78#[cfg(test)]
79mod tests {
80    use super::*;
81
82    #[test]
83    fn test_parse_workspace_config() {
84        let toml = r#"
85[workspace]
86name = "my-monorepo"
87projects = ["apps/*", "libs/*"]
88"#;
89        let config = WorkspaceConfig::from_str(toml, PathBuf::from("/tmp")).unwrap();
90        assert_eq!(config.name(), "my-monorepo");
91        assert_eq!(config.project_patterns(), &["apps/*", "libs/*"]);
92        assert_eq!(config.root(), Path::new("/tmp"));
93    }
94
95    #[test]
96    fn test_parse_workspace_missing_name() {
97        let toml = r#"
98[workspace]
99projects = ["apps/*"]
100"#;
101        assert!(WorkspaceConfig::from_str(toml, PathBuf::from("/tmp")).is_err());
102    }
103
104    #[test]
105    fn test_parse_workspace_missing_projects() {
106        let toml = r#"
107[workspace]
108name = "test"
109"#;
110        assert!(WorkspaceConfig::from_str(toml, PathBuf::from("/tmp")).is_err());
111    }
112}