1use std::{
2 collections::{hash_map::Entry, HashMap},
3 io::ErrorKind,
4 path::{Path, PathBuf},
5};
6
7use etcetera::BaseStrategy;
8use serde::{Deserialize, Serialize};
9
10use crate::{
11 label::Label, metadata::RegistryMetadata, package::PackageRef, registry::Registry, Error,
12};
13
14mod toml;
15
16const DEFAULT_FALLBACK_NAMESPACE_REGISTRIES: &[(&str, &str)] =
17 &[("wasi", "wasi.dev"), ("ba", "bytecodealliance.org")];
18
19#[derive(Debug, Clone, Serialize)]
26#[serde(into = "toml::TomlConfig")]
27pub struct Config {
28 default_registry: Option<Registry>,
29 namespace_registries: HashMap<Label, RegistryMapping>,
30 package_registry_overrides: HashMap<PackageRef, RegistryMapping>,
31 fallback_namespace_registries: HashMap<Label, Registry>,
33 registry_configs: HashMap<Registry, RegistryConfig>,
34}
35
36#[derive(Debug, Clone, Serialize, Deserialize)]
38#[serde(untagged)]
39pub enum RegistryMapping {
40 Registry(Registry),
42 Custom(CustomConfig),
44}
45
46#[derive(Debug, Clone, Serialize, Deserialize)]
48pub struct CustomConfig {
49 pub registry: Registry,
53 pub metadata: RegistryMetadata,
56}
57
58impl Default for Config {
59 fn default() -> Self {
60 let fallback_namespace_registries = DEFAULT_FALLBACK_NAMESPACE_REGISTRIES
61 .iter()
62 .map(|(k, v)| (k.parse().unwrap(), v.parse().unwrap()))
63 .collect();
64 Self {
65 default_registry: Default::default(),
66 namespace_registries: Default::default(),
67 package_registry_overrides: Default::default(),
68 fallback_namespace_registries,
69 registry_configs: Default::default(),
70 }
71 }
72}
73
74impl Config {
75 pub fn empty() -> Self {
80 Self {
81 default_registry: Default::default(),
82 namespace_registries: Default::default(),
83 package_registry_overrides: Default::default(),
84 fallback_namespace_registries: Default::default(),
85 registry_configs: Default::default(),
86 }
87 }
88
89 pub async fn global_defaults() -> Result<Self, Error> {
99 let mut config = Self::default();
100 if let Some(global_config) = Self::read_global_config().await? {
101 config.merge(global_config);
102 }
103 Ok(config)
104 }
105
106 pub fn global_config_path() -> Option<PathBuf> {
108 etcetera::choose_base_strategy()
109 .ok()
110 .map(|strat| strat.config_dir().join("wasm-pkg").join("config.toml"))
111 }
112
113 pub async fn read_global_config() -> Result<Option<Self>, Error> {
115 let path = match Config::global_config_path() {
116 Some(path) => path,
117 None => return Ok(None),
118 };
119 let contents = match tokio::fs::read_to_string(&path).await {
120 Ok(contents) => contents,
121 Err(err) if err.kind() == ErrorKind::NotFound => return Ok(None),
122 Err(err) => return Err(Error::ConfigFileIoError(err)),
123 };
124 Ok(Some(Self::from_toml(&contents)?))
125 }
126
127 pub async fn from_file(path: impl AsRef<Path>) -> Result<Self, Error> {
129 let contents = tokio::fs::read_to_string(path)
130 .await
131 .map_err(Error::ConfigFileIoError)?;
132 Self::from_toml(&contents)
133 }
134
135 pub fn from_toml(contents: &str) -> Result<Self, Error> {
137 let toml_cfg: toml::TomlConfig =
138 ::toml::from_str(contents).map_err(Error::invalid_config)?;
139 Ok(toml_cfg.into())
140 }
141
142 pub async fn to_file(&self, path: impl AsRef<Path>) -> Result<(), Error> {
144 let toml_str = ::toml::to_string(&self).map_err(Error::invalid_config)?;
145 tokio::fs::write(path, toml_str)
146 .await
147 .map_err(Error::ConfigFileIoError)
148 }
149
150 pub fn merge(&mut self, other: Self) {
152 let Self {
153 default_registry,
154 namespace_registries,
155 package_registry_overrides,
156 fallback_namespace_registries,
157 registry_configs,
158 } = other;
159 if default_registry.is_some() {
160 self.default_registry = default_registry;
161 }
162 self.namespace_registries.extend(namespace_registries);
163 self.package_registry_overrides
164 .extend(package_registry_overrides);
165 self.fallback_namespace_registries
166 .extend(fallback_namespace_registries);
167 for (registry, config) in registry_configs {
168 match self.registry_configs.entry(registry) {
169 Entry::Occupied(mut occupied) => occupied.get_mut().merge(config),
170 Entry::Vacant(vacant) => {
171 vacant.insert(config);
172 }
173 }
174 }
175 }
176
177 pub fn resolve_registry(&self, package: &PackageRef) -> Option<&Registry> {
185 if let Some(RegistryMapping::Registry(reg)) = self.package_registry_overrides.get(package) {
186 Some(reg)
187 } else if let Some(RegistryMapping::Custom(custom)) =
188 self.package_registry_overrides.get(package)
189 {
190 Some(&custom.registry)
191 } else if let Some(RegistryMapping::Registry(reg)) =
192 self.namespace_registries.get(package.namespace())
193 {
194 Some(reg)
195 } else if let Some(RegistryMapping::Custom(custom)) =
196 self.namespace_registries.get(package.namespace())
197 {
198 Some(&custom.registry)
199 } else if let Some(reg) = self.default_registry.as_ref() {
200 Some(reg)
201 } else if let Some(reg) = self.fallback_namespace_registries.get(package.namespace()) {
202 Some(reg)
203 } else {
204 None
205 }
206 }
207
208 pub fn default_registry(&self) -> Option<&Registry> {
210 self.default_registry.as_ref()
211 }
212
213 pub fn set_default_registry(&mut self, registry: Option<Registry>) {
217 self.default_registry = registry;
218 }
219
220 pub fn namespace_registry(&self, namespace: &Label) -> Option<&RegistryMapping> {
225 self.namespace_registries.get(namespace)
226 }
227
228 pub fn set_namespace_registry(&mut self, namespace: Label, registry: RegistryMapping) {
230 self.namespace_registries.insert(namespace, registry);
231 }
232
233 pub fn package_registry_override(&self, package: &PackageRef) -> Option<&RegistryMapping> {
238 self.package_registry_overrides.get(package)
239 }
240
241 pub fn set_package_registry_override(
243 &mut self,
244 package: PackageRef,
245 registry: RegistryMapping,
246 ) {
247 self.package_registry_overrides.insert(package, registry);
248 }
249
250 pub fn registry_config(&self, registry: &Registry) -> Option<&RegistryConfig> {
252 self.registry_configs.get(registry)
253 }
254
255 pub fn get_or_insert_registry_config_mut(
258 &mut self,
259 registry: &Registry,
260 ) -> &mut RegistryConfig {
261 if !self.registry_configs.contains_key(registry) {
262 self.registry_configs
263 .insert(registry.clone(), Default::default());
264 }
265 self.registry_configs.get_mut(registry).unwrap()
266 }
267}
268
269#[derive(Clone, Default)]
270pub struct RegistryConfig {
271 default_backend: Option<String>,
272 backend_configs: HashMap<String, ::toml::Table>,
273}
274
275impl RegistryConfig {
276 pub fn merge(&mut self, other: Self) {
278 let Self {
279 default_backend: backend_type,
280 backend_configs,
281 } = other;
282 if backend_type.is_some() {
283 self.default_backend = backend_type;
284 }
285 for (ty, config) in backend_configs {
286 match self.backend_configs.entry(ty) {
287 Entry::Occupied(mut occupied) => occupied.get_mut().extend(config),
288 Entry::Vacant(vacant) => {
289 vacant.insert(config);
290 }
291 }
292 }
293 }
294
295 pub fn default_backend(&self) -> Option<&str> {
298 match self.default_backend.as_deref() {
299 Some(ty) => Some(ty),
300 None => {
301 if self.backend_configs.len() == 1 {
302 self.backend_configs.keys().next().map(|ty| ty.as_str())
303 } else {
304 None
305 }
306 }
307 }
308 }
309
310 pub fn set_default_backend(&mut self, default_backend: Option<String>) {
314 self.default_backend = default_backend;
315 }
316
317 pub fn configured_backend_types(&self) -> impl Iterator<Item = &str> {
319 self.backend_configs.keys().map(|ty| ty.as_str())
320 }
321
322 pub fn backend_config<'a, T: Deserialize<'a>>(
327 &'a self,
328 backend_type: &str,
329 ) -> Result<Option<T>, Error> {
330 let Some(table) = self.backend_configs.get(backend_type) else {
331 return Ok(None);
332 };
333 let config = table.clone().try_into().map_err(Error::invalid_config)?;
334 Ok(Some(config))
335 }
336
337 pub fn set_backend_config<T: Serialize>(
339 &mut self,
340 backend_type: impl Into<String>,
341 backend_config: T,
342 ) -> Result<(), Error> {
343 let table = ::toml::Table::try_from(backend_config).map_err(Error::invalid_config)?;
344 self.backend_configs.insert(backend_type.into(), table);
345 Ok(())
346 }
347}
348
349impl std::fmt::Debug for RegistryConfig {
350 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
351 f.debug_struct("RegistryConfig")
352 .field("backend_type", &self.default_backend)
353 .field(
354 "backend_configs",
355 &DebugBackendConfigs(&self.backend_configs),
356 )
357 .finish()
358 }
359}
360
361struct DebugBackendConfigs<'a>(&'a HashMap<String, ::toml::Table>);
363
364impl std::fmt::Debug for DebugBackendConfigs<'_> {
365 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
366 f.debug_map()
367 .entries(self.0.keys().map(|ty| (ty, &"<HIDDEN>")))
368 .finish()
369 }
370}