Skip to main content

spin_serde/
dependencies.rs

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