Skip to main content

wasm_compose/
config.rs

1//! Module for composition configuration.
2
3use anyhow::Result;
4use indexmap::IndexMap;
5use std::{path::PathBuf, str::FromStr};
6
7/// An explicit transitive dependency of a composed component.
8#[derive(Debug, Clone, Default)]
9#[cfg_attr(feature = "serde", derive(serde_derive::Deserialize))]
10#[cfg_attr(
11    feature = "serde",
12    serde(rename_all = "kebab-case", deny_unknown_fields)
13)]
14pub struct Dependency {
15    /// The path to the dependency's component file.
16    pub path: PathBuf,
17}
18
19impl FromStr for Dependency {
20    type Err = ();
21
22    fn from_str(s: &str) -> Result<Self, Self::Err> {
23        Ok(Self { path: s.into() })
24    }
25}
26
27/// An argument of an instantiation.
28#[derive(Debug, Clone, Default)]
29#[cfg_attr(feature = "serde", derive(serde_derive::Deserialize))]
30#[cfg_attr(
31    feature = "serde",
32    serde(rename_all = "kebab-case", deny_unknown_fields)
33)]
34pub struct InstantiationArg {
35    /// The name of the instance passed as the argument.
36    pub instance: String,
37
38    /// The name of the instance export to use as the argument.
39    ///
40    /// If `None`, the instance itself will be used as the argument.
41    #[cfg_attr(feature = "serde", serde(default))]
42    pub export: Option<String>,
43}
44
45impl FromStr for InstantiationArg {
46    type Err = ();
47
48    fn from_str(s: &str) -> Result<Self, Self::Err> {
49        Ok(Self {
50            instance: s.to_string(),
51            export: None,
52        })
53    }
54}
55
56/// An instantiation of a component.
57#[derive(Debug, Clone, Default)]
58#[cfg_attr(feature = "serde", derive(serde_derive::Deserialize))]
59#[cfg_attr(
60    feature = "serde",
61    serde(rename_all = "kebab-case", deny_unknown_fields)
62)]
63pub struct Instantiation {
64    /// The name of the dependency being instantiated.
65    ///
66    /// Defaults to a dependency with the same name as the instantiation.
67    pub dependency: Option<String>,
68
69    /// The explicit instantiation arguments.
70    ///
71    /// Maps the argument name to the name of the instance to pass as
72    /// the argument.
73    #[cfg_attr(feature = "serde", serde(default, deserialize_with = "de::index_map"))]
74    pub arguments: IndexMap<String, InstantiationArg>,
75}
76
77/// The configuration for composing a WebAssembly component.
78#[derive(Default, Debug, Clone)]
79#[cfg_attr(feature = "serde", derive(serde_derive::Deserialize))]
80#[cfg_attr(
81    feature = "serde",
82    serde(rename_all = "kebab-case", deny_unknown_fields)
83)]
84pub struct Config {
85    /// The path of the configuration file's directory.
86    ///
87    /// All paths are relative to this directory.
88    #[cfg_attr(feature = "serde", serde(skip))]
89    pub dir: PathBuf,
90
91    /// Components whose exports define import dependencies to fulfill from.
92    #[cfg_attr(feature = "serde", serde(default))]
93    pub definitions: Vec<PathBuf>,
94
95    /// The paths to search when automatically resolving dependencies.
96    ///
97    /// The config directory is always searched first.
98    #[cfg_attr(feature = "serde", serde(default))]
99    pub search_paths: Vec<PathBuf>,
100
101    /// Whether or not to skip validation of the output component.
102    #[cfg_attr(feature = "serde", serde(default))]
103    pub skip_validation: bool,
104
105    /// Whether or not to import components in the composed component.
106    ///
107    /// By default, components are defined rather than imported in
108    /// the composed component.
109    #[cfg_attr(feature = "serde", serde(default))]
110    pub import_components: bool,
111
112    /// Whether or not to disallow instance imports in the output component.
113    ///
114    /// Enabling this option will cause an error if a dependency cannot be
115    /// located.
116    #[cfg_attr(feature = "serde", serde(default))]
117    pub disallow_imports: bool,
118
119    /// The explicit, transitive dependencies of the root component.
120    #[cfg_attr(feature = "serde", serde(default, deserialize_with = "de::index_map"))]
121    pub dependencies: IndexMap<String, Dependency>,
122
123    /// The explicit instantiations of the composed component.
124    #[cfg_attr(feature = "serde", serde(default))]
125    pub instantiations: IndexMap<String, Instantiation>,
126}
127
128impl Config {
129    /// Reads a composition configuration from the given path.
130    #[cfg(feature = "yaml")]
131    pub fn from_file(path: impl Into<PathBuf>) -> Result<Self> {
132        use anyhow::Context;
133        use std::path::Path;
134
135        let path = path.into();
136
137        log::info!("reading configuration file `{}`", path.display());
138
139        let config = std::fs::read_to_string(&path)
140            .with_context(|| format!("failed to read configuration file `{}`", path.display()))?;
141
142        let mut config: Config = serde_yaml2::from_str(&config)
143            .with_context(|| format!("failed to parse configuration file `{}`", path.display()))?;
144
145        config.dir = path.parent().map(Path::to_path_buf).unwrap_or_default();
146
147        Ok(config)
148    }
149
150    /// Gets the dependency name for the given instance name.
151    pub fn dependency_name<'a>(&'a self, instance: &'a str) -> &'a str {
152        self.instantiations
153            .get(instance)
154            .and_then(|i| i.dependency.as_deref())
155            .unwrap_or(instance)
156    }
157}
158
159#[cfg(feature = "serde")]
160mod de {
161    use indexmap::IndexMap;
162    use serde::{
163        Deserialize, Deserializer,
164        de::{self, MapAccess, Visitor},
165    };
166    use std::{fmt, hash::Hash, marker::PhantomData, str::FromStr};
167
168    /// Utility function for deserializing index maps where the values can
169    /// be deserialized either from a string or from a map value.
170    pub fn index_map<'de, K, V, D>(deserializer: D) -> Result<IndexMap<K, V>, D::Error>
171    where
172        K: Hash + Eq + Deserialize<'de>,
173        V: Deserialize<'de> + FromStr<Err = ()>,
174        D: Deserializer<'de>,
175    {
176        deserializer.deserialize_map(MapVisitor(PhantomData))
177    }
178
179    struct MapVisitor<K, V>(PhantomData<(K, V)>);
180
181    impl<'de, K, V> Visitor<'de> for MapVisitor<K, V>
182    where
183        K: Hash + Eq + Deserialize<'de>,
184        V: Deserialize<'de> + FromStr<Err = ()>,
185    {
186        type Value = IndexMap<K, V>;
187
188        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
189            formatter.write_str("map")
190        }
191
192        fn visit_map<M>(self, mut access: M) -> Result<Self::Value, M::Error>
193        where
194            M: MapAccess<'de>,
195        {
196            struct Wrapper<V>(V);
197
198            impl<'de, V> Deserialize<'de> for Wrapper<V>
199            where
200                V: Deserialize<'de> + FromStr<Err = ()>,
201            {
202                fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
203                where
204                    D: Deserializer<'de>,
205                {
206                    Ok(Self(
207                        deserializer.deserialize_any(StringOrMapVisitor(PhantomData))?,
208                    ))
209                }
210            }
211
212            let mut map = Self::Value::with_capacity(access.size_hint().unwrap_or(0));
213            while let Some((key, value)) = access.next_entry::<_, Wrapper<V>>()? {
214                map.insert(key, value.0);
215            }
216
217            Ok(map)
218        }
219    }
220
221    struct StringOrMapVisitor<V>(PhantomData<V>);
222
223    impl<'de, V> Visitor<'de> for StringOrMapVisitor<V>
224    where
225        V: Deserialize<'de> + FromStr<Err = ()>,
226    {
227        type Value = V;
228
229        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
230            formatter.write_str("string or map")
231        }
232
233        fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
234        where
235            E: de::Error,
236        {
237            Ok(V::from_str(value).unwrap())
238        }
239
240        fn visit_map<M>(self, access: M) -> Result<Self::Value, M::Error>
241        where
242            M: MapAccess<'de>,
243        {
244            Deserialize::deserialize(de::value::MapAccessDeserializer::new(access))
245        }
246    }
247}