1use crate::constants::{DEFAULT_RUNTIME, WAPM_RUNTIME_ENV_KEY};
2use crate::data::manifest::PACKAGES_DIR_NAME;
3use crate::graphql::execute_query;
4use graphql_client::*;
5use license_exprs;
6use semver::Version;
7use std::path::{Path, PathBuf};
8use std::{env, fs, io};
9use thiserror::Error;
10
11pub static MAX_NAME_LENGTH: usize = 50;
12
13#[derive(Debug, Error)]
14pub enum NameError {
15 #[error("Please enter a name")]
16 Empty,
17 #[error("The name \"{0}\" is too long. It must be {1} characters or fewer")]
18 NameTooLong(String, usize),
19 #[error(
20 "The name \"{0}\" contains invalid characters. Please use alpha-numeric characters, '-', and '_'",
21 )]
22 InvalidCharacters(String),
23}
24
25pub fn validate_name(name: &str) -> Result<String, NameError> {
27 if name.len() > MAX_NAME_LENGTH {
28 return Err(NameError::NameTooLong(name.to_string(), MAX_NAME_LENGTH));
29 }
30
31 let re = regex::Regex::new("^[-a-zA-Z0-9_]+").unwrap();
32
33 if !re.is_match(name) {
34 return Err(NameError::InvalidCharacters(name.to_string()));
35 }
36
37 Ok(name.to_owned())
38}
39
40pub fn validate_runner(runner: &str) -> Result<String, NameError> {
42 if runner.trim().is_empty() {
43 Err(NameError::Empty)
44 } else {
45 Ok(runner.trim().to_string())
46 }
47}
48
49#[derive(Debug, Error)]
50pub enum LicenseError {
51 #[error("\"{0}\" is not a valid SPDX license")]
52 UnknownLicenseId(String),
53 #[error("License should be a valid SPDX license expression (without \"LicenseRef\")")]
54 InvalidStructure(),
55}
56
57pub fn validate_license(license: &str) -> Result<String, LicenseError> {
59 match license_exprs::validate_license_expr(license) {
60 Ok(_) => Ok(license.to_owned()),
61 Err(license_exprs::ParseError::UnknownLicenseId(word)) => {
62 Err(LicenseError::UnknownLicenseId(word.to_owned()))
63 }
64 Err(license_exprs::ParseError::InvalidStructure(_)) => {
65 Err(LicenseError::InvalidStructure())
66 }
67 }
68}
69
70#[derive(GraphQLQuery)]
71#[graphql(
72 schema_path = "graphql/schema.graphql",
73 query_path = "graphql/queries/whoami.graphql",
74 response_derives = "Debug"
75)]
76struct WhoAmIQuery;
77
78pub fn get_username() -> anyhow::Result<Option<String>> {
79 let q = WhoAmIQuery::build_query(who_am_i_query::Variables {});
80 let response: who_am_i_query::ResponseData = execute_query(&q)?;
81 Ok(response.viewer.map(|viewer| viewer.username))
82}
83
84#[cfg(feature = "telemetry")]
85pub fn telemetry_is_enabled() -> bool {
86 let mut config = if let Ok(c) = crate::config::Config::from_file() {
87 c
88 } else {
89 return true;
93 };
94 let telemetry_str = crate::config::get(&mut config, "telemetry.enabled".to_string())
95 .unwrap_or_else(|_| "true".to_string());
96
97 telemetry_str.parse::<bool>().unwrap_or(false)
99}
100
101#[inline]
102pub fn get_package_namespace_and_name(package_name: &str) -> anyhow::Result<(&str, &str)> {
103 let split: Vec<&str> = package_name.split('/').collect();
104 match &split[..] {
105 [namespace, name] => Ok((*namespace, *name)),
106 [global_package_name] => {
107 info!(
108 "Interpreting unqualified global package name \"{}\" as \"_/{}\"",
109 package_name, global_package_name
110 );
111 Ok(("_", *global_package_name))
112 }
113 _ => bail!("Package name is invalid"),
114 }
115}
116
117#[inline]
118pub fn fully_qualified_package_display_name(
119 package_name: &str,
120 package_version: &Version,
121) -> String {
122 format!("{}@{}", package_name, package_version)
123}
124
125pub fn create_package_dir(
126 project_dir: &Path,
127 namespace_dir: &str,
128 fully_qualified_package_name: &str,
129) -> Result<PathBuf, io::Error> {
130 let mut package_dir = project_dir.join(PACKAGES_DIR_NAME);
131 package_dir.push(namespace_dir);
132 package_dir.push(fully_qualified_package_name);
133 fs::create_dir_all(&package_dir)?;
134 Ok(package_dir)
135}
136
137pub fn wapm_should_print_color() -> bool {
138 std::env::var("WAPM_DISABLE_COLOR")
139 .map(|_| false)
140 .unwrap_or(true)
141}
142
143use lazy_static::lazy_static;
144use std::sync::Mutex;
145
146#[derive(Debug, Default)]
147pub struct SetOnce<T: Default> {
149 set: bool,
150 value: T,
151}
152
153impl<T: Default> SetOnce<T> {
154 pub fn new() -> Self {
155 Self {
156 set: false,
157 value: T::default(),
158 }
159 }
160 pub fn set(&mut self, value: T) -> Option<()> {
161 if self.set {
162 return None;
163 }
164
165 self.value = value;
166 self.set = true;
167 Some(())
168 }
169
170 pub fn get(&self) -> &T {
171 &self.value
172 }
173}
174
175lazy_static! {
176 pub static ref WAPM_FORCE_YES_TO_PROMPTS: Mutex<SetOnce<bool>> = Mutex::new(SetOnce::new());
178}
179
180pub fn wapm_should_accept_all_prompts() -> bool {
182 let guard = WAPM_FORCE_YES_TO_PROMPTS.lock().unwrap();
183 *guard.get()
184}
185
186pub fn set_wapm_should_accept_all_prompts(val: bool) -> Option<()> {
187 let mut guard = WAPM_FORCE_YES_TO_PROMPTS.lock().unwrap();
188 guard.set(val)
189}
190
191pub fn prompt_user_for_yes(prompt: &str) -> anyhow::Result<bool> {
194 use std::io::Write;
195
196 print!("{}\n[y/n] ", prompt);
197 std::io::stdout().flush()?;
198 if wapm_should_accept_all_prompts() {
199 Ok(true)
200 } else {
201 let mut input_str = String::new();
202 std::io::stdin().read_line(&mut input_str)?;
203 match input_str.to_lowercase().trim_end() {
204 "yes" | "y" => Ok(true),
205 _ => Ok(false),
206 }
207 }
208}
209
210#[cfg(feature = "prehash-module")]
211pub fn get_hashed_module_key(path: &Path) -> Option<String> {
215 debug!("Creating hash of wasm module at {:?}", path);
216 let bytes = match std::fs::read(path) {
217 Ok(bytes) => bytes,
218 Err(e) => {
219 error!(
220 "Could not read wasm module at {} when attempting to generate hash: {}",
221 path.to_string_lossy().to_string(),
222 e.to_string()
223 );
224 return None;
225 }
226 };
227 let hash = blake3::hash(&bytes[..]);
228 let str_hash = hex::encode(&hash.as_bytes());
229 Some(str_hash)
230}
231
232#[cfg(not(feature = "prehash-module"))]
233pub fn get_hashed_module_key(_path: &Path) -> Option<String> {
234 None
235}
236
237#[cfg(feature = "update-notifications")]
238pub fn get_latest_runtime_version(runtime: &str) -> Result<String, String> {
239 use std::process::Command;
240
241 let output = Command::new(runtime)
242 .arg("-V")
243 .output()
244 .map_err(|err| err.to_string())?;
245 let stdout_str = std::str::from_utf8(&output.stdout).map_err(|err| err.to_string())?;
246 let mut whitespace_iter = stdout_str.split_whitespace();
247 let _first = whitespace_iter.next();
248 debug_assert_eq!(_first, Some(runtime));
249
250 match whitespace_iter.next() {
251 Some(v) => Ok(v.to_string()),
252 None => Err("Can't find the version of wasmer".to_string()),
253 }
254}
255
256#[cfg(feature = "update-notifications")]
257pub fn compare_versions(old: &str, new: &str) -> Option<bool> {
262 let old_ver_pieces = old.split('.').collect::<Vec<&str>>();
263 let new_ver_pieces = new.split('.').collect::<Vec<&str>>();
264
265 if !(old_ver_pieces.len() == 3 && new_ver_pieces.len() == 3) {
266 return None;
267 }
268 let parse = |pieces: Vec<&str>| -> Option<(usize, usize, usize)> {
269 Some((
270 pieces[0].parse::<usize>().ok()?,
271 pieces[1].parse::<usize>().ok()?,
272 pieces[2].parse::<usize>().ok()?,
273 ))
274 };
275 if let (Some(old_ver), Some(new_ver)) = (parse(old_ver_pieces), parse(new_ver_pieces)) {
276 Some(old_ver >= new_ver)
277 } else {
278 None
279 }
280}
281
282fn get_runtime() -> String {
285 env::var(WAPM_RUNTIME_ENV_KEY).unwrap_or_else(|_| DEFAULT_RUNTIME.to_owned())
286}
287
288fn split_runtime_and_args(runtime: String) -> (String, Vec<String>) {
290 let runtime_split = runtime.split_whitespace();
291 if let Some((split_runtime, split_runtime_args)) = runtime_split
292 .map(|s| s.to_string())
293 .collect::<Vec<String>>()
294 .split_first()
295 {
296 return (split_runtime.to_string(), split_runtime_args.to_vec());
297 }
298 (runtime, vec![])
299}
300
301pub fn get_runtime_with_args() -> (String, Vec<String>) {
305 split_runtime_and_args(get_runtime())
306}
307
308#[cfg(not(target_os = "wasi"))]
309pub fn create_temp_dir() -> Result<tempfile::TempDir, std::io::Error> {
310 tempfile::TempDir::new()
311}
312
313#[cfg(target_os = "wasi")]
314pub fn create_temp_dir() -> Result<std::path::PathBuf, std::io::Error> {
315 let mut buf = [0u8; 4];
316 getrandom::getrandom(&mut buf)?;
317 let path = format!("/tmp/{:#10x}", u32::from_be_bytes(buf));
318 let ret: std::path::PathBuf = path.into();
319 Ok(ret)
320}
321
322#[cfg(target_os = "wasi")]
323pub fn whoami_distro() -> String {
324 whoami::os().to_lowercase()
325}
326
327#[cfg(not(target_os = "wasi"))]
328pub fn whoami_distro() -> String {
329 whoami::distro().to_lowercase()
330}
331
332#[cfg(test)]
333mod test {
334 use super::*;
335
336 #[cfg(feature = "update-notifications")]
337 #[test]
338 pub fn compare_ver_test() {
339 assert_eq!(compare_versions("0.1.0", "0.1.0"), Some(true));
340 assert_eq!(compare_versions("1.1.0", "0.1.0"), Some(true));
341 assert_eq!(compare_versions("1.0.0", "0.2.5"), Some(true));
342 assert_eq!(compare_versions("1.0.0", "2.2.5"), Some(false));
343 assert_eq!(compare_versions("1.0.0", "2.0.5"), Some(false));
344 assert_eq!(compare_versions("1.1.0", "2.0.5"), Some(false));
345 assert_eq!(compare_versions("1.1.6", "2.0.0"), Some(false));
346 assert_eq!(compare_versions("0.1.1", "0.1.0"), Some(true));
347 assert_eq!(compare_versions("0.1.1", "0.2.0"), Some(false));
348 }
349
350 #[test]
351 pub fn test_split_runtime_and_args() {
352 assert_eq!(
353 split_runtime_and_args("wasmer".to_owned()),
354 ("wasmer".to_owned(), vec![])
355 );
356 assert_eq!(
357 split_runtime_and_args("wasmer --backend=llvm".to_owned()),
358 ("wasmer".to_owned(), vec!["--backend=llvm".to_owned()])
359 );
360 assert_eq!(
361 split_runtime_and_args("wasmer run".to_owned()),
362 ("wasmer".to_owned(), vec!["run".to_owned()])
363 );
364
365 assert_eq!(
367 split_runtime_and_args(" wasmer ".to_owned()),
368 ("wasmer".to_owned(), vec![])
369 );
370 assert_eq!(
371 split_runtime_and_args("wasmer run ".to_owned()),
372 ("wasmer".to_owned(), vec!["run".to_owned()])
373 );
374 assert_eq!(
375 split_runtime_and_args(" wasmer run ".to_owned()),
376 ("wasmer".to_owned(), vec!["run".to_owned()])
377 );
378 }
379}