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#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
85pub struct Registry {
86 pub url: String,
87 pub token: Option<String>,
88}
89
90pub fn format_graphql(registry: &str) -> String {
91 if let Ok(mut url) = Url::parse(registry) {
92 if url.has_host() {
94 if url.path() == "/" {
95 url.set_path("/graphql");
98 }
99
100 return url.to_string();
101 }
102 }
103
104 if !registry.contains("://") && !registry.contains('/') {
105 return endpoint_from_domain_name(registry);
106 }
107
108 registry.to_string()
112}
113
114fn endpoint_from_domain_name(domain_name: &str) -> String {
117 if domain_name.contains("localhost") {
118 return format!("http://{domain_name}/graphql");
119 }
120
121 format!("https://registry.{domain_name}/graphql")
122}
123
124async fn test_if_registry_present(registry: &str) -> anyhow::Result<()> {
125 let client = WasmerClient::new(url::Url::parse(registry)?, &DEFAULT_WASMER_CLI_USER_AGENT)?;
126
127 wasmer_backend_api::query::current_user(&client)
128 .await
129 .map(|_| ())
130}
131
132#[derive(PartialEq, Eq, Copy, Clone)]
133pub enum UpdateRegistry {
134 Update,
135 #[allow(unused)]
136 LeaveAsIs,
137}
138
139impl MultiRegistry {
140 pub fn remove_registry(&mut self, registry: &str) {
142 let MultiRegistry { tokens, .. } = self;
143 tokens.retain(|i| i.registry != registry);
144 tokens.retain(|i| i.registry != format_graphql(registry));
145 }
146
147 #[allow(unused)]
148 pub fn get_graphql_url(&self) -> String {
149 self.get_current_registry()
150 }
151
152 pub fn get_current_registry(&self) -> String {
154 format_graphql(&self.active_registry)
155 }
156
157 pub fn is_current_registry(&self, registry: &str) -> bool {
159 format_graphql(&self.active_registry) == format_graphql(registry)
160 }
161
162 #[allow(unused)]
163 pub fn current_login(&self) -> Option<&RegistryLogin> {
164 self.tokens
165 .iter()
166 .find(|login| login.registry == self.active_registry)
167 }
168
169 pub async fn set_current_registry(&mut self, registry: &str) {
171 let registry = format_graphql(registry);
172 if let Err(e) = test_if_registry_present(®istry).await {
173 println!("Error when trying to ping registry {registry:?}: {e}");
174 println!("WARNING: Registry {registry:?} will be used, but commands may not succeed.");
175 }
176 self.active_registry = registry;
177 }
178
179 pub fn get_login_token_for_registry(&self, registry: &str) -> Option<String> {
181 let registry_formatted = format_graphql(registry);
182 self.tokens
183 .iter()
184 .rev()
185 .find(|login| login.registry == registry || login.registry == registry_formatted)
186 .map(|login| login.token.clone())
187 }
188
189 pub fn set_login_token_for_registry(
191 &mut self,
192 registry: &str,
193 token: &str,
194 update_current_registry: UpdateRegistry,
195 ) {
196 let registry_formatted = format_graphql(registry);
197 self.tokens
198 .retain(|login| !(login.registry == registry || login.registry == registry_formatted));
199 self.tokens.push(RegistryLogin {
200 registry: format_graphql(registry),
201 token: token.to_string(),
202 });
203 if update_current_registry == UpdateRegistry::Update {
204 self.active_registry = format_graphql(registry);
205 }
206 }
207}
208
209impl WasmerConfig {
210 pub fn save<P: AsRef<Path>>(&self, to: P) -> anyhow::Result<()> {
212 use std::{fs::File, io::Write};
213 let config_serialized = toml::to_string(&self)?;
214 let mut file = File::create(to)?;
215 file.write_all(config_serialized.as_bytes())?;
216 Ok(())
217 }
218
219 pub fn from_file(wasmer_dir: &Path) -> Result<Self, String> {
220 let path = Self::get_file_location(wasmer_dir);
221 match std::fs::read_to_string(path) {
222 Ok(config_toml) => Ok(toml::from_str(&config_toml).unwrap_or_else(|_| Self::default())),
223 Err(_e) => Ok(Self::default()),
224 }
225 }
226
227 pub fn get_wasmer_dir() -> Result<PathBuf, String> {
229 Ok(
230 if let Some(folder_str) = std::env::var("WASMER_DIR").ok().filter(|s| !s.is_empty()) {
231 let folder = PathBuf::from(folder_str);
232 std::fs::create_dir_all(folder.clone())
233 .map_err(|e| format!("cannot create config directory: {e}"))?;
234 folder
235 } else {
236 let home_dir =
237 dirs::home_dir().ok_or_else(|| "cannot find home directory".to_string())?;
238 let mut folder = home_dir;
239 folder.push(".wasmer");
240 std::fs::create_dir_all(folder.clone())
241 .map_err(|e| format!("cannot create config directory: {e}"))?;
242 folder
243 },
244 )
245 }
246
247 #[allow(unused)]
248 pub fn from_env() -> Result<Self, anyhow::Error> {
250 let dir = Self::get_wasmer_dir()
251 .map_err(|err| anyhow::anyhow!("Could not determine wasmer dir: {err}"))?;
252 let file_path = Self::get_file_location(&dir);
253 Self::from_file(&file_path).map_err(|err| {
254 anyhow::anyhow!(
255 "Could not load config file at '{}': {}",
256 file_path.display(),
257 err
258 )
259 })
260 }
261
262 pub fn get_file_location(wasmer_dir: &Path) -> PathBuf {
263 wasmer_dir.join(GLOBAL_CONFIG_FILE_NAME)
264 }
265}
266
267#[derive(Deserialize, Serialize, Debug, PartialEq, Eq, Clone)]
268pub struct RegistryLogin {
269 pub registry: String,
271 pub token: String,
273}
274
275#[cfg(test)]
276mod tests {
277 use super::*;
278
279 #[tokio::test]
280 async fn test_registries_switch_token() {
281 let mut registries = MultiRegistry::default();
282
283 registries
284 .set_current_registry("https://registry.wasmer.wtf")
285 .await;
286 assert_eq!(
287 registries.get_current_registry(),
288 "https://registry.wasmer.wtf/graphql".to_string()
289 );
290 registries.set_login_token_for_registry(
291 "https://registry.wasmer.io",
292 "token1",
293 UpdateRegistry::LeaveAsIs,
294 );
295 assert_eq!(
296 registries.get_current_registry(),
297 "https://registry.wasmer.wtf/graphql".to_string()
298 );
299 assert_eq!(
300 registries.get_login_token_for_registry(®istries.get_current_registry()),
301 None
302 );
303 registries
304 .set_current_registry("https://registry.wasmer.io")
305 .await;
306 assert_eq!(
307 registries.get_login_token_for_registry(®istries.get_current_registry()),
308 Some("token1".to_string())
309 );
310 registries.remove_registry("https://registry.wasmer.io");
311 assert_eq!(
312 registries.get_login_token_for_registry(®istries.get_current_registry()),
313 None
314 );
315 }
316
317 #[test]
318 fn format_registry_urls() {
319 let inputs = [
320 ("wasmer.io", "https://registry.wasmer.io/graphql"),
322 ("wasmer.wtf", "https://registry.wasmer.wtf/graphql"),
323 (
325 "https://registry.wasmer.wtf/graphql",
326 "https://registry.wasmer.wtf/graphql",
327 ),
328 (
329 "https://registry.wasmer.wtf/something/else",
330 "https://registry.wasmer.wtf/something/else",
331 ),
332 ("https://wasmer.wtf/", "https://wasmer.wtf/graphql"),
335 ("https://wasmer.wtf", "https://wasmer.wtf/graphql"),
336 (
338 "http://localhost:8000/graphql",
339 "http://localhost:8000/graphql",
340 ),
341 ("localhost:8000", "http://localhost:8000/graphql"),
342 ];
343
344 for (input, expected) in inputs {
345 let url = format_graphql(input);
346 assert_eq!(url, expected);
347 }
348 }
349}