mcpls_core/config/
workspace_root.rs1use std::path::{Path, PathBuf};
4
5use serde::{Deserialize, Serialize};
6
7use crate::bridge::join_relative_root;
8
9#[derive(thiserror::Error, Debug, Clone, Copy, PartialEq, Eq)]
11#[error("workspace.roots entries cannot be empty")]
12pub struct InvalidWorkspaceRoot;
13
14#[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
30#[serde(try_from = "PathBuf", into = "PathBuf")]
31pub struct ConfiguredRoot(PathBuf);
32
33impl ConfiguredRoot {
34 pub fn new(root: impl Into<PathBuf>) -> Result<Self, InvalidWorkspaceRoot> {
40 let root = root.into();
41 if root.is_empty() {
42 return Err(InvalidWorkspaceRoot);
43 }
44 Ok(Self(root))
45 }
46
47 #[must_use]
49 pub fn as_path(&self) -> &Path {
50 &self.0
51 }
52
53 #[must_use]
56 pub(crate) fn join_onto(&self, base: &Path) -> Self {
57 let joined = join_relative_root(base, &self.0);
58 if joined.is_empty() {
59 self.clone()
60 } else {
61 Self(joined)
62 }
63 }
64}
65
66impl TryFrom<PathBuf> for ConfiguredRoot {
67 type Error = InvalidWorkspaceRoot;
68
69 fn try_from(root: PathBuf) -> Result<Self, Self::Error> {
70 Self::new(root)
71 }
72}
73
74impl From<ConfiguredRoot> for PathBuf {
75 fn from(root: ConfiguredRoot) -> Self {
76 root.0
77 }
78}
79
80impl AsRef<Path> for ConfiguredRoot {
81 fn as_ref(&self) -> &Path {
82 self.as_path()
83 }
84}
85
86#[cfg(test)]
87mod tests {
88 use super::*;
89
90 #[test]
91 fn test_rejects_empty_and_keeps_the_path_as_written() {
92 assert_eq!(ConfiguredRoot::new(""), Err(InvalidWorkspaceRoot));
93 let root = ConfiguredRoot::new("rel/dir").unwrap();
94 assert_eq!(root.as_path(), Path::new("rel/dir"));
95 assert_eq!(PathBuf::from(root), PathBuf::from("rel/dir"));
96 }
97
98 #[test]
99 fn test_deserialization_applies_the_same_rule() {
100 assert!(serde_json::from_str::<ConfiguredRoot>("\"\"").is_err());
101 let root: ConfiguredRoot = serde_json::from_str("\"a\"").unwrap();
102 assert_eq!(serde_json::to_string(&root).unwrap(), "\"a\"");
103 }
104
105 #[test]
106 fn test_join_onto_appends_the_relative_tail() {
107 let root = ConfiguredRoot::new("proj").unwrap();
108 assert_eq!(
109 root.join_onto(Path::new("base")).as_path(),
110 Path::new("base").join("proj")
111 );
112 }
113}