wasm_pkg_common/config/
toml.rs

1// TODO: caused by inner bytes::Bytes; probably fixed in Rust 1.79
2#![allow(clippy::mutable_key_type)]
3
4use std::collections::HashMap;
5
6use serde::{Deserialize, Serialize};
7
8use crate::{label::Label, package::PackageRef, registry::Registry};
9
10use super::RegistryMapping;
11
12#[derive(Deserialize, Serialize)]
13#[serde(deny_unknown_fields)]
14pub struct TomlConfig {
15    default_registry: Option<Registry>,
16    #[serde(default)]
17    namespace_registries: HashMap<Label, RegistryMapping>,
18    #[serde(default)]
19    package_registry_overrides: HashMap<PackageRef, RegistryMapping>,
20    #[serde(default)]
21    registry: HashMap<Registry, TomlRegistryConfig>,
22}
23
24impl From<TomlConfig> for super::Config {
25    fn from(value: TomlConfig) -> Self {
26        let TomlConfig {
27            default_registry,
28            namespace_registries,
29            package_registry_overrides,
30            registry,
31        } = value;
32
33        let registry_configs = registry
34            .into_iter()
35            .map(|(reg, config)| (reg, config.into()))
36            .collect();
37
38        Self {
39            default_registry,
40            namespace_registries,
41            package_registry_overrides,
42            fallback_namespace_registries: Default::default(),
43            registry_configs,
44        }
45    }
46}
47
48impl From<super::Config> for TomlConfig {
49    fn from(value: super::Config) -> Self {
50        let registry = value
51            .registry_configs
52            .into_iter()
53            .map(|(reg, config)| (reg, config.into()))
54            .collect();
55        Self {
56            default_registry: value.default_registry,
57            namespace_registries: value.namespace_registries,
58            package_registry_overrides: value.package_registry_overrides,
59            registry,
60        }
61    }
62}
63
64#[derive(Deserialize, Serialize)]
65struct TomlRegistryConfig {
66    #[serde(alias = "type")]
67    default: Option<String>,
68    #[serde(flatten)]
69    backend_configs: HashMap<String, toml::Table>,
70}
71
72impl From<TomlRegistryConfig> for super::RegistryConfig {
73    fn from(value: TomlRegistryConfig) -> Self {
74        let TomlRegistryConfig {
75            default,
76            backend_configs,
77        } = value;
78        Self {
79            default_backend: default,
80            backend_configs,
81        }
82    }
83}
84
85impl From<super::RegistryConfig> for TomlRegistryConfig {
86    fn from(value: super::RegistryConfig) -> Self {
87        let super::RegistryConfig {
88            default_backend: backend_default,
89            backend_configs,
90        } = value;
91        Self {
92            default: backend_default,
93            backend_configs,
94        }
95    }
96}
97
98#[cfg(test)]
99mod tests {
100    use super::*;
101
102    #[test]
103    fn smoke_test() {
104        let toml_config = toml::toml! {
105            default_registry = "example.com"
106
107            [namespace_registries]
108            wasi = "wasi.dev"
109
110            [package_registry_overrides]
111            "example:foo" = "example.com"
112
113            [registry."wasi.dev".oci]
114            auth = { username = "open", password = "sesame" }
115
116            [registry."example.com"]
117            type = "test"
118            test = { token = "top_secret" }
119        };
120        let wasi_dev: Registry = "wasi.dev".parse().unwrap();
121        let example_com: Registry = "example.com".parse().unwrap();
122
123        let toml_cfg: TomlConfig = toml_config.try_into().unwrap();
124        let cfg = crate::config::Config::from(toml_cfg);
125
126        assert_eq!(cfg.default_registry(), Some(&example_com));
127        assert_eq!(
128            cfg.resolve_registry(&"wasi:http".parse().unwrap()),
129            Some(&wasi_dev)
130        );
131        assert_eq!(
132            cfg.resolve_registry(&"example:foo".parse().unwrap()),
133            Some(&example_com)
134        );
135
136        #[derive(Deserialize)]
137        struct TestConfig {
138            token: String,
139        }
140        let test_cfg: TestConfig = cfg
141            .registry_config(&example_com)
142            .unwrap()
143            .backend_config("test")
144            .unwrap()
145            .unwrap();
146        assert_eq!(test_cfg.token, "top_secret");
147    }
148
149    #[test]
150    fn type_parses_correctly() {
151        let toml_config = toml::toml! {
152            [namespace_registries]
153            test = "localhost:1234"
154
155            [package_registry_overrides]
156
157            [registry."localhost:1234".warg]
158            config_file = "/a/path"
159        };
160
161        let toml_cfg: TomlConfig = toml_config.try_into().unwrap();
162        let cfg = crate::config::Config::from(toml_cfg);
163        let reg_conf = cfg
164            .registry_config(&"localhost:1234".parse().unwrap())
165            .expect("Should have config for registry");
166        assert_eq!(
167            reg_conf
168                .default_backend()
169                .expect("Should have a default set"),
170            "warg"
171        );
172
173        let toml_config = toml::toml! {
174            [namespace_registries]
175            test = "localhost:1234"
176
177            [package_registry_overrides]
178
179            [registry."localhost:1234".warg]
180            config_file = "/a/path"
181            [registry."localhost:1234".oci]
182            auth = { username = "open", password = "sesame" }
183        };
184
185        let toml_cfg: TomlConfig = toml_config.try_into().unwrap();
186        let cfg = crate::config::Config::from(toml_cfg);
187        let reg_conf = cfg
188            .registry_config(&"localhost:1234".parse().unwrap())
189            .expect("Should have config for registry");
190        assert!(
191            reg_conf.default_backend().is_none(),
192            "Should not have a type set when two configs exist"
193        );
194
195        let toml_config = toml::toml! {
196            [namespace_registries]
197            test = "localhost:1234"
198
199            [package_registry_overrides]
200
201            [registry."localhost:1234"]
202            type = "foobar"
203            [registry."localhost:1234".warg]
204            config_file = "/a/path"
205            [registry."localhost:1234".oci]
206            auth = { username = "open", password = "sesame" }
207        };
208
209        let toml_cfg: TomlConfig = toml_config.try_into().unwrap();
210        let cfg = crate::config::Config::from(toml_cfg);
211        let reg_conf = cfg
212            .registry_config(&"localhost:1234".parse().unwrap())
213            .expect("Should have config for registry");
214        assert_eq!(
215            reg_conf
216                .default_backend()
217                .expect("Should have a default set using the type alias"),
218            "foobar"
219        );
220
221        let toml_config = toml::toml! {
222            [namespace_registries]
223            test = "localhost:1234"
224
225            [registry."localhost:1234"]
226            default = "foobar"
227            [registry."localhost:1234".warg]
228            config_file = "/a/path"
229            [registry."localhost:1234".oci]
230            auth = { username = "open", password = "sesame" }
231        };
232
233        let toml_cfg: TomlConfig = toml_config.try_into().unwrap();
234        let cfg = crate::config::Config::from(toml_cfg);
235        let reg_conf = cfg
236            .registry_config(&"localhost:1234".parse().unwrap())
237            .expect("Should have config for registry");
238        assert_eq!(
239            reg_conf
240                .default_backend()
241                .expect("Should have a default set"),
242            "foobar"
243        );
244    }
245
246    #[test]
247    fn test_custom_namespace_config() {
248        let toml_config = toml::toml! {
249            [namespace_registries]
250            test = { registry = "localhost", metadata = { preferredProtocol = "oci", "oci" = {registry = "ghcr.io", namespacePrefix = "webassembly/" } } }
251            foo = "foo:1234"
252
253            [package_registry_overrides]
254            "foo:bar" = { registry = "localhost", metadata = { preferredProtocol = "oci", "oci" = {registry = "ghcr.io", namespacePrefix = "webassembly/" } } }
255
256            [registry."localhost".oci]
257            auth = { username = "open", password = "sesame" }
258        };
259
260        let toml_cfg: TomlConfig = toml_config.try_into().unwrap();
261        let cfg = crate::config::Config::from(toml_cfg);
262
263        // First check the the normal string case works
264        let ns_config = cfg
265            .namespace_registry(&"foo".parse().unwrap())
266            .expect("Should have a namespace config");
267        let reg = match ns_config {
268            RegistryMapping::Registry(r) => r,
269            _ => panic!("Should have a registry namespace config"),
270        };
271        assert_eq!(
272            reg,
273            &"foo:1234".parse::<Registry>().unwrap(),
274            "Should have a registry"
275        );
276
277        let ns_config = cfg
278            .namespace_registry(&"test".parse().unwrap())
279            .expect("Should have a namespace config");
280        let custom = match ns_config {
281            RegistryMapping::Custom(c) => c,
282            _ => panic!("Should have a custom namespace config"),
283        };
284        assert_eq!(
285            custom.registry,
286            "localhost".parse().unwrap(),
287            "Should have a registry"
288        );
289        assert_eq!(
290            custom.metadata.preferred_protocol(),
291            Some("oci"),
292            "Should have a preferred protocol"
293        );
294        // Specific deserializations are tested in the client model
295        let map = custom
296            .metadata
297            .protocol_configs
298            .get("oci")
299            .expect("Should have a protocol config");
300        assert_eq!(
301            map.get("registry").expect("registry should exist"),
302            "ghcr.io",
303            "Should have a registry"
304        );
305        assert_eq!(
306            map.get("namespacePrefix")
307                .expect("namespacePrefix should exist"),
308            "webassembly/",
309            "Should have a namespace prefix"
310        );
311
312        // Now test the same thing for a package override
313        let ns_config = cfg
314            .package_registry_override(&"foo:bar".parse().unwrap())
315            .expect("Should have a package override config");
316        let custom = match ns_config {
317            RegistryMapping::Custom(c) => c,
318            _ => panic!("Should have a custom namespace config"),
319        };
320        assert_eq!(
321            custom.registry,
322            "localhost".parse().unwrap(),
323            "Should have a registry"
324        );
325        assert_eq!(
326            custom.metadata.preferred_protocol(),
327            Some("oci"),
328            "Should have a preferred protocol"
329        );
330        // Specific deserializations are tested in the client model
331        let map = custom
332            .metadata
333            .protocol_configs
334            .get("oci")
335            .expect("Should have a protocol config");
336        assert_eq!(
337            map.get("registry").expect("registry should exist"),
338            "ghcr.io",
339            "Should have a registry"
340        );
341        assert_eq!(
342            map.get("namespacePrefix")
343                .expect("namespacePrefix should exist"),
344            "webassembly/",
345            "Should have a namespace prefix"
346        );
347    }
348}