blaze_common/
workspace.rs

1use std::{
2    collections::{BTreeMap, BTreeSet},
3    path::{Path, PathBuf},
4};
5
6use serde::{de::Error, Deserialize, Serialize};
7
8use crate::{
9    configuration_file::ConfigurationFileFormat, settings::GlobalSettings, util::normalize_path,
10};
11
12/// Main workspace configuration object.
13#[derive(Debug, Serialize, Deserialize)]
14#[serde(rename_all = "camelCase")]
15pub struct Workspace {
16    root: PathBuf,
17    configuration_file_path: PathBuf,
18    configuration_file_format: ConfigurationFileFormat,
19    #[serde(flatten)]
20    configuration: WorkspaceConfiguration,
21}
22
23#[derive(Debug, Serialize)]
24pub struct ProjectRef {
25    path: PathBuf,
26    tags: BTreeSet<String>,
27    #[serde(skip_serializing_if = "Option::is_none")]
28    description: Option<String>,
29}
30
31impl ProjectRef {
32    pub fn path(&self) -> &Path {
33        &self.path
34    }
35
36    pub fn tags(&self) -> &BTreeSet<String> {
37        &self.tags
38    }
39
40    pub fn description(&self) -> Option<&str> {
41        self.description.as_deref()
42    }
43}
44
45impl From<PathBuf> for ProjectRef {
46    fn from(path: PathBuf) -> Self {
47        Self {
48            description: None,
49            tags: BTreeSet::new(),
50            path,
51        }
52    }
53}
54
55impl<'de> Deserialize<'de> for ProjectRef {
56    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
57    where
58        D: serde::Deserializer<'de>,
59    {
60        #[derive(Deserialize)]
61        #[serde(remote = "ProjectRef")]
62        struct ProjectRefAsObject {
63            path: PathBuf,
64            #[serde(default)]
65            tags: BTreeSet<String>,
66            description: Option<String>,
67        }
68
69        #[derive(Deserialize)]
70        #[serde(untagged)]
71        enum ProjectRefDeserializationModes {
72            SinglePath(PathBuf),
73            #[serde(with = "ProjectRefAsObject")]
74            Full(ProjectRef),
75        }
76
77        Ok(
78            match ProjectRefDeserializationModes::deserialize(deserializer)? {
79                ProjectRefDeserializationModes::SinglePath(path) => {
80                    normalize_path(path).map_err(D::Error::custom)?.into()
81                }
82                ProjectRefDeserializationModes::Full(mut project_ref) => {
83                    project_ref.path =
84                        normalize_path(&project_ref.path).map_err(D::Error::custom)?;
85                    project_ref
86                }
87            },
88        )
89    }
90}
91
92#[derive(Debug, Serialize, Deserialize)]
93pub struct WorkspaceConfiguration {
94    name: String,
95    #[serde(default)]
96    projects: BTreeMap<String, ProjectRef>,
97    #[serde(default)]
98    settings: GlobalSettings,
99}
100
101impl Workspace {
102    /// Create a [`Workspace`] from configuration file metadata and deserialized content.
103    pub fn from_configuration_and_metadata<P: AsRef<Path>>(
104        source: (P, ConfigurationFileFormat),
105        configuration: WorkspaceConfiguration,
106    ) -> Self {
107        let mut root = source.0.as_ref().to_path_buf();
108        let _ = root.pop();
109
110        Self {
111            root,
112            configuration_file_path: source.0.as_ref().to_path_buf(),
113            configuration_file_format: source.1,
114            configuration,
115        }
116    }
117
118    pub fn root(&self) -> &Path {
119        &self.root
120    }
121
122    pub fn configuration_file_path(&self) -> &Path {
123        &self.configuration_file_path
124    }
125
126    pub fn configuration_file_format(&self) -> ConfigurationFileFormat {
127        self.configuration_file_format
128    }
129
130    pub fn name(&self) -> &str {
131        &self.configuration.name
132    }
133
134    pub fn projects(&self) -> &BTreeMap<String, ProjectRef> {
135        &self.configuration.projects
136    }
137
138    pub fn settings(&self) -> &GlobalSettings {
139        &self.configuration.settings
140    }
141}