spin_environments/environment/
definition.rs1use std::collections::HashMap;
8
9use anyhow::Context;
10
11#[derive(Debug, serde::Deserialize)]
21#[serde(deny_unknown_fields)]
22pub struct EnvironmentDefinition {
23 triggers: HashMap<String, TriggerEnvironment>,
24 #[serde(default)]
25 default: Option<TriggerEnvironment>,
26 #[serde(default)]
27 metadata: Metadata,
28}
29
30#[derive(Debug, serde::Deserialize)]
34#[serde(deny_unknown_fields)]
35pub struct TriggerEnvironment {
36 worlds: Vec<WorldRef>,
37 #[serde(default)]
38 capabilities: Vec<String>,
39}
40
41impl TriggerEnvironment {
42 pub fn world_refs(&self) -> &[WorldRef] {
43 &self.worlds
44 }
45
46 pub fn capabilities(&self) -> Vec<String> {
47 self.capabilities.clone()
48 }
49}
50
51impl EnvironmentDefinition {
52 pub fn triggers(&self) -> &HashMap<String, TriggerEnvironment> {
53 &self.triggers
54 }
55
56 pub fn default(&self) -> Option<&TriggerEnvironment> {
57 self.default.as_ref()
58 }
59
60 pub fn templates(&self) -> Option<&GitRepo> {
61 self.metadata.templates.as_ref()
62 }
63
64 pub fn plugins(&self) -> &[String] {
65 &self.metadata.plugins
66 }
67}
68
69#[derive(Clone, Debug, serde::Deserialize)]
73#[serde(untagged, deny_unknown_fields)]
74pub enum WorldRef {
75 DefaultRegistry(WorldName),
76 Registry {
77 registry: String,
78 world: WorldName,
79 },
80 OciRegistry {
81 reference: String,
82 world: WorldName,
83 },
84 WitDirectory {
85 path: std::path::PathBuf,
86 world: WorldName,
87 },
88}
89
90#[derive(Clone, Debug, serde::Deserialize)]
95#[serde(try_from = "String")]
96pub struct WorldName {
97 package: wit_parser::PackageName,
98 world: String,
99}
100
101impl WorldName {
102 pub fn package(&self) -> &wit_parser::PackageName {
103 &self.package
104 }
105
106 pub fn name(&self) -> &str {
107 &self.world
108 }
109
110 pub fn package_namespaced_name(&self) -> String {
111 format!("{}:{}", self.package.namespace, self.package.name)
112 }
113
114 pub fn package_ref(&self) -> anyhow::Result<wasm_pkg_client::PackageRef> {
115 let pkg_name = self.package_namespaced_name();
116 pkg_name
117 .parse()
118 .with_context(|| format!("Environment {pkg_name} is not a valid package name"))
119 }
120
121 pub fn package_version(&self) -> Option<&semver::Version> {
122 self.package.version.as_ref()
123 }
124}
125
126impl TryFrom<String> for WorldName {
127 type Error = anyhow::Error;
128
129 fn try_from(value: String) -> Result<Self, Self::Error> {
130 use wasmparser::names::{ComponentName, ComponentNameKind};
131
132 let parsed = ComponentName::new(&value, 0)?;
134 let ComponentNameKind::Interface(itf) = parsed.kind() else {
135 anyhow::bail!("{value} is not a well-formed world name");
136 };
137
138 let package = wit_parser::PackageName {
139 namespace: itf.namespace().to_string(),
140 name: itf.package().to_string(),
141 version: itf.version(),
142 };
143
144 let world = itf.interface().to_string();
145
146 Ok(Self { package, world })
147 }
148}
149
150impl std::fmt::Display for WorldName {
151 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
152 f.write_str(&self.package.namespace)?;
153 f.write_str(":")?;
154 f.write_str(&self.package.name)?;
155 f.write_str("/")?;
156 f.write_str(&self.world)?;
157
158 if let Some(v) = self.package.version.as_ref() {
159 f.write_str("@")?;
160 f.write_str(&v.to_string())?;
161 }
162
163 Ok(())
164 }
165}
166
167#[derive(Debug, Default, serde::Deserialize)]
168pub struct Metadata {
169 #[serde(default)]
170 templates: Option<GitRepo>,
171 #[serde(default)]
172 plugins: Vec<String>,
173}
174
175#[derive(Debug, serde::Deserialize)]
176pub struct GitRepo {
177 url: String,
178 tag: Option<String>,
179}
180
181impl GitRepo {
182 pub fn url(&self) -> &str {
183 &self.url
184 }
185
186 pub fn tag(&self) -> &Option<String> {
187 &self.tag
188 }
189}
190
191#[cfg(test)]
192mod test {
193 use super::*;
194
195 #[test]
196 fn can_parse_versioned_world_name() {
197 let text = "ns:name/world@1.0.0";
198 let w = WorldName::try_from(text.to_owned()).unwrap();
199
200 assert_eq!("ns", w.package().namespace);
201 assert_eq!("name", w.package().name);
202 assert_eq!("ns:name", w.package_namespaced_name());
203 assert_eq!("ns", w.package_ref().unwrap().namespace().to_string());
204 assert_eq!("name", w.package_ref().unwrap().name().to_string());
205 assert_eq!("world", w.world);
206 assert_eq!(
207 &semver::Version::new(1, 0, 0),
208 w.package().version.as_ref().unwrap()
209 );
210
211 assert_eq!(text, w.to_string());
212 }
213
214 #[test]
215 fn can_parse_unversioned_world_name() {
216 let text = "ns:name/world";
217 let w = WorldName::try_from("ns:name/world".to_owned()).unwrap();
218
219 assert_eq!("ns", w.package().namespace);
220 assert_eq!("name", w.package().name);
221 assert_eq!("ns:name", w.package_namespaced_name());
222 assert_eq!("ns", w.package_ref().unwrap().namespace().to_string());
223 assert_eq!("name", w.package_ref().unwrap().name().to_string());
224 assert_eq!("world", w.world);
225 assert!(w.package().version.is_none());
226
227 assert_eq!(text, w.to_string());
228 }
229}