1mod env;
2pub use env::*;
3
4use serde::{Deserialize, Serialize};
5use std::path::{Path, PathBuf};
6use std::sync::LazyLock;
7use url::Url;
8use wasmer_backend_api::WasmerClient;
9
10pub static GLOBAL_CONFIG_FILE_NAME: &str = "wasmer.toml";
11pub static DEFAULT_PROD_REGISTRY: &str = "https://registry.wasmer.io/graphql";
12
13pub static DEFAULT_WASMER_DIR: LazyLock<PathBuf> =
15 LazyLock::new(|| match WasmerConfig::get_wasmer_dir() {
16 Ok(path) => path,
17 Err(e) => {
18 if let Some(install_prefix) = option_env!("WASMER_INSTALL_PREFIX") {
19 return PathBuf::from(install_prefix);
20 }
21
22 panic!("Unable to determine the wasmer dir: {e}");
23 }
24 });
25
26pub static DEFAULT_WASMER_CACHE_DIR: LazyLock<PathBuf> =
28 LazyLock::new(|| DEFAULT_WASMER_DIR.join("cache"));
29
30#[derive(Deserialize, Serialize, Debug, PartialEq, Eq)]
31pub struct WasmerConfig {
32 #[serde(default)]
34 pub telemetry_enabled: bool,
35
36 #[serde(default)]
38 pub update_notifications_enabled: bool,
39
40 pub registry: MultiRegistry,
42
43 #[serde(default)]
45 pub proxy: Proxy,
46}
47
48impl Default for WasmerConfig {
49 fn default() -> Self {
50 Self {
51 telemetry_enabled: true,
52 update_notifications_enabled: true,
53 registry: Default::default(),
54 proxy: Default::default(),
55 }
56 }
57}
58
59#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Default)]
60pub struct Proxy {
61 pub url: Option<String>,
62}
63
64#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
67pub struct MultiRegistry {
68 pub active_registry: String,
70 pub tokens: Vec<RegistryLogin>,
73}
74
75impl Default for MultiRegistry {
76 fn default() -> Self {
77 MultiRegistry {
78 active_registry: format_graphql(DEFAULT_PROD_REGISTRY),
79 tokens: Vec::new(),
80 }
81 }
82}
83
84#[allow(unused)]
85#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
86pub struct Registry {
87 pub url: String,
88 pub token: Option<String>,
89}
90
91pub fn format_graphql(registry: &str) -> String {
92 if let Ok(mut url) = Url::parse(registry) {
93 if url.has_host() {
95 if url.path() == "/" {
96 url.set_path("/graphql");
99 }
100
101 return url.to_string();
102 }
103 }
104
105 if !registry.contains("://") && !registry.contains('/') {
106 return endpoint_from_domain_name(registry);
107 }
108
109 registry.to_string()
113}
114
115fn endpoint_from_domain_name(domain_name: &str) -> String {
118 if domain_name.contains("localhost") {
119 return format!("http://{domain_name}/graphql");
120 }
121
122 format!("https://registry.{domain_name}/graphql")
123}
124
125async fn test_if_registry_present(registry: &str) -> anyhow::Result<()> {
126 let client = WasmerClient::new(url::Url::parse(registry)?, &DEFAULT_WASMER_CLI_USER_AGENT)?;
127
128 wasmer_backend_api::query::current_user(&client)
129 .await
130 .map(|_| ())
131}
132
133#[derive(PartialEq, Eq, Copy, Clone)]
134pub enum UpdateRegistry {
135 Update,
136 #[allow(unused)]
137 LeaveAsIs,
138}
139
140impl MultiRegistry {
141 pub fn remove_registry(&mut self, registry: &str) {
143 let MultiRegistry { tokens, .. } = self;
144 tokens.retain(|i| i.registry != registry);
145 tokens.retain(|i| i.registry != format_graphql(registry));
146 }
147
148 #[allow(unused)]
149 pub fn get_graphql_url(&self) -> String {
150 self.get_current_registry()
151 }
152
153 pub fn get_current_registry(&self) -> String {
155 format_graphql(&self.active_registry)
156 }
157
158 pub fn is_current_registry(&self, registry: &str) -> bool {
160 format_graphql(&self.active_registry) == format_graphql(registry)
161 }
162
163 #[allow(unused)]
164 pub fn current_login(&self) -> Option<&RegistryLogin> {
165 self.tokens
166 .iter()
167 .find(|login| login.registry == self.active_registry)
168 }
169
170 pub async fn set_current_registry(&mut self, registry: &str) {
172 let registry = format_graphql(registry);
173 if let Err(e) = test_if_registry_present(®istry).await {
174 println!("Error when trying to ping registry {registry:?}: {e}");
175 println!("WARNING: Registry {registry:?} will be used, but commands may not succeed.");
176 }
177 self.active_registry = registry;
178 }
179
180 pub fn get_login_token_for_registry(&self, registry: &str) -> Option<String> {
182 let registry_formatted = format_graphql(registry);
183 self.tokens
184 .iter()
185 .rev()
186 .find(|login| login.registry == registry || login.registry == registry_formatted)
187 .map(|login| login.token.clone())
188 }
189
190 pub fn set_login_token_for_registry(
192 &mut self,
193 registry: &str,
194 token: &str,
195 update_current_registry: UpdateRegistry,
196 ) {
197 let registry_formatted = format_graphql(registry);
198 self.tokens
199 .retain(|login| !(login.registry == registry || login.registry == registry_formatted));
200 self.tokens.push(RegistryLogin {
201 registry: format_graphql(registry),
202 token: token.to_string(),
203 });
204 if update_current_registry == UpdateRegistry::Update {
205 self.active_registry = format_graphql(registry);
206 }
207 }
208}
209
210impl WasmerConfig {
211 pub fn save<P: AsRef<Path>>(&self, to: P) -> anyhow::Result<()> {
213 use std::{fs::File, io::Write};
214 let config_serialized = toml::to_string(&self)?;
215 let mut file = File::create(to)?;
216 file.write_all(config_serialized.as_bytes())?;
217 Ok(())
218 }
219
220 pub fn from_file(wasmer_dir: &Path) -> Result<Self, String> {
221 let path = Self::get_file_location(wasmer_dir);
222 match std::fs::read_to_string(path) {
223 Ok(config_toml) => Ok(toml::from_str(&config_toml).unwrap_or_else(|_| Self::default())),
224 Err(_e) => Ok(Self::default()),
225 }
226 }
227
228 pub fn get_wasmer_dir() -> Result<PathBuf, String> {
230 Ok(
231 if let Some(folder_str) = std::env::var("WASMER_DIR").ok().filter(|s| !s.is_empty()) {
232 let folder = PathBuf::from(folder_str);
233 std::fs::create_dir_all(folder.clone())
234 .map_err(|e| format!("cannot create config directory: {e}"))?;
235 folder
236 } else {
237 let home_dir =
238 dirs::home_dir().ok_or_else(|| "cannot find home directory".to_string())?;
239 let mut folder = home_dir;
240 folder.push(".wasmer");
241 std::fs::create_dir_all(folder.clone())
242 .map_err(|e| format!("cannot create config directory: {e}"))?;
243 folder
244 },
245 )
246 }
247
248 #[allow(unused)]
249 pub fn from_env() -> Result<Self, anyhow::Error> {
251 let dir = Self::get_wasmer_dir()
252 .map_err(|err| anyhow::anyhow!("Could not determine wasmer dir: {err}"))?;
253 let file_path = Self::get_file_location(&dir);
254 Self::from_file(&file_path).map_err(|err| {
255 anyhow::anyhow!(
256 "Could not load config file at '{}': {}",
257 file_path.display(),
258 err
259 )
260 })
261 }
262
263 pub fn get_file_location(wasmer_dir: &Path) -> PathBuf {
264 wasmer_dir.join(GLOBAL_CONFIG_FILE_NAME)
265 }
266}
267
268#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
269pub struct RegistryLogin {
270 pub registry: String,
272 pub token: String,
274}
275
276#[cfg(test)]
277mod tests {
278 use super::*;
279
280 #[tokio::test]
281 async fn test_registries_switch_token() {
282 let mut registries = MultiRegistry::default();
283
284 registries
285 .set_current_registry("https://registry.wasmer.wtf")
286 .await;
287 assert_eq!(
288 registries.get_current_registry(),
289 "https://registry.wasmer.wtf/graphql".to_string()
290 );
291 registries.set_login_token_for_registry(
292 "https://registry.wasmer.io",
293 "token1",
294 UpdateRegistry::LeaveAsIs,
295 );
296 assert_eq!(
297 registries.get_current_registry(),
298 "https://registry.wasmer.wtf/graphql".to_string()
299 );
300 assert_eq!(
301 registries.get_login_token_for_registry(®istries.get_current_registry()),
302 None
303 );
304 registries
305 .set_current_registry("https://registry.wasmer.io")
306 .await;
307 assert_eq!(
308 registries.get_login_token_for_registry(®istries.get_current_registry()),
309 Some("token1".to_string())
310 );
311 registries.remove_registry("https://registry.wasmer.io");
312 assert_eq!(
313 registries.get_login_token_for_registry(®istries.get_current_registry()),
314 None
315 );
316 }
317
318 #[test]
319 fn format_registry_urls() {
320 let inputs = [
321 ("wasmer.io", "https://registry.wasmer.io/graphql"),
323 ("wasmer.wtf", "https://registry.wasmer.wtf/graphql"),
324 (
326 "https://registry.wasmer.wtf/graphql",
327 "https://registry.wasmer.wtf/graphql",
328 ),
329 (
330 "https://registry.wasmer.wtf/something/else",
331 "https://registry.wasmer.wtf/something/else",
332 ),
333 ("https://wasmer.wtf/", "https://wasmer.wtf/graphql"),
336 ("https://wasmer.wtf", "https://wasmer.wtf/graphql"),
337 (
339 "http://localhost:8000/graphql",
340 "http://localhost:8000/graphql",
341 ),
342 ("localhost:8000", "http://localhost:8000/graphql"),
343 ];
344
345 for (input, expected) in inputs {
346 let url = format_graphql(input);
347 assert_eq!(url, expected);
348 }
349 }
350}