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