1use std::{io::Write, path::PathBuf, sync::Arc, time::Duration};
2
3use anyhow::{Context, Error};
4use bytes::Bytes;
5use http::{HeaderMap, Method};
6use semver::{Version, VersionReq};
7use url::Url;
8use wasmer_config::package::{NamedPackageId, PackageHash, PackageId, PackageIdent, PackageSource};
9use web_time::SystemTime;
10use webc::metadata::Manifest;
11
12use crate::{
13 http::{HttpClient, HttpRequest, USER_AGENT},
14 runtime::resolver::{
15 DistributionInfo, PackageInfo, PackageSummary, QueryError, Source, WebcHash,
16 },
17};
18
19#[derive(Debug, Clone)]
22pub struct BackendSource {
23 registry_endpoint: Url,
24 client: Arc<dyn HttpClient + Send + Sync>,
25 cache: Option<QueryCacheConfig>,
26 token: Option<String>,
27 preferred_webc_version: webc::Version,
28}
29
30impl BackendSource {
31 pub const WASMER_DEV_ENDPOINT: &'static str = "https://registry.wasmer.wtf/graphql";
32 pub const WASMER_PROD_ENDPOINT: &'static str = "https://registry.wasmer.io/graphql";
33
34 pub fn new(registry_endpoint: Url, client: Arc<dyn HttpClient + Send + Sync>) -> Self {
35 BackendSource {
36 registry_endpoint,
37 client,
38 cache: None,
39 token: None,
40 preferred_webc_version: webc::Version::V3,
41 }
42 }
43
44 pub fn with_local_cache(self, cache_dir: impl Into<PathBuf>, timeout: Duration) -> Self {
46 self.with_query_cache(Arc::new(FileSystemQueryCache::new(cache_dir)), timeout)
47 }
48
49 pub fn with_query_cache(self, cache: Arc<dyn QueryCache>, timeout: Duration) -> Self {
51 BackendSource {
52 cache: Some(QueryCacheConfig { cache, timeout }),
53 ..self
54 }
55 }
56
57 pub fn with_auth_token(self, token: impl Into<String>) -> Self {
58 BackendSource {
59 token: Some(token.into()),
60 ..self
61 }
62 }
63
64 pub fn with_preferred_webc_version(self, version: webc::Version) -> Self {
65 BackendSource {
66 preferred_webc_version: version,
67 ..self
68 }
69 }
70
71 pub fn registry_endpoint(&self) -> &Url {
72 &self.registry_endpoint
73 }
74
75 #[tracing::instrument(level = "debug", skip_all)]
76 async fn query_graphql_named(&self, package_name: &str) -> Result<WebQuery, Error> {
77 #[derive(serde::Serialize)]
78 struct Body {
79 query: String,
80 }
81
82 let body = Body {
83 query: WASMER_WEBC_QUERY_ALL.replace("$NAME", package_name),
84 };
85
86 let request = HttpRequest {
87 url: self.registry_endpoint.clone(),
88 method: Method::POST,
89 body: Some(serde_json::to_string(&body)?.into_bytes()),
90 headers: self.headers(),
91 options: Default::default(),
92 };
93
94 tracing::debug!(%request.url, %request.method, "Querying the GraphQL API");
95 tracing::trace!(?request.headers, request.body=body.query.as_str());
96
97 let response = self.client.request(request).await?;
98
99 if !response.is_ok() {
100 let url = &self.registry_endpoint;
101 let status = response.status;
102
103 let body = if let Some(body) = &response.body {
104 String::from_utf8_lossy(body).into_owned()
105 } else {
106 "<no body>".to_string()
107 };
108
109 tracing::warn!(
110 %url,
111 %status,
112 package=%package_name,
113 %body,
114 "failed to query package info from registry"
115 );
116
117 anyhow::bail!("\"{url}\" replied with {status}");
118 }
119
120 let body = response.body.unwrap_or_default();
121 tracing::trace!(
122 %response.status,
123 %response.redirected,
124 ?response.headers,
125 "Received a response from GraphQL",
126 );
127
128 let response: WebQuery =
129 serde_json::from_slice(&body).context("Unable to deserialize the response")?;
130
131 Ok(response)
132 }
133
134 #[tracing::instrument(level = "debug", skip_all)]
135 async fn query_graphql_by_hash(
136 &self,
137 hash: &PackageHash,
138 ) -> Result<Option<PackageWebc>, Error> {
139 #[derive(serde::Serialize)]
140 struct Body {
141 query: String,
142 }
143
144 let body = Body {
145 query: WASMER_WEBC_QUERY_BY_HASH.replace("$HASH", &hash.to_string()),
146 };
147
148 let request = HttpRequest {
149 url: self.registry_endpoint.clone(),
150 method: Method::POST,
151 body: Some(serde_json::to_string(&body)?.into_bytes()),
152 headers: self.headers(),
153 options: Default::default(),
154 };
155
156 tracing::debug!(%request.url, %request.method, "Querying the GraphQL API");
157 tracing::trace!(?request.headers, request.body=body.query.as_str());
158
159 let response = self.client.request(request).await?;
160
161 if !response.is_ok() {
162 let url = &self.registry_endpoint;
163 let status = response.status;
164
165 let body = if let Some(body) = &response.body {
166 String::from_utf8_lossy(body).into_owned()
167 } else {
168 "<no body>".to_string()
169 };
170
171 tracing::warn!(
172 %url,
173 %status,
174 %hash,
175 %body,
176 "failed to query package info from registry"
177 );
178
179 anyhow::bail!("\"{url}\" replied with {status}");
180 }
181
182 let body = response.body.unwrap_or_default();
183 tracing::trace!(
184 %response.status,
185 %response.redirected,
186 ?response.headers,
187 "Received a response from GraphQL",
188 );
189
190 let response: Reply<GetPackageRelease> =
191 serde_json::from_slice(&body).context("Unable to deserialize the response")?;
192
193 Ok(response.data.get_package_release)
194 }
195
196 fn headers(&self) -> HeaderMap {
197 let mut headers = HeaderMap::new();
198 headers.insert("Content-Type", "application/json".parse().unwrap());
199 headers.insert("User-Agent", USER_AGENT.parse().unwrap());
200
201 if let Some(token) = self.token.as_deref() {
202 let raw_header = format!("Bearer {token}");
203
204 match http::HeaderValue::from_str(&raw_header) {
205 Ok(header) => {
206 headers.insert(http::header::AUTHORIZATION, header);
207 }
208 Err(e) => {
209 tracing::warn!(
210 error = &e as &dyn std::error::Error,
211 "Unable to parse the token into a header",
212 );
213 }
214 }
215 }
216
217 headers
218 }
219
220 async fn query_by_hash(
221 &self,
222 hash: &PackageHash,
223 ) -> Result<Option<PackageSummary>, anyhow::Error> {
224 let Some(data) = self.query_graphql_by_hash(hash).await? else {
227 return Ok(None);
228 };
229
230 let summary = data.try_into_summary(hash.clone())?;
231
232 Ok(Some(summary))
233 }
234}
235
236#[async_trait::async_trait]
237impl Source for BackendSource {
238 #[tracing::instrument(level = "debug", skip_all, fields(%package))]
239 async fn query(&self, package: &PackageSource) -> Result<Vec<PackageSummary>, QueryError> {
240 let (package_name, version_constraint) = match package {
241 PackageSource::Ident(PackageIdent::Named(n)) => (
242 n.full_name(),
243 n.version_opt().cloned().unwrap_or(semver::VersionReq::STAR),
244 ),
245 PackageSource::Ident(PackageIdent::Hash(hash)) => {
246 match self.query_by_hash(hash).await {
248 Ok(Some(summary)) => return Ok(vec![summary]),
249 Ok(None) => {
250 return Err(QueryError::NoMatches {
251 query: package.clone(),
252 archived_versions: Vec::new(),
253 });
254 }
255 Err(error) => {
256 return Err(QueryError::new_other(error, package));
257 }
258 }
259 }
260 _ => {
261 return Err(QueryError::Unsupported {
262 query: package.clone(),
263 });
264 }
265 };
266
267 if let Some(cache) = &self.cache {
268 match lookup_cached_query(cache, &package_name).await {
269 Ok(Some(cached)) => {
270 if let Ok(cached) = matching_package_summaries(
271 package,
272 cached,
273 &version_constraint,
274 self.preferred_webc_version,
275 ) {
276 tracing::debug!("Cache hit!");
277 return Ok(cached);
278 }
279 }
280 Ok(None) => {}
281 Err(e) => {
282 tracing::warn!(
283 package_name,
284 error = &*e,
285 "An unexpected error occurred while checking the local query cache",
286 );
287 }
288 }
289 }
290
291 let response = self
292 .query_graphql_named(&package_name)
293 .await
294 .map_err(|error| QueryError::new_other(error, package))?;
295
296 if let Some(cache) = &self.cache
297 && let Err(e) = update_cached_query(cache, &package_name, &response).await
298 {
299 tracing::warn!(
300 package_name,
301 error = &*e,
302 "An error occurred while caching the GraphQL response",
303 );
304 }
305
306 matching_package_summaries(
307 package,
308 response,
309 &version_constraint,
310 self.preferred_webc_version,
311 )
312 }
313}
314
315#[allow(clippy::result_large_err)]
316fn matching_package_summaries(
317 query: &PackageSource,
318 response: WebQuery,
319 version_constraint: &VersionReq,
320 preferred_webc_version: webc::Version,
321) -> Result<Vec<PackageSummary>, QueryError> {
322 let mut summaries = Vec::new();
323
324 let WebQueryGetPackage {
325 namespace,
326 package_name,
327 versions,
328 ..
329 } = response
330 .data
331 .get_package
332 .ok_or_else(|| QueryError::NotFound {
333 query: query.clone(),
334 })?;
335 let mut archived_versions = Vec::new();
336
337 for pkg_version in versions {
338 let version = match Version::parse(&pkg_version.version) {
339 Ok(v) => v,
340 Err(e) => {
341 tracing::debug!(
342 pkg.version = pkg_version.version.as_str(),
343 error = &e as &dyn std::error::Error,
344 "Skipping a version because it doesn't have a valid version number",
345 );
346 continue;
347 }
348 };
349
350 if pkg_version.is_archived {
351 tracing::debug!(
352 pkg.version=%version,
353 "Skipping an archived version",
354 );
355 archived_versions.push(version);
356 continue;
357 }
358
359 if version_constraint.matches(&version) {
360 match decode_summary(
361 &namespace,
362 &package_name,
363 pkg_version,
364 preferred_webc_version,
365 ) {
366 Ok(summary) => summaries.push(summary),
367 Err(e) => {
368 tracing::debug!(
369 version=%version,
370 error=&*e,
371 "Skipping version because its metadata couldn't be parsed"
372 );
373 }
374 }
375 }
376 }
377
378 if summaries.is_empty() {
379 Err(QueryError::NoMatches {
380 query: query.clone(),
381 archived_versions,
382 })
383 } else {
384 Ok(summaries)
385 }
386}
387
388fn decode_summary(
389 namespace: &str,
390 package_name: &str,
391 pkg_version: WebQueryGetPackageVersion,
392 preferred_webc_version: webc::Version,
393) -> Result<PackageSummary, Error> {
394 let WebQueryGetPackageVersion {
395 v2:
396 WebQueryGetPackageVersionDistribution {
397 pirita_sha256_hash: v2_pirita_sha256_hash,
398 pirita_download_url: v2_pirita_download_url,
399 webc_manifest: v2_manifest,
400 },
401 v3:
402 WebQueryGetPackageVersionDistribution {
403 pirita_sha256_hash: v3_pirita_sha256_hash,
404 pirita_download_url: v3_pirita_download_url,
405 webc_manifest: v3_manifest,
406 },
407 ..
408 } = pkg_version;
409
410 let (version, pirita_sha256_hash, pirita_download_url, manifest) =
411 if preferred_webc_version == webc::Version::V3 {
412 (
413 webc::Version::V3,
414 v3_pirita_sha256_hash,
415 v3_pirita_download_url,
416 v3_manifest,
417 )
418 } else {
419 (
420 webc::Version::V2,
421 v2_pirita_sha256_hash,
422 v2_pirita_download_url,
423 v2_manifest,
424 )
425 };
426
427 let id = PackageId::Named(NamedPackageId {
428 full_name: format!("{namespace}/{package_name}"),
429 version: pkg_version
430 .version
431 .parse()
432 .context("could not parse package version")?,
433 });
434
435 let manifest = manifest.context("missing Manifest")?;
436 let hash = pirita_sha256_hash.context("missing sha256")?;
437 let webc = pirita_download_url.context("missing download URL")?;
438
439 let manifest: Manifest = serde_json::from_slice(manifest.as_bytes())
440 .context("Unable to deserialize the manifest")?;
441
442 let webc_sha256 = WebcHash::parse_hex(&hash).context("invalid webc sha256 hash in manifest")?;
443
444 Ok(PackageSummary {
445 pkg: PackageInfo::from_manifest(id, &manifest, version)?,
446 dist: DistributionInfo { webc, webc_sha256 },
447 })
448}
449
450#[async_trait::async_trait]
452pub trait QueryCache: Send + Sync + std::fmt::Debug {
453 async fn load(&self, package_name: &str) -> Result<Option<Bytes>, Error>;
455
456 async fn save(&self, package_name: &str, bytes: Bytes) -> Result<(), Error>;
458
459 async fn remove(&self, package_name: &str) -> Result<(), Error>;
461}
462
463#[derive(Debug, Clone)]
464struct QueryCacheConfig {
465 cache: Arc<dyn QueryCache>,
466 timeout: Duration,
467}
468
469#[derive(Debug, Clone)]
471struct FileSystemQueryCache {
472 cache_dir: PathBuf,
473}
474
475impl FileSystemQueryCache {
476 fn new(cache_dir: impl Into<PathBuf>) -> Self {
477 Self {
478 cache_dir: cache_dir.into(),
479 }
480 }
481
482 fn path(&self, package_name: &str) -> PathBuf {
483 self.cache_dir.join(package_name.replace(['/', '\\'], "#"))
484 }
485}
486
487#[async_trait::async_trait]
488impl QueryCache for FileSystemQueryCache {
489 async fn load(&self, package_name: &str) -> Result<Option<Bytes>, Error> {
490 let filename = self.path(package_name);
491
492 tracing::trace!(filename=%filename.display(), "Reading cached entry from disk");
493 let result = crate::spawn_blocking(move || match std::fs::read(&filename) {
494 Ok(json) => Ok(Some(Bytes::from(json))),
495 Err(e) if e.kind() == std::io::ErrorKind::NotFound => Ok(None),
496 Err(e) => {
497 Err(Error::new(e).context(format!("Unable to read \"{}\"", filename.display())))
498 }
499 })
500 .await
501 .context("Unable to run the cache read task")??;
502 if result.is_none() {
503 tracing::debug!("Cache miss");
504 }
505 Ok(result)
506 }
507
508 async fn save(&self, package_name: &str, bytes: Bytes) -> Result<(), Error> {
509 let cache_dir = self.cache_dir.clone();
510 let filename = self.path(package_name);
511 tracing::debug!(
512 filename=%filename.display(),
513 package_name,
514 "Saving the query to disk",
515 );
516
517 crate::spawn_blocking(move || -> Result<(), Error> {
518 let _ = std::fs::create_dir_all(&cache_dir);
519
520 let mut temp = tempfile::NamedTempFile::new_in(&cache_dir)
521 .context("Unable to create a temporary file")?;
522 temp.write_all(&bytes)
523 .context("Unable to write the cached query")?;
524 temp.as_file()
525 .sync_all()
526 .context("Flushing the temp file failed")?;
527
528 if let Some(parent) = filename.parent() {
529 let _ = std::fs::create_dir_all(parent);
530 }
531 temp.persist(&filename).with_context(|| {
532 format!(
533 "Unable to persist the temp file to \"{}\"",
534 filename.display()
535 )
536 })?;
537
538 Ok(())
539 })
540 .await
541 .context("Unable to run the cache write task")??;
542
543 Ok(())
544 }
545
546 async fn remove(&self, package_name: &str) -> Result<(), Error> {
547 let filename = self.path(package_name);
548 crate::spawn_blocking(move || match std::fs::remove_file(filename) {
549 Ok(()) => Ok(()),
550 Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(()),
551 Err(error) => Err(error.into()),
552 })
553 .await
554 .context("Unable to run the cache removal task")?
555 }
556}
557
558async fn lookup_cached_query(
559 cache: &QueryCacheConfig,
560 package_name: &str,
561) -> Result<Option<WebQuery>, Error> {
562 let Some(json) = cache.cache.load(package_name).await? else {
563 return Ok(None);
564 };
565
566 let entry: CacheEntry = match serde_json::from_slice(&json) {
567 Ok(entry) => entry,
568 Err(error) => {
569 let _ = cache.cache.remove(package_name).await;
570 return Err(Error::new(error).context("Unable to parse the cached query"));
571 }
572 };
573
574 if !entry.is_still_valid(cache.timeout) {
575 tracing::debug!(timestamp = entry.unix_timestamp, "Cached entry is stale");
576 let _ = cache.cache.remove(package_name).await;
577 return Ok(None);
578 }
579
580 if entry.package_name != package_name {
581 let _ = cache.cache.remove(package_name).await;
582 anyhow::bail!(
583 "The cached response corresponds to the \"{}\" package, but expected \"{}\"",
584 entry.package_name,
585 package_name,
586 );
587 }
588
589 Ok(Some(entry.response))
590}
591
592async fn update_cached_query(
593 cache: &QueryCacheConfig,
594 package_name: &str,
595 response: &WebQuery,
596) -> Result<(), Error> {
597 let entry = CacheEntry {
598 unix_timestamp: SystemTime::UNIX_EPOCH
599 .elapsed()
600 .unwrap_or_default()
601 .as_secs(),
602 package_name: package_name.to_string(),
603 response: response.clone(),
604 };
605 let bytes = serde_json::to_vec_pretty(&entry).context("Unable to serialize cached query")?;
606 cache.cache.save(package_name, Bytes::from(bytes)).await
607}
608
609#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
610struct CacheEntry {
611 unix_timestamp: u64,
612 package_name: String,
613 response: WebQuery,
614}
615
616impl CacheEntry {
617 fn is_still_valid(&self, timeout: Duration) -> bool {
618 let timestamp = SystemTime::UNIX_EPOCH + Duration::from_secs(self.unix_timestamp);
619
620 match timestamp.elapsed() {
621 Ok(duration) if duration <= timeout => true,
622 Ok(_) => {
623 false
625 }
626 Err(_) => {
627 false
631 }
632 }
633 }
634}
635
636#[allow(dead_code)]
637pub const WASMER_WEBC_QUERY_ALL: &str = r#"{
638 getPackage(name: "$NAME") {
639 packageName
640 namespace
641 versions {
642 version
643 isArchived
644 v2: distribution(version: V2) {
645 piritaDownloadUrl
646 piritaSha256Hash
647 webcManifest
648 }
649 v3: distribution(version: V3) {
650 piritaDownloadUrl
651 piritaSha256Hash
652 webcManifest
653 }
654 }
655 }
656 info {
657 defaultFrontend
658 }
659}"#;
660
661pub const WASMER_WEBC_QUERY_BY_HASH: &str = r#"{
662 getPackageRelease(hash: "$HASH") {
663 piritaManifest
664 isArchived
665 webcUrl
666 }
667}"#;
668
669#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
670pub struct Reply<T> {
671 pub data: T,
672}
673
674#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
675struct GetPackageRelease {
676 #[serde(rename = "getPackageRelease")]
677 get_package_release: Option<PackageWebc>,
678}
679
680#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
681struct PackageWebc {
682 #[serde(rename = "piritaManifest")]
683 pub pirita_manifest: String,
684 #[serde(rename = "isArchived")]
685 pub is_archived: bool,
686 #[serde(rename = "webcUrl")]
687 pub webc_url: url::Url,
688}
689
690impl PackageWebc {
691 fn try_into_summary(self, hash: PackageHash) -> Result<PackageSummary, anyhow::Error> {
692 let manifest: Manifest = serde_json::from_str(&self.pirita_manifest)
693 .context("Unable to deserialize the manifest")?;
694
695 let id = PackageId::Hash(hash.clone());
696
697 let info = PackageInfo::from_manifest(id, &manifest, webc::Version::V3)
698 .context("could not convert the manifest ")?;
699
700 Ok(PackageSummary {
701 pkg: info,
702 dist: DistributionInfo {
703 webc: self.webc_url,
704 webc_sha256: WebcHash(hash.as_sha256().context("invalid hash")?.0),
706 },
707 })
708 }
709}
710
711#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
712pub struct WebQuery {
713 #[serde(rename = "data")]
714 pub data: WebQueryData,
715}
716
717#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
718pub struct WebQueryData {
719 #[serde(rename = "getPackage")]
720 pub get_package: Option<WebQueryGetPackage>,
721 pub info: WebQueryInfo,
722}
723
724#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
725pub struct WebQueryInfo {
726 #[serde(rename = "defaultFrontend")]
727 pub default_frontend: Url,
728}
729
730#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
731pub struct WebQueryGetPackage {
732 #[serde(rename = "packageName")]
733 pub package_name: String,
734 pub namespace: String,
735 pub versions: Vec<WebQueryGetPackageVersion>,
736}
737
738#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
739pub struct WebQueryGetPackageVersion {
740 pub version: String,
741 #[serde(rename = "isArchived", default)]
743 pub is_archived: bool,
744 pub v2: WebQueryGetPackageVersionDistribution,
745 pub v3: WebQueryGetPackageVersionDistribution,
746}
747
748#[derive(Debug, serde::Serialize, serde::Deserialize, Clone, Default)]
749pub enum WebCVersion {
750 #[default]
751 V2,
752 V3,
753}
754
755impl From<WebCVersion> for webc::Version {
756 fn from(val: WebCVersion) -> Self {
757 match val {
758 WebCVersion::V2 => webc::Version::V2,
759 WebCVersion::V3 => webc::Version::V3,
760 }
761 }
762}
763
764#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
765pub struct WebQueryGetPackageVersionDistribution {
766 #[serde(rename = "piritaDownloadUrl")]
767 pub pirita_download_url: Option<Url>,
768 #[serde(rename = "piritaSha256Hash")]
769 pub pirita_sha256_hash: Option<String>,
770 #[serde(rename = "webcManifest")]
771 pub webc_manifest: Option<String>,
772}
773
774#[cfg(test)]
775mod tests {
776 use std::{str::FromStr, sync::Mutex};
777
778 use http::{HeaderMap, StatusCode};
779
780 use crate::{
781 http::HttpResponse,
782 runtime::resolver::inputs::{DistributionInfo, PackageInfo},
783 };
784
785 use super::*;
786
787 const WASMER_PACK_CLI_REQUEST: &[u8] = br#"
793 {
794 "query":"{\n getPackage(name: \"wasmer/wasmer-pack-cli\") {\n packageName\n namespace\n versions {\n version\n isArchived\n v2: distribution(version: V2) {\n piritaDownloadUrl\n piritaSha256Hash\n webcManifest\n }\n v3: distribution(version: V3) {\n piritaDownloadUrl\n piritaSha256Hash\n webcManifest\n }\n }\n }\n info {\n defaultFrontend\n }\n}"
795 }
796 "#;
797 const WASMER_PACK_CLI_RESPONSE: &[u8] = br#"
798 {
799 "data": {
800 "getPackage": {
801 "packageName": "wasmer-pack-cli",
802 "namespace": "wasmer",
803 "versions": [
804 {
805 "version": "0.7.1",
806 "isArchived": false,
807 "v2": {
808 "webcManifest": "{\"atoms\": {\"wasmer-pack\": {\"kind\": \"https://webc.org/kind/wasm\", \"signature\": \"sha256:gGeLZqPitpg893Jj/nvGa+1235RezSWA9FjssopzOZY=\"}}, \"package\": {\"wapm\": {\"name\": \"wasmer/wasmer-pack-cli\", \"readme\": {\"path\": \"README.md\", \"volume\": \"metadata\"}, \"license\": \"MIT\", \"version\": \"0.7.1\", \"homepage\": \"https://wasmer.io/\", \"repository\": \"https://github.com/wasmerio/wasmer-pack\", \"description\": \"A code generator that lets you treat WebAssembly modules like native dependencies.\"}}, \"commands\": {\"wasmer-pack\": {\"runner\": \"https://webc.org/runner/wasi/command@unstable_\", \"annotations\": {\"wasi\": {\"atom\": \"wasmer-pack\", \"package\": \"wasmer/wasmer-pack-cli\", \"main_args\": null}}}}, \"entrypoint\": \"wasmer-pack\"}",
809 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.7.1/wasmer-pack-cli-0.7.1.webc",
810 "piritaSha256Hash": "e821047f446dd20fb6b43a1648fe98b882276dfc480f020df6f00a49f69771fa"
811 },
812 "v3": {
813 "webcManifest": "{\"atoms\": {\"wasmer-pack\": {\"kind\": \"https://webc.org/kind/wasm\", \"signature\": \"sha256:gGeLZqPitpg893Jj/nvGa+1235RezSWA9FjssopzOZY=\"}}, \"package\": {\"wapm\": {\"name\": \"wasmer/wasmer-pack-cli\", \"readme\": {\"path\": \"README.md\", \"volume\": \"metadata\"}, \"license\": \"MIT\", \"version\": \"0.7.1\", \"homepage\": \"https://wasmer.io/\", \"repository\": \"https://github.com/wasmerio/wasmer-pack\", \"description\": \"A code generator that lets you treat WebAssembly modules like native dependencies.\"}}, \"commands\": {\"wasmer-pack\": {\"runner\": \"https://webc.org/runner/wasi/command@unstable_\", \"annotations\": {\"wasi\": {\"atom\": \"wasmer-pack\", \"package\": \"wasmer/wasmer-pack-cli\", \"main_args\": null}}}}, \"entrypoint\": \"wasmer-pack\"}",
814 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.7.1/wasmer-pack-cli-0.7.1.webc",
815 "piritaSha256Hash": "e821047f446dd20fb6b43a1648fe98b882276dfc480f020df6f00a49f69771fa"
816 }
817 },
818 {
819 "version": "0.7.0",
820 "isArchived": false,
821 "v2": {
822 "webcManifest": "{\"atoms\": {\"wasmer-pack\": {\"kind\": \"https://webc.org/kind/wasm\", \"signature\": \"sha256:FesCIAS6URjrIAAyy4G5u5HjJjGQBLGmnafjHPHRvqo=\"}}, \"package\": {\"wapm\": {\"name\": \"wasmer/wasmer-pack-cli\", \"readme\": {\"path\": \"/home/consulting/Documents/wasmer/wasmer-pack/crates/cli/../../README.md\", \"volume\": \"metadata\"}, \"license\": \"MIT\", \"version\": \"0.7.0\", \"homepage\": \"https://wasmer.io/\", \"repository\": \"https://github.com/wasmerio/wasmer-pack\", \"description\": \"A code generator that lets you treat WebAssembly modules like native dependencies.\"}}, \"commands\": {\"wasmer-pack\": {\"runner\": \"https://webc.org/runner/wasi/command@unstable_\", \"annotations\": {\"wasi\": {\"atom\": \"wasmer-pack\", \"package\": \"wasmer/wasmer-pack-cli\", \"main_args\": null}}}}, \"entrypoint\": \"wasmer-pack\"}",
823 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.7.0/wasmer-pack-cli-0.7.0.webc",
824 "piritaSha256Hash": "d085869201aa602673f70abbd5e14e5a6936216fa93314c5b103cda3da56e29e"
825 },
826 "v3": {
827 "webcManifest": "{\"atoms\": {\"wasmer-pack\": {\"kind\": \"https://webc.org/kind/wasm\", \"signature\": \"sha256:FesCIAS6URjrIAAyy4G5u5HjJjGQBLGmnafjHPHRvqo=\"}}, \"package\": {\"wapm\": {\"name\": \"wasmer/wasmer-pack-cli\", \"readme\": {\"path\": \"/home/consulting/Documents/wasmer/wasmer-pack/crates/cli/../../README.md\", \"volume\": \"metadata\"}, \"license\": \"MIT\", \"version\": \"0.7.0\", \"homepage\": \"https://wasmer.io/\", \"repository\": \"https://github.com/wasmerio/wasmer-pack\", \"description\": \"A code generator that lets you treat WebAssembly modules like native dependencies.\"}}, \"commands\": {\"wasmer-pack\": {\"runner\": \"https://webc.org/runner/wasi/command@unstable_\", \"annotations\": {\"wasi\": {\"atom\": \"wasmer-pack\", \"package\": \"wasmer/wasmer-pack-cli\", \"main_args\": null}}}}, \"entrypoint\": \"wasmer-pack\"}",
828 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.7.0/wasmer-pack-cli-0.7.0.webc",
829 "piritaSha256Hash": "d085869201aa602673f70abbd5e14e5a6936216fa93314c5b103cda3da56e29e"
830 }
831 },
832 {
833 "version": "0.6.0",
834 "isArchived": false,
835 "v2": {
836 "webcManifest": "{\"atoms\": {\"wasmer-pack\": {\"kind\": \"https://webc.org/kind/wasm\", \"signature\": \"sha256:CzzhNaav3gjBkCJECGbk7e+qAKurWbcIAzQvEqsr2Co=\"}}, \"package\": {\"wapm\": {\"name\": \"wasmer/wasmer-pack-cli\", \"readme\": {\"path\": \"/home/consulting/Documents/wasmer/wasmer-pack/crates/cli/../../README.md\", \"volume\": \"metadata\"}, \"license\": \"MIT\", \"version\": \"0.6.0\", \"homepage\": \"https://wasmer.io/\", \"repository\": \"https://github.com/wasmerio/wasmer-pack\", \"description\": \"A code generator that lets you treat WebAssembly modules like native dependencies.\"}}, \"commands\": {\"wasmer-pack\": {\"runner\": \"https://webc.org/runner/wasi/command@unstable_\", \"annotations\": {\"wasi\": {\"atom\": \"wasmer-pack\", \"package\": \"wasmer/wasmer-pack-cli\", \"main_args\": null}}}}, \"entrypoint\": \"wasmer-pack\"}",
837 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.6.0/wasmer-pack-cli-0.6.0.webc",
838 "piritaSha256Hash": "7e1add1640d0037ff6a726cd7e14ea36159ec2db8cb6debd0e42fa2739bea52b"
839 },
840 "v3": {
841 "webcManifest": "{\"atoms\": {\"wasmer-pack\": {\"kind\": \"https://webc.org/kind/wasm\", \"signature\": \"sha256:CzzhNaav3gjBkCJECGbk7e+qAKurWbcIAzQvEqsr2Co=\"}}, \"package\": {\"wapm\": {\"name\": \"wasmer/wasmer-pack-cli\", \"readme\": {\"path\": \"/home/consulting/Documents/wasmer/wasmer-pack/crates/cli/../../README.md\", \"volume\": \"metadata\"}, \"license\": \"MIT\", \"version\": \"0.6.0\", \"homepage\": \"https://wasmer.io/\", \"repository\": \"https://github.com/wasmerio/wasmer-pack\", \"description\": \"A code generator that lets you treat WebAssembly modules like native dependencies.\"}}, \"commands\": {\"wasmer-pack\": {\"runner\": \"https://webc.org/runner/wasi/command@unstable_\", \"annotations\": {\"wasi\": {\"atom\": \"wasmer-pack\", \"package\": \"wasmer/wasmer-pack-cli\", \"main_args\": null}}}}, \"entrypoint\": \"wasmer-pack\"}",
842 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.6.0/wasmer-pack-cli-0.6.0.webc",
843 "piritaSha256Hash": "7e1add1640d0037ff6a726cd7e14ea36159ec2db8cb6debd0e42fa2739bea52b"
844 }
845 },
846 {
847 "version": "0.5.3",
848 "isArchived": false,
849 "v2": {
850 "webcManifest": "{\"atoms\": {\"wasmer-pack\": {\"kind\": \"https://webc.org/kind/wasm\", \"signature\": \"sha256:qdiJVfpi4icJXdR7Y5US/pJ4PjqbAq9PkU+obMZIMlE=\"}}, \"package\": {\"wapm\": {\"name\": \"wasmer/wasmer-pack-cli\", \"readme\": {\"path\": \"/home/runner/work/wasmer-pack/wasmer-pack/crates/cli/../../README.md\", \"volume\": \"metadata\"}, \"license\": \"MIT\", \"version\": \"0.5.3\", \"homepage\": \"https://wasmer.io/\", \"repository\": \"https://github.com/wasmerio/wasmer-pack\", \"description\": \"A code generator that lets you treat WebAssembly modules like native dependencies.\"}}, \"commands\": {\"wasmer-pack\": {\"runner\": \"https://webc.org/runner/wasi/command@unstable_\", \"annotations\": {\"wasi\": {\"atom\": \"wasmer-pack\", \"package\": \"wasmer/wasmer-pack-cli\", \"main_args\": null}}}}, \"entrypoint\": \"wasmer-pack\"}",
851 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.5.3/wasmer-pack-cli-0.5.3.webc",
852 "piritaSha256Hash": "44fdcdde23d34175887243d7c375e4e4a7e6e2cd1ae063ebffbede4d1f68f14a"
853 },
854 "v3": {
855 "webcManifest": "{\"atoms\": {\"wasmer-pack\": {\"kind\": \"https://webc.org/kind/wasm\", \"signature\": \"sha256:qdiJVfpi4icJXdR7Y5US/pJ4PjqbAq9PkU+obMZIMlE=\"}}, \"package\": {\"wapm\": {\"name\": \"wasmer/wasmer-pack-cli\", \"readme\": {\"path\": \"/home/runner/work/wasmer-pack/wasmer-pack/crates/cli/../../README.md\", \"volume\": \"metadata\"}, \"license\": \"MIT\", \"version\": \"0.5.3\", \"homepage\": \"https://wasmer.io/\", \"repository\": \"https://github.com/wasmerio/wasmer-pack\", \"description\": \"A code generator that lets you treat WebAssembly modules like native dependencies.\"}}, \"commands\": {\"wasmer-pack\": {\"runner\": \"https://webc.org/runner/wasi/command@unstable_\", \"annotations\": {\"wasi\": {\"atom\": \"wasmer-pack\", \"package\": \"wasmer/wasmer-pack-cli\", \"main_args\": null}}}}, \"entrypoint\": \"wasmer-pack\"}",
856 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.5.3/wasmer-pack-cli-0.5.3.webc",
857 "piritaSha256Hash": "44fdcdde23d34175887243d7c375e4e4a7e6e2cd1ae063ebffbede4d1f68f14a"
858 }
859 },
860 {
861 "version": "0.5.2",
862 "isArchived": false,
863 "v2": {
864 "webcManifest": "{\"atoms\": {\"wasmer-pack\": {\"kind\": \"https://webc.org/kind/wasm\", \"signature\": \"sha256:xiwrUFAo+cU1xW/IE6MVseiyjNGHtXooRlkYKiOKzQc=\"}}, \"package\": {\"wapm\": {\"name\": \"wasmer/wasmer-pack-cli\", \"readme\": {\"path\": \"/home/consulting/Documents/wasmer/wasmer-pack/crates/cli/../../README.md\", \"volume\": \"metadata\"}, \"license\": \"MIT\", \"version\": \"0.5.2\", \"homepage\": \"https://wasmer.io/\", \"repository\": \"https://github.com/wasmerio/wasmer-pack\", \"description\": \"A code generator that lets you treat WebAssembly modules like native dependencies.\"}}, \"commands\": {\"wasmer-pack\": {\"runner\": \"https://webc.org/runner/wasi/command@unstable_\", \"annotations\": {\"wasi\": {\"atom\": \"wasmer-pack\", \"package\": \"wasmer/wasmer-pack-cli\", \"main_args\": null}}}}, \"entrypoint\": \"wasmer-pack\"}",
865 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.5.2/wasmer-pack-cli-0.5.2.webc",
866 "piritaSha256Hash": "d1dbc8168c3a2491a7158017a9c88df9e0c15bed88ebcd6d9d756e4b03adde95"
867 },
868 "v3": {
869 "webcManifest": "{\"atoms\": {\"wasmer-pack\": {\"kind\": \"https://webc.org/kind/wasm\", \"signature\": \"sha256:xiwrUFAo+cU1xW/IE6MVseiyjNGHtXooRlkYKiOKzQc=\"}}, \"package\": {\"wapm\": {\"name\": \"wasmer/wasmer-pack-cli\", \"readme\": {\"path\": \"/home/consulting/Documents/wasmer/wasmer-pack/crates/cli/../../README.md\", \"volume\": \"metadata\"}, \"license\": \"MIT\", \"version\": \"0.5.2\", \"homepage\": \"https://wasmer.io/\", \"repository\": \"https://github.com/wasmerio/wasmer-pack\", \"description\": \"A code generator that lets you treat WebAssembly modules like native dependencies.\"}}, \"commands\": {\"wasmer-pack\": {\"runner\": \"https://webc.org/runner/wasi/command@unstable_\", \"annotations\": {\"wasi\": {\"atom\": \"wasmer-pack\", \"package\": \"wasmer/wasmer-pack-cli\", \"main_args\": null}}}}, \"entrypoint\": \"wasmer-pack\"}",
870 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.5.2/wasmer-pack-cli-0.5.2.webc",
871 "piritaSha256Hash": "d1dbc8168c3a2491a7158017a9c88df9e0c15bed88ebcd6d9d756e4b03adde95"
872 }
873 },
874 {
875 "version": "0.5.1",
876 "isArchived": false,
877 "v2": {
878 "webcManifest": "{\"atoms\": {\"wasmer-pack\": {\"kind\": \"https://webc.org/kind/wasm\", \"signature\": \"sha256:TliPwutfkFvRite/3/k3OpLqvV0EBKGwyp3L5UjCuEI=\"}}, \"package\": {\"wapm\": {\"name\": \"wasmer/wasmer-pack-cli\", \"readme\": {\"path\": \"/home/runner/work/wasmer-pack/wasmer-pack/crates/cli/../../README.md\", \"volume\": \"metadata\"}, \"license\": \"MIT\", \"version\": \"0.5.1\", \"homepage\": \"https://wasmer.io/\", \"repository\": \"https://github.com/wasmerio/wasmer-pack\", \"description\": \"A code generator that lets you treat WebAssembly modules like native dependencies.\"}}, \"commands\": {\"wasmer-pack\": {\"runner\": \"https://webc.org/runner/wasi/command@unstable_\", \"annotations\": {\"wasi\": {\"atom\": \"wasmer-pack\", \"package\": \"wasmer/wasmer-pack-cli\", \"main_args\": null}}}}, \"entrypoint\": \"wasmer-pack\"}",
879 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.5.1/wasmer-pack-cli-0.5.1.webc",
880 "piritaSha256Hash": "c42924619660e2befd69b5c72729388985dcdcbf912d51a00015237fec3e1ade"
881 },
882 "v3": {
883 "webcManifest": "{\"atoms\": {\"wasmer-pack\": {\"kind\": \"https://webc.org/kind/wasm\", \"signature\": \"sha256:TliPwutfkFvRite/3/k3OpLqvV0EBKGwyp3L5UjCuEI=\"}}, \"package\": {\"wapm\": {\"name\": \"wasmer/wasmer-pack-cli\", \"readme\": {\"path\": \"/home/runner/work/wasmer-pack/wasmer-pack/crates/cli/../../README.md\", \"volume\": \"metadata\"}, \"license\": \"MIT\", \"version\": \"0.5.1\", \"homepage\": \"https://wasmer.io/\", \"repository\": \"https://github.com/wasmerio/wasmer-pack\", \"description\": \"A code generator that lets you treat WebAssembly modules like native dependencies.\"}}, \"commands\": {\"wasmer-pack\": {\"runner\": \"https://webc.org/runner/wasi/command@unstable_\", \"annotations\": {\"wasi\": {\"atom\": \"wasmer-pack\", \"package\": \"wasmer/wasmer-pack-cli\", \"main_args\": null}}}}, \"entrypoint\": \"wasmer-pack\"}",
884 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.5.1/wasmer-pack-cli-0.5.1.webc",
885 "piritaSha256Hash": "c42924619660e2befd69b5c72729388985dcdcbf912d51a00015237fec3e1ade"
886 }
887 },
888 {
889 "version": "0.5.0",
890 "isArchived": false,
891 "v2": {
892 "webcManifest": "{\"atoms\": {\"wasmer-pack\": {\"kind\": \"https://webc.org/kind/wasm\", \"signature\": \"sha256:6UD7NS4KtyNYa3TcnKOvd+kd3LxBCw+JQ8UWRpMXeC0=\"}}, \"package\": {\"wapm\": {\"name\": \"wasmer/wasmer-pack-cli\", \"readme\": {\"path\": \"README.md\", \"volume\": \"metadata\"}, \"license\": \"MIT\", \"version\": \"0.5.0\", \"homepage\": \"https://wasmer.io/\", \"repository\": \"https://github.com/wasmerio/wasmer-pack\", \"description\": \"A code generator that lets you treat WebAssembly modules like native dependencies.\"}}, \"commands\": {\"wasmer-pack\": {\"runner\": \"https://webc.org/runner/wasi/command@unstable_\", \"annotations\": {\"wasi\": {\"atom\": \"wasmer-pack\", \"package\": \"wasmer/wasmer-pack-cli\", \"main_args\": null}}}}, \"entrypoint\": \"wasmer-pack\"}",
893 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.5.0/wasmer-pack-cli-0.5.0.webc",
894 "piritaSha256Hash": "d30ca468372faa96469163d2d1546dd34be9505c680677e6ab86a528a268e5f5"
895 },
896 "v3": {
897 "webcManifest": "{\"atoms\": {\"wasmer-pack\": {\"kind\": \"https://webc.org/kind/wasm\", \"signature\": \"sha256:6UD7NS4KtyNYa3TcnKOvd+kd3LxBCw+JQ8UWRpMXeC0=\"}}, \"package\": {\"wapm\": {\"name\": \"wasmer/wasmer-pack-cli\", \"readme\": {\"path\": \"README.md\", \"volume\": \"metadata\"}, \"license\": \"MIT\", \"version\": \"0.5.0\", \"homepage\": \"https://wasmer.io/\", \"repository\": \"https://github.com/wasmerio/wasmer-pack\", \"description\": \"A code generator that lets you treat WebAssembly modules like native dependencies.\"}}, \"commands\": {\"wasmer-pack\": {\"runner\": \"https://webc.org/runner/wasi/command@unstable_\", \"annotations\": {\"wasi\": {\"atom\": \"wasmer-pack\", \"package\": \"wasmer/wasmer-pack-cli\", \"main_args\": null}}}}, \"entrypoint\": \"wasmer-pack\"}",
898 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.5.0/wasmer-pack-cli-0.5.0.webc",
899 "piritaSha256Hash": "d30ca468372faa96469163d2d1546dd34be9505c680677e6ab86a528a268e5f5"
900 }
901 },
902 {
903 "version": "0.5.0-rc.1",
904 "isArchived": false,
905 "v2": {
906 "webcManifest": "{\"atoms\": {\"wasmer-pack\": {\"kind\": \"https://webc.org/kind/wasm\", \"signature\": \"sha256:ThybHIc2elJEcDdQiq5ffT1TVaNs70+WAqoKw4Tkh3E=\"}}, \"package\": {\"wapm\": {\"name\": \"wasmer/wasmer-pack-cli\", \"readme\": {\"path\": \"README.md\", \"volume\": \"metadata\"}, \"license\": \"MIT\", \"version\": \"0.5.0-rc.1\", \"homepage\": \"https://wasmer.io/\", \"repository\": \"https://github.com/wasmerio/wasmer-pack\", \"description\": \"A code generator that lets you treat WebAssembly modules like native dependencies.\"}}, \"commands\": {\"wasmer-pack\": {\"runner\": \"https://webc.org/runner/wasi/command@unstable_\", \"annotations\": {\"wasi\": {\"atom\": \"wasmer-pack\", \"package\": \"wasmer/wasmer-pack-cli\", \"main_args\": null}}}}, \"entrypoint\": \"wasmer-pack\"}",
907 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.5.0-rc.1/wasmer-pack-cli-0.5.0-rc.1.webc",
908 "piritaSha256Hash": "0cd5d6e4c33c92c52784afed3a60c056953104d719717948d4663ff2521fe2bb"
909 },
910 "v3": {
911 "webcManifest": "{\"atoms\": {\"wasmer-pack\": {\"kind\": \"https://webc.org/kind/wasm\", \"signature\": \"sha256:ThybHIc2elJEcDdQiq5ffT1TVaNs70+WAqoKw4Tkh3E=\"}}, \"package\": {\"wapm\": {\"name\": \"wasmer/wasmer-pack-cli\", \"readme\": {\"path\": \"README.md\", \"volume\": \"metadata\"}, \"license\": \"MIT\", \"version\": \"0.5.0-rc.1\", \"homepage\": \"https://wasmer.io/\", \"repository\": \"https://github.com/wasmerio/wasmer-pack\", \"description\": \"A code generator that lets you treat WebAssembly modules like native dependencies.\"}}, \"commands\": {\"wasmer-pack\": {\"runner\": \"https://webc.org/runner/wasi/command@unstable_\", \"annotations\": {\"wasi\": {\"atom\": \"wasmer-pack\", \"package\": \"wasmer/wasmer-pack-cli\", \"main_args\": null}}}}, \"entrypoint\": \"wasmer-pack\"}",
912 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.5.0-rc.1/wasmer-pack-cli-0.5.0-rc.1.webc",
913 "piritaSha256Hash": "0cd5d6e4c33c92c52784afed3a60c056953104d719717948d4663ff2521fe2bb"
914 }
915 }
916 ]
917 },
918 "info": {
919 "defaultFrontend": "https://wasmer.io"
920 }
921 }
922 }
923 "#;
924
925 #[derive(Debug)]
926 struct DummyClient {
927 requests: Mutex<Vec<HttpRequest>>,
928 responses: Mutex<Vec<HttpResponse>>,
929 }
930
931 impl DummyClient {
932 fn new(responses: Vec<HttpResponse>) -> Self {
933 DummyClient {
934 requests: Mutex::new(Vec::new()),
935 responses: Mutex::new(responses),
936 }
937 }
938
939 fn take_requests(&self) -> Vec<HttpRequest> {
940 std::mem::take(&mut *self.requests.lock().unwrap())
941 }
942 }
943
944 impl HttpClient for DummyClient {
945 fn request(
946 &self,
947 request: HttpRequest,
948 ) -> futures::future::BoxFuture<'_, Result<HttpResponse, anyhow::Error>> {
949 self.requests.lock().unwrap().push(request);
950 let response = self.responses.lock().unwrap().remove(0);
951 Box::pin(async { Ok(response) })
952 }
953 }
954
955 #[tokio::test]
956 async fn run_known_query() {
957 let response = HttpResponse {
958 body: Some(WASMER_PACK_CLI_RESPONSE.to_vec()),
959 redirected: false,
960 status: StatusCode::OK,
961 headers: HeaderMap::new(),
962 };
963 let client = Arc::new(DummyClient::new(vec![response]));
964 let registry_endpoint = BackendSource::WASMER_PROD_ENDPOINT.parse().unwrap();
965 let request = PackageSource::from_str("wasmer/wasmer-pack-cli@^0.6").unwrap();
966 let source = BackendSource::new(registry_endpoint, client.clone());
967
968 let summaries = source.query(&request).await.unwrap();
969
970 assert_eq!(
971 summaries,
972 [PackageSummary {
973 pkg: PackageInfo {
974 id: PackageId::new_named("wasmer/wasmer-pack-cli", Version::new(0, 6, 0)),
975 dependencies: Vec::new(),
976 commands: vec![crate::runtime::resolver::Command {
977 name: "wasmer-pack".to_string(),
978 },],
979 entrypoint: Some("wasmer-pack".to_string()),
980 filesystem: vec![],
981 },
982 dist: DistributionInfo {
983 webc: "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.6.0/wasmer-pack-cli-0.6.0.webc"
984 .parse()
985 .unwrap(),
986 webc_sha256: WebcHash::from_bytes([
987 126, 26, 221, 22, 64, 208, 3, 127, 246, 167, 38, 205, 126, 20, 234, 54, 21,
988 158, 194, 219, 140, 182, 222, 189, 14, 66, 250, 39, 57, 190, 165, 43,
989 ]),
990 }
991 }]
992 );
993 let requests = client.take_requests();
994 assert_eq!(requests.len(), 1);
995 let request = &requests[0];
996 assert_eq!(request.method, http::Method::POST);
997 assert_eq!(request.url.as_str(), BackendSource::WASMER_PROD_ENDPOINT);
998 assert_eq!(request.headers.len(), 2);
999 assert_eq!(request.headers["User-Agent"], USER_AGENT);
1000 assert_eq!(request.headers["Content-Type"], "application/json");
1001 let body: serde_json::Value =
1002 serde_json::from_slice(request.body.as_deref().unwrap()).unwrap();
1003 let expected_body: serde_json::Value =
1004 serde_json::from_slice(WASMER_PACK_CLI_REQUEST).unwrap();
1005 assert_eq!(body, expected_body);
1006 }
1007
1008 #[tokio::test]
1012 async fn skip_package_versions_with_missing_fields() {
1013 let body = serde_json::json! {
1014 {
1015 "data": {
1016 "getPackage": {
1017 "packageName": "cowsay",
1018 "namespace": "_",
1019 "versions": [
1020 {
1021 "version": "0.2.0",
1022 "v2": {
1023 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/packages/_/cowsay/cowsay-0.2.0.webc",
1024 "piritaSha256Hash": "9586938a0a89219dafe4ae97a901c56d4b3e2a9941520d1309ae880c9a1868c9",
1025 "webcManifest": "{\"atoms\": {\"cowsay\": {\"kind\": \"https://webc.org/kind/wasm\", \"signature\": \"sha256:DPmhiSNXCg5261eTUi3BIvAc/aJttGj+nD+bGhQkVQo=\"}}, \"package\": {\"wapm\": {\"name\": \"cowsay\", \"readme\": {\"path\": \"README.md\", \"volume\": \"metadata\"}, \"version\": \"0.2.0\", \"repository\": \"https://github.com/wapm-packages/cowsay\", \"description\": \"cowsay is a program that generates ASCII pictures of a cow with a message\"}}, \"commands\": {\"cowsay\": {\"runner\": \"https://webc.org/runner/wasi/command@unstable_\", \"annotations\": {\"wasi\": {\"atom\": \"cowsay\", \"package\": null, \"main_args\": null}}}, \"cowthink\": {\"runner\": \"https://webc.org/runner/wasi/command@unstable_\", \"annotations\": {\"wasi\": {\"atom\": \"cowsay\", \"package\": null, \"main_args\": null}}}}}",
1026 },
1027 "v3": {
1028 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/packages/_/cowsay/cowsay-0.2.0.webc",
1029 "piritaSha256Hash": "9586938a0a89219dafe4ae97a901c56d4b3e2a9941520d1309ae880c9a1868c9",
1030 "webcManifest": "{\"atoms\": {\"cowsay\": {\"kind\": \"https://webc.org/kind/wasm\", \"signature\": \"sha256:DPmhiSNXCg5261eTUi3BIvAc/aJttGj+nD+bGhQkVQo=\"}}, \"package\": {\"wapm\": {\"name\": \"cowsay\", \"readme\": {\"path\": \"README.md\", \"volume\": \"metadata\"}, \"version\": \"0.2.0\", \"repository\": \"https://github.com/wapm-packages/cowsay\", \"description\": \"cowsay is a program that generates ASCII pictures of a cow with a message\"}}, \"commands\": {\"cowsay\": {\"runner\": \"https://webc.org/runner/wasi/command@unstable_\", \"annotations\": {\"wasi\": {\"atom\": \"cowsay\", \"package\": null, \"main_args\": null}}}, \"cowthink\": {\"runner\": \"https://webc.org/runner/wasi/command@unstable_\", \"annotations\": {\"wasi\": {\"atom\": \"cowsay\", \"package\": null, \"main_args\": null}}}}}",
1031 }
1032 },
1033 {
1034 "version": "0.1.3",
1035 "v2": {
1036 "piritaDownloadUrl": "https://example.com/",
1037 "piritaSha256Hash": "1234asdf"
1038 },
1039 "v3": {
1040 "piritaDownloadUrl": "https://example.com/",
1041 "piritaSha256Hash": "1234asdf"
1042 }
1043 },
1044 {
1045 "version": "0.1.2",
1046 "v2": {
1047 "piritaDownloadUrl": "https://example.com/",
1048 "piritaSha256Hash": "1234asdf",
1049 "webcManifest": "{}",
1050 },
1051 "v3": {
1052 "piritaDownloadUrl": "https://example.com/",
1053 "piritaSha256Hash": "1234asdf",
1054 "webcManifest": "{}",
1055 }
1056 },
1057 {
1058 "version": "0.1.3",
1059 "v2": {
1060 "webcManifest": "{}",
1061 "piritaDownloadUrl": "https://example.com/",
1062 "piritaSha256Hash": "1234asdf"
1063 },
1064 "v3": {
1065 "webcManifest": "{}",
1066 "piritaDownloadUrl": "https://example.com/",
1067 "piritaSha256Hash": "1234asdf"
1068 }
1069 }
1070 ]
1071 },
1072 "info": {
1073 "defaultFrontend": "https://wasmer.io/",
1074 },
1075 }
1076 }
1077
1078 };
1079 let response = HttpResponse {
1080 body: Some(serde_json::to_vec(&body).unwrap()),
1081 redirected: false,
1082 status: StatusCode::OK,
1083 headers: HeaderMap::new(),
1084 };
1085 let client = Arc::new(DummyClient::new(vec![response]));
1086 let registry_endpoint = BackendSource::WASMER_PROD_ENDPOINT.parse().unwrap();
1087 let request = PackageSource::from_str("_/cowsay").unwrap();
1088 let source = BackendSource::new(registry_endpoint, client.clone());
1089
1090 let summaries = source.query(&request).await.unwrap();
1091
1092 assert_eq!(summaries.len(), 1);
1093 assert_eq!(
1094 summaries[0].pkg.id.as_named().unwrap().version.to_string(),
1095 "0.2.0"
1096 );
1097 }
1098
1099 #[tokio::test]
1100 async fn skip_archived_package_versions() {
1101 let body = serde_json::json! {
1102 {
1103 "data": {
1104 "getPackage": {
1105 "packageName": "python",
1106 "namespace": "wasmer",
1107 "versions": [
1108 {
1109 "version": "3.12.2",
1110 "isArchived": true,
1111 "v2": {
1112 "webcManifest": "{\"atoms\": {\"python\": {\"kind\": \"https://webc.org/kind/wasm\", \"signature\": \"sha256:ibsq6QL4qB4GtCE8IA2yfHVwI4fLoIGXsALsAx16y5M=\"}}, \"package\": {\"wapm\": {\"name\": \"wasmer/python\", \"license\": \"ISC\", \"version\": \"3.12.2\", \"repository\": \"https://github.com/wapm-packages/python\", \"description\": \"Python is an interpreted, high-level, general-purpose programming language\"}}, \"commands\": {\"python\": {\"runner\": \"https://webc.org/runner/wasi/command@unstable_\", \"annotations\": {\"wasi\": {\"atom\": \"python\", \"package\": null, \"main_args\": null}}}}, \"entrypoint\": \"python\"}",
1113 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/packages/wasmer/python/python-3.12.0-build.5-a11e0414-c68d-473c-958f-fc96ef7adb20.webc",
1114 "piritaSha256Hash": "7771ed54376c16da86581736fad84fb761a049915902a7070e854965be0d5874"
1115 },
1116 "v3": {
1117 "webcManifest": "{\"atoms\": {\"python\": {\"kind\": \"https://webc.org/kind/wasm\", \"signature\": \"sha256:ibsq6QL4qB4GtCE8IA2yfHVwI4fLoIGXsALsAx16y5M=\"}}, \"package\": {\"wapm\": {\"name\": \"wasmer/python\", \"license\": \"ISC\", \"version\": \"3.12.2\", \"repository\": \"https://github.com/wapm-packages/python\", \"description\": \"Python is an interpreted, high-level, general-purpose programming language\"}}, \"commands\": {\"python\": {\"runner\": \"https://webc.org/runner/wasi/command@unstable_\", \"annotations\": {\"wasi\": {\"atom\": \"python\", \"package\": null, \"main_args\": null}}}}, \"entrypoint\": \"python\"}",
1118 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/packages/wasmer/python/python-3.12.0-build.5-a11e0414-c68d-473c-958f-fc96ef7adb20.webc",
1119 "piritaSha256Hash": "7771ed54376c16da86581736fad84fb761a049915902a7070e854965be0d5874"
1120 }
1121 },
1122 {
1123 "version": "3.12.1",
1124 "isArchived": false,
1125 "v2": {
1126 "webcManifest": "{\"atoms\": {\"python\": {\"kind\": \"https://webc.org/kind/wasm\", \"signature\": \"sha256:O36BXLHv3/80cABbAiF7gzuSHzzin1blTfJ42LDhT18=\"}}, \"package\": {\"wapm\": {\"name\": \"wasmer/python\", \"license\": \"ISC\", \"version\": \"3.12.1\", \"repository\": \"https://github.com/wapm-packages/python\", \"description\": \"Python is an interpreted, high-level, general-purpose programming language\"}}, \"commands\": {\"python\": {\"runner\": \"https://webc.org/runner/wasi/command@unstable_\", \"annotations\": {\"wasi\": {\"atom\": \"python\", \"package\": null, \"main_args\": null}}}}, \"entrypoint\": \"python\"}",
1127 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/packages/wasmer/python/python-3.12.0-build.2-ed98c999-fcda-4f80-96dc-7c0f8be8baa6.webc",
1128 "piritaSha256Hash": "7835401e3ca1977ba05b5e51541363783b8a7700da270dd851f10fe2e4f27f07"
1129 },
1130 "v3": {
1131 "webcManifest": "{\"atoms\": {\"python\": {\"kind\": \"https://webc.org/kind/wasm\", \"signature\": \"sha256:O36BXLHv3/80cABbAiF7gzuSHzzin1blTfJ42LDhT18=\"}}, \"package\": {\"wapm\": {\"name\": \"wasmer/python\", \"license\": \"ISC\", \"version\": \"3.12.1\", \"repository\": \"https://github.com/wapm-packages/python\", \"description\": \"Python is an interpreted, high-level, general-purpose programming language\"}}, \"commands\": {\"python\": {\"runner\": \"https://webc.org/runner/wasi/command@unstable_\", \"annotations\": {\"wasi\": {\"atom\": \"python\", \"package\": null, \"main_args\": null}}}}, \"entrypoint\": \"python\"}",
1132 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/packages/wasmer/python/python-3.12.0-build.2-ed98c999-fcda-4f80-96dc-7c0f8be8baa6.webc",
1133 "piritaSha256Hash": "7835401e3ca1977ba05b5e51541363783b8a7700da270dd851f10fe2e4f27f07"
1134 }
1135 },
1136 {
1137 "version": "3.12.0",
1138 "isArchived": true,
1139 "v2": {
1140 "webcManifest": "{\"atoms\": {\"python\": {\"kind\": \"https://webc.org/kind/wasm\", \"signature\": \"sha256:O36BXLHv3/80cABbAiF7gzuSHzzin1blTfJ42LDhT18=\"}}, \"package\": {\"wapm\": {\"name\": \"wasmer/python\", \"license\": \"ISC\", \"version\": \"3.12.0\", \"repository\": \"https://github.com/wapm-packages/python\", \"description\": \"Python is an interpreted, high-level, general-purpose programming language\"}}, \"commands\": {\"python\": {\"runner\": \"https://webc.org/runner/wasi/command@unstable_\", \"annotations\": {\"wasi\": {\"atom\": \"python\", \"package\": null, \"main_args\": null}}}}, \"entrypoint\": \"python\"}",
1141 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/packages/wasmer/python/python-3.12.0-32065e5e-84fe-4483-a380-0aa750772a3a.webc",
1142 "piritaSha256Hash": "e5d6e9d16db988eb323e34e2c152ebfb32dc7043d6b7ddc00ad57d3beae24adb"
1143 },
1144 "v3": {
1145 "webcManifest": "{\"atoms\": {\"python\": {\"kind\": \"https://webc.org/kind/wasm\", \"signature\": \"sha256:O36BXLHv3/80cABbAiF7gzuSHzzin1blTfJ42LDhT18=\"}}, \"package\": {\"wapm\": {\"name\": \"wasmer/python\", \"license\": \"ISC\", \"version\": \"3.12.0\", \"repository\": \"https://github.com/wapm-packages/python\", \"description\": \"Python is an interpreted, high-level, general-purpose programming language\"}}, \"commands\": {\"python\": {\"runner\": \"https://webc.org/runner/wasi/command@unstable_\", \"annotations\": {\"wasi\": {\"atom\": \"python\", \"package\": null, \"main_args\": null}}}}, \"entrypoint\": \"python\"}",
1146 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/packages/wasmer/python/python-3.12.0-32065e5e-84fe-4483-a380-0aa750772a3a.webc",
1147 "piritaSha256Hash": "e5d6e9d16db988eb323e34e2c152ebfb32dc7043d6b7ddc00ad57d3beae24adb"
1148 }
1149 },
1150 ]
1151 },
1152 "info": {
1153 "defaultFrontend": "https://wasmer.io/",
1154 },
1155 }
1156 }
1157 };
1158 let response = HttpResponse {
1159 body: Some(serde_json::to_vec(&body).unwrap()),
1160 redirected: false,
1161 status: StatusCode::OK,
1162 headers: HeaderMap::new(),
1163 };
1164 let client = Arc::new(DummyClient::new(vec![response]));
1165 let registry_endpoint = BackendSource::WASMER_PROD_ENDPOINT.parse().unwrap();
1166 let request = PackageSource::from_str("wasmer/python").unwrap();
1167 let source = BackendSource::new(registry_endpoint, client.clone());
1168
1169 let summaries = source.query(&request).await.unwrap();
1170
1171 assert_eq!(summaries.len(), 1);
1172 assert_eq!(
1173 summaries[0].pkg.id.as_named().unwrap().version.to_string(),
1174 "3.12.1"
1175 );
1176 }
1177
1178 #[tokio::test]
1179 async fn query_the_backend_again_if_cached_queries_dont_match() {
1180 let cached_value = serde_json::from_value(serde_json::json! {
1181 {
1182 "data": {
1183 "getPackage": {
1184 "packageName": "python",
1185 "namespace": "wasmer",
1186 "versions": [
1187 {
1188 "version": "3.12.0",
1189 "v2": {
1190 "webcManifest": "{\"package\": {\"wapm\": {\"name\": \"wasmer/python\", \"version\": \"3.12.0\", \"description\": \"Python\"}}}",
1191 "piritaDownloadUrl": "https://wasmer.io/wasmer/python@3.12.0",
1192 "piritaSha256Hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
1193 },
1194 "v3": {
1195 "webcManifest": "{\"package\": {\"wapm\": {\"name\": \"wasmer/python\", \"version\": \"3.12.0\", \"description\": \"Python\"}}}",
1196 "piritaDownloadUrl": "https://wasmer.io/wasmer/python@3.12.0",
1197 "piritaSha256Hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
1198 }
1199 },
1200 ]
1201 },
1202 "info": {
1203 "defaultFrontend": "https://wasmer.io/",
1204 },
1205 }
1206 }
1207 }).unwrap();
1208 let body = serde_json::json! {
1209 {
1210 "data": {
1211 "getPackage": {
1212 "packageName": "python",
1213 "namespace": "wasmer",
1214 "versions": [
1215 {
1216 "version": "4.0.0",
1217 "v2": {
1218 "webcManifest": "{\"package\": {\"wapm\": {\"name\": \"wasmer/python\", \"version\": \"4.0.0\", \"description\": \"Python\"}}}",
1219 "piritaDownloadUrl": "https://wasmer.io/wasmer/python@4.0.0",
1220 "piritaSha256Hash": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
1221 },
1222 "v3": {
1223 "webcManifest": "{\"package\": {\"wapm\": {\"name\": \"wasmer/python\", \"version\": \"4.0.0\", \"description\": \"Python\"}}}",
1224 "piritaDownloadUrl": "https://wasmer.io/wasmer/python@4.0.0",
1225 "piritaSha256Hash": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
1226 }
1227 },
1228 {
1229 "version": "3.12.0",
1230 "v2": {
1231 "webcManifest": "{\"package\": {\"wapm\": {\"name\": \"wasmer/python\", \"version\": \"3.12.0\", \"description\": \"Python\"}}}",
1232 "piritaDownloadUrl": "https://wasmer.io/wasmer/python@3.12.0",
1233 "piritaSha256Hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
1234 },
1235 "v3": {
1236 "webcManifest": "{\"package\": {\"wapm\": {\"name\": \"wasmer/python\", \"version\": \"3.12.0\", \"description\": \"Python\"}}}",
1237 "piritaDownloadUrl": "https://wasmer.io/wasmer/python@3.12.0",
1238 "piritaSha256Hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
1239 }
1240 },
1241 ]
1242 },
1243 "info": {
1244 "defaultFrontend": "https://wasmer.io/",
1245 },
1246 }
1247 }
1248 };
1249 let response = HttpResponse {
1250 body: Some(serde_json::to_vec(&body).unwrap()),
1251 redirected: false,
1252 status: StatusCode::OK,
1253 headers: HeaderMap::new(),
1254 };
1255 let client = Arc::new(DummyClient::new(vec![response]));
1256 let registry_endpoint = BackendSource::WASMER_PROD_ENDPOINT.parse().unwrap();
1257 let request = PackageSource::from_str("wasmer/python@4.0.0").unwrap();
1258 let temp = tempfile::tempdir().unwrap();
1259 let source = BackendSource::new(registry_endpoint, client.clone())
1260 .with_local_cache(temp.path(), Duration::from_secs(0));
1261 update_cached_query(
1262 source.cache.as_ref().unwrap(),
1263 "wasmer/python",
1264 &cached_value,
1265 )
1266 .await
1267 .unwrap();
1268
1269 let summaries = source.query(&request).await.unwrap();
1270
1271 assert_eq!(summaries.len(), 1);
1272 assert_eq!(
1273 summaries[0].pkg.id.as_named().unwrap().version.to_string(),
1274 "4.0.0"
1275 );
1276 }
1277}