wasmer_registry/package/
mod.rs1#[cfg(feature = "build-package")]
2pub mod builder;
3
4use crate::WasmerConfig;
5use regex::Regex;
6use std::path::{Path, PathBuf};
7use std::{fmt, str::FromStr};
8use url::Url;
9
10const REGEX_PACKAGE_WITH_VERSION: &str =
11 r"^([a-zA-Z0-9\-_]+)/([a-zA-Z0-9\-_]+)(@([a-zA-Z0-9\.\-_]+*))?$";
12
13lazy_static::lazy_static! {
14 static ref PACKAGE_WITH_VERSION: Regex = regex::Regex::new(REGEX_PACKAGE_WITH_VERSION).unwrap();
15}
16
17#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord)]
18pub struct Package {
19 pub namespace: String,
20 pub name: String,
21 pub version: Option<String>,
22}
23
24impl fmt::Display for Package {
25 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
26 write!(f, "{}", self.file())
27 }
28}
29
30impl Package {
31 pub fn already_installed(&self, wasmer_dir: &Path) -> Option<PathBuf> {
33 let checkouts_dir = crate::get_checkouts_dir(wasmer_dir);
34 let config = WasmerConfig::from_file(wasmer_dir).ok()?;
35 let current_registry = config.registry.get_current_registry();
36 let hash = self.get_hash(¤t_registry);
37
38 let found = std::fs::read_dir(&checkouts_dir)
39 .ok()?
40 .filter_map(|e| Some(e.ok()?.file_name().to_str()?.to_string()))
41 .find(|s| match self.version.as_ref() {
42 None => s.contains(&hash),
43 Some(v) => s.contains(&hash) && s.ends_with(v),
44 })?;
45 Some(checkouts_dir.join(found))
46 }
47
48 pub fn is_url_already_installed(url: &Url, wasmer_dir: &Path) -> Option<PathBuf> {
51 let checkouts_dir = crate::get_checkouts_dir(wasmer_dir);
52
53 let url_string = url.to_string();
54 let (url, version) = match url_string.split('@').collect::<Vec<_>>()[..] {
55 [url, version] => (url.to_string(), Some(version)),
56 _ => (url_string, None),
57 };
58 let hash = Self::hash_url(&url);
59 let found = std::fs::read_dir(&checkouts_dir)
60 .ok()?
61 .filter_map(|e| Some(e.ok()?.file_name().to_str()?.to_string()))
62 .find(|s| match version.as_ref() {
63 None => s.contains(&hash),
64 Some(v) => s.contains(&hash) && s.ends_with(v),
65 })?;
66 Some(checkouts_dir.join(found))
67 }
68
69 pub fn hash_url(url: &str) -> String {
72 hex::encode(url).chars().take(128).collect()
73 }
74
75 pub fn unhash_url(hashed: &str) -> String {
77 String::from_utf8_lossy(&hex::decode(hashed).unwrap_or_default()).to_string()
78 }
79
80 pub fn get_hash(&self, registry: &str) -> String {
83 let url = self.get_url_without_version(registry);
84 Self::hash_url(&url.unwrap_or_default())
85 }
86
87 fn get_url_without_version(&self, registry: &str) -> Result<String, anyhow::Error> {
88 let url = self.url(registry);
89 Ok(format!(
90 "{}/{}/{}",
91 url?.origin().ascii_serialization(),
92 self.namespace,
93 self.name
94 ))
95 }
96
97 pub fn file(&self) -> String {
99 let version = self
100 .version
101 .as_ref()
102 .map(|f| format!("@{f}"))
103 .unwrap_or_default();
104 format!("{}/{}{version}", self.namespace, self.name)
105 }
106
107 pub fn package(&self) -> String {
109 format!("{}/{}", self.namespace, self.name)
110 }
111
112 pub fn url(&self, registry: &str) -> Result<Url, anyhow::Error> {
114 let registry_tld = tldextract::TldExtractor::new(tldextract::TldOption::default())
115 .extract(registry)
116 .map_err(|e| anyhow::anyhow!("Invalid registry: {}: {e}", registry))?;
117
118 let registry_tld = format!(
119 "{}.{}",
120 registry_tld.domain.as_deref().unwrap_or(""),
121 registry_tld.suffix.as_deref().unwrap_or(""),
122 );
123
124 let version = self
125 .version
126 .as_ref()
127 .map(|f| format!("@{f}"))
128 .unwrap_or_default();
129 let url = format!(
130 "https://{registry_tld}/{}/{}{version}",
131 self.namespace, self.name
132 );
133 url::Url::parse(&url).map_err(|e| anyhow::anyhow!("error parsing {url}: {e}"))
134 }
135
136 pub fn get_path(&self, wasmer_dir: &Path) -> Result<PathBuf, anyhow::Error> {
139 let checkouts_dir = crate::get_checkouts_dir(wasmer_dir);
140 let config = WasmerConfig::from_file(wasmer_dir)
141 .map_err(|e| anyhow::anyhow!("could not load config {e}"))?;
142 let hash = self.get_hash(&config.registry.get_current_registry());
143
144 match self.version.as_ref() {
145 Some(v) => Ok(checkouts_dir.join(format!("{}@{}", hash, v))),
146 None => Ok(checkouts_dir.join(&hash)),
147 }
148 }
149}
150
151impl FromStr for Package {
152 type Err = anyhow::Error;
153
154 fn from_str(s: &str) -> Result<Self, Self::Err> {
155 let captures = PACKAGE_WITH_VERSION
156 .captures(s.trim())
157 .map(|c| {
158 c.iter()
159 .flatten()
160 .map(|m| m.as_str().to_owned())
161 .collect::<Vec<_>>()
162 })
163 .unwrap_or_default();
164
165 match captures.len() {
166 3 => {
168 let namespace = captures[1].to_string();
169 let name = captures[2].to_string();
170 Ok(Package {
171 namespace,
172 name,
173 version: None,
174 })
175 }
176 5 => {
178 let namespace = captures[1].to_string();
179 let name = captures[2].to_string();
180 let version = captures[4].to_string();
181 Ok(Package {
182 namespace,
183 name,
184 version: Some(version),
185 })
186 }
187 other => Err(anyhow::anyhow!("invalid package {other}")),
188 }
189 }
190}