spin_serde/
dependencies.rs

1//! Types for working with component dependencies.
2
3use crate::KebabId;
4use anyhow::anyhow;
5use serde::{Deserialize, Serialize};
6use std::str::FromStr;
7use wasm_pkg_common::package::PackageRef;
8
9/// Name of an import package dependency.
10///
11/// For example: `foo:bar/baz@0.1.0`, `foo:bar/baz`, `foo:bar@0.1.0`, `foo:bar`.
12#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
13#[serde(into = "String", try_from = "String")]
14pub struct DependencyPackageName {
15    /// The package spec, `foo:bar`, `foo:bar@0.1.0`.
16    pub package: PackageRef,
17    /// Package version
18    pub version: Option<semver::Version>,
19    /// Optional interface name.
20    pub interface: Option<KebabId>,
21}
22
23impl std::fmt::Display for DependencyPackageName {
24    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
25        write!(f, "{}", self.package)?;
26        if let Some(interface) = &self.interface {
27            write!(f, "/{interface}")?;
28        }
29        if let Some(version) = &self.version {
30            write!(f, "@{version}")?;
31        }
32        Ok(())
33    }
34}
35
36impl TryFrom<String> for DependencyPackageName {
37    type Error = anyhow::Error;
38
39    fn try_from(s: String) -> Result<Self, Self::Error> {
40        s.parse()
41    }
42}
43
44impl From<DependencyPackageName> for String {
45    fn from(value: DependencyPackageName) -> Self {
46        value.to_string()
47    }
48}
49
50impl FromStr for DependencyPackageName {
51    type Err = anyhow::Error;
52
53    fn from_str(s: &str) -> Result<Self, Self::Err> {
54        let (name, version) = match s.split_once('@') {
55            Some((name, version)) => (name, Some(version.parse()?)),
56            None => (s, None),
57        };
58
59        let (package, interface) = match name.split_once('/') {
60            Some((package, interface)) => (
61                package.parse()?,
62                Some(
63                    interface
64                        .to_string()
65                        .try_into()
66                        .map_err(|e| anyhow::anyhow!("{e}"))?,
67                ),
68            ),
69            None => (name.parse()?, None),
70        };
71
72        Ok(DependencyPackageName {
73            package,
74            version,
75            interface,
76        })
77    }
78}
79
80/// Name of an import dependency.
81///
82/// For example: `foo:bar/baz@0.1.0`, `foo:bar/baz`, `foo:bar@0.1.0`, `foo:bar`, `foo-bar`.
83#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
84#[serde(into = "String", try_from = "String")]
85pub enum DependencyName {
86    /// Plain name
87    Plain(KebabId),
88    /// Package spec
89    Package(DependencyPackageName),
90}
91
92// TODO: replace with derive once wasm-pkg-common is released
93impl PartialOrd for DependencyName {
94    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
95        Some(self.cmp(other))
96    }
97}
98
99// TODO: replace with derive once wasm-pkg-common is released
100impl Ord for DependencyName {
101    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
102        match (self, other) {
103            (DependencyName::Plain(a), DependencyName::Plain(b)) => a.cmp(b),
104            (DependencyName::Package(a), DependencyName::Package(b)) => {
105                let big_ole_tup = (
106                    a.package.namespace().as_ref(),
107                    a.package.name().as_ref(),
108                    a.interface.as_ref(),
109                    a.version.as_ref(),
110                );
111                let other_big_ole_tup = (
112                    b.package.namespace().as_ref(),
113                    b.package.name().as_ref(),
114                    b.interface.as_ref(),
115                    b.version.as_ref(),
116                );
117                big_ole_tup.cmp(&other_big_ole_tup)
118            }
119            (DependencyName::Plain(_), DependencyName::Package(_)) => std::cmp::Ordering::Less,
120            (DependencyName::Package(_), DependencyName::Plain(_)) => std::cmp::Ordering::Greater,
121        }
122    }
123}
124
125impl std::fmt::Display for DependencyName {
126    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
127        match self {
128            DependencyName::Plain(plain) => write!(f, "{plain}"),
129            DependencyName::Package(name) => {
130                write!(f, "{}", name.package)?;
131                if let Some(interface) = &name.interface {
132                    write!(f, "/{interface}")?;
133                }
134                if let Some(version) = &name.version {
135                    write!(f, "@{version}")?;
136                }
137                Ok(())
138            }
139        }
140    }
141}
142
143impl TryFrom<String> for DependencyName {
144    type Error = anyhow::Error;
145
146    fn try_from(s: String) -> Result<Self, Self::Error> {
147        s.parse()
148    }
149}
150
151impl From<DependencyName> for String {
152    fn from(value: DependencyName) -> Self {
153        value.to_string()
154    }
155}
156
157impl FromStr for DependencyName {
158    type Err = anyhow::Error;
159
160    fn from_str(s: &str) -> Result<Self, Self::Err> {
161        if s.contains([':', '/']) {
162            Ok(Self::Package(s.parse()?))
163        } else {
164            Ok(Self::Plain(
165                s.to_string().try_into().map_err(|e| anyhow!("{e}"))?,
166            ))
167        }
168    }
169}
170
171impl DependencyName {
172    /// Returns the package reference if this is a package dependency name.
173    pub fn package(&self) -> Option<&PackageRef> {
174        match self {
175            DependencyName::Package(name) => Some(&name.package),
176            DependencyName::Plain(_) => None,
177        }
178    }
179}