Skip to main content

morphir_core/naming/
package_name.rs

1use super::Path;
2use schemars::JsonSchema;
3use serde::{Deserialize, Serialize};
4use std::convert::Infallible;
5use std::fmt;
6use std::str::FromStr;
7
8/// PackageName is a newtype wrapper around Path for type safety.
9/// It distinguishes package paths from module paths at the type level.
10///
11/// Serializes as a canonical string (e.g., "my-org/my-lib") for V4 format.
12/// Deserializes from both string (V4) and array (Classic) formats.
13#[derive(Debug, Clone, PartialEq, Eq, Hash, JsonSchema)]
14pub struct PackageName(pub Path);
15
16impl Serialize for PackageName {
17    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
18    where
19        S: serde::Serializer,
20    {
21        // Serialize as canonical string for V4 format
22        serializer.serialize_str(&self.0.to_string())
23    }
24}
25
26impl<'de> Deserialize<'de> for PackageName {
27    fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
28    where
29        D: serde::Deserializer<'de>,
30    {
31        // Delegate to Path which handles both string and array formats
32        let path = Path::deserialize(deserializer)?;
33        Ok(PackageName(path))
34    }
35}
36
37impl PackageName {
38    /// Create a new PackageName from a Path
39    pub fn new(path: Path) -> Self {
40        Self(path)
41    }
42
43    /// Create a PackageName from a string (e.g., "org/package")
44    ///
45    /// This is a convenience wrapper around `FromStr::from_str`.
46    pub fn parse(s: &str) -> Self {
47        Self(Path::new(s))
48    }
49
50    /// Get the underlying Path
51    pub fn as_path(&self) -> &Path {
52        &self.0
53    }
54
55    /// Get the underlying Path. Equivalent to [`PackageName::as_path`].
56    pub fn path(&self) -> &Path {
57        &self.0
58    }
59
60    /// Convert to the underlying Path
61    pub fn into_path(self) -> Path {
62        self.0
63    }
64
65    /// Check if the package name is empty
66    pub fn is_empty(&self) -> bool {
67        self.0.is_empty()
68    }
69
70    /// Render using [`super::CANONICAL_STYLE`].
71    pub fn to_canonical_string(&self) -> String {
72        self.0.to_canonical_string()
73    }
74
75    /// Parse a canonical package path in either encoding.
76    pub fn from_canonical_string(source: &str) -> Result<Self, String> {
77        Path::from_canonical_string(source).map(Self)
78    }
79}
80
81impl fmt::Display for PackageName {
82    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
83        write!(f, "{}", self.0)
84    }
85}
86
87impl From<Path> for PackageName {
88    fn from(path: Path) -> Self {
89        Self(path)
90    }
91}
92
93impl From<PackageName> for Path {
94    fn from(pkg: PackageName) -> Self {
95        pkg.0
96    }
97}
98
99impl FromStr for PackageName {
100    type Err = Infallible;
101
102    fn from_str(s: &str) -> Result<Self, Self::Err> {
103        Ok(Self(Path::new(s)))
104    }
105}
106
107#[cfg(test)]
108mod tests {
109    use super::*;
110
111    #[test]
112    fn test_package_name_from_str() {
113        let pkg = PackageName::parse("org/morphir/sdk");
114        assert_eq!(pkg.to_string(), "org/morphir/sdk");
115    }
116
117    #[test]
118    fn test_package_name_equality() {
119        let pkg1 = PackageName::parse("my/package");
120        let pkg2 = PackageName::parse("my/package");
121        assert_eq!(pkg1, pkg2);
122    }
123
124    #[test]
125    fn test_package_name_from_str_trait() {
126        let pkg: PackageName = "my/package".parse().unwrap();
127        assert_eq!(pkg.to_string(), "my/package");
128    }
129
130    #[test]
131    fn test_package_name_from_path() {
132        let path = Path::new("test/pkg");
133        let pkg = PackageName::from(path.clone());
134        assert_eq!(pkg.as_path(), &path);
135    }
136}