1use std::{
2 path::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 if let Err(e) = cache.update(&package_name, &response) {
295 tracing::warn!(
296 package_name,
297 error = &*e,
298 "An error occurred while caching the GraphQL response",
299 );
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.join(package_name)
464 }
465
466 fn lookup_cached_query(&self, package_name: &str) -> Result<Option<WebQuery>, Error> {
467 let filename = self.path(package_name);
468
469 let _span =
470 tracing::debug_span!("lookup_cached_query", filename=%filename.display()).entered();
471
472 tracing::trace!("Reading cached entry from disk");
473 let json = match std::fs::read(&filename) {
474 Ok(json) => json,
475 Err(e) if e.kind() == std::io::ErrorKind::NotFound => {
476 tracing::debug!("Cache miss");
477 return Ok(None);
478 }
479 Err(e) => {
480 return Err(
481 Error::new(e).context(format!("Unable to read \"{}\"", filename.display()))
482 );
483 }
484 };
485
486 let entry: CacheEntry = match serde_json::from_slice(&json) {
487 Ok(entry) => entry,
488 Err(e) => {
489 let _ = std::fs::remove_file(&filename);
492
493 return Err(Error::new(e).context("Unable to parse the cached query"));
494 }
495 };
496
497 if !entry.is_still_valid(self.timeout) {
498 tracing::debug!(timestamp = entry.unix_timestamp, "Cached entry is stale");
499 let _ = std::fs::remove_file(&filename);
500 return Ok(None);
501 }
502
503 if entry.package_name != package_name {
504 let _ = std::fs::remove_file(&filename);
505 anyhow::bail!(
506 "The cached response at \"{}\" corresponds to the \"{}\" package, but expected \"{}\"",
507 filename.display(),
508 entry.package_name,
509 package_name,
510 );
511 }
512
513 Ok(Some(entry.response))
514 }
515
516 fn update(&self, package_name: &str, response: &WebQuery) -> Result<(), Error> {
517 let entry = CacheEntry {
518 unix_timestamp: SystemTime::UNIX_EPOCH
519 .elapsed()
520 .unwrap_or_default()
521 .as_secs(),
522 package_name: package_name.to_string(),
523 response: response.clone(),
524 };
525
526 let _ = std::fs::create_dir_all(&self.cache_dir);
527
528 let mut temp = tempfile::NamedTempFile::new_in(&self.cache_dir)
530 .context("Unable to create a temporary file")?;
531 serde_json::to_writer_pretty(&mut temp, &entry)
532 .context("Unable to serialize the cache entry")?;
533 temp.as_file()
534 .sync_all()
535 .context("Flushing the temp file failed")?;
536
537 let filename = self.path(package_name);
541 tracing::debug!(
542 filename=%filename.display(),
543 package_name,
544 "Saving the query to disk",
545 );
546
547 if let Some(parent) = filename.parent() {
548 let _ = std::fs::create_dir_all(parent);
549 }
550 temp.persist(&filename).with_context(|| {
551 format!(
552 "Unable to persist the temp file to \"{}\"",
553 filename.display()
554 )
555 })?;
556
557 Ok(())
558 }
559}
560
561#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
562struct CacheEntry {
563 unix_timestamp: u64,
564 package_name: String,
565 response: WebQuery,
566}
567
568impl CacheEntry {
569 fn is_still_valid(&self, timeout: Duration) -> bool {
570 let timestamp = SystemTime::UNIX_EPOCH + Duration::from_secs(self.unix_timestamp);
571
572 match timestamp.elapsed() {
573 Ok(duration) if duration <= timeout => true,
574 Ok(_) => {
575 false
577 }
578 Err(_) => {
579 false
583 }
584 }
585 }
586}
587
588#[allow(dead_code)]
589pub const WASMER_WEBC_QUERY_ALL: &str = r#"{
590 getPackage(name: "$NAME") {
591 packageName
592 namespace
593 versions {
594 version
595 isArchived
596 v2: distribution(version: V2) {
597 piritaDownloadUrl
598 piritaSha256Hash
599 webcManifest
600 }
601 v3: distribution(version: V3) {
602 piritaDownloadUrl
603 piritaSha256Hash
604 webcManifest
605 }
606 }
607 }
608 info {
609 defaultFrontend
610 }
611}"#;
612
613pub const WASMER_WEBC_QUERY_BY_HASH: &str = r#"{
614 getPackageRelease(hash: "$HASH") {
615 piritaManifest
616 isArchived
617 webcUrl
618 }
619}"#;
620
621#[derive(serde::Serialize, serde::Deserialize, Clone, Debug)]
622pub struct Reply<T> {
623 pub data: T,
624}
625
626#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
627struct GetPackageRelease {
628 #[serde(rename = "getPackageRelease")]
629 get_package_release: Option<PackageWebc>,
630}
631
632#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
633struct PackageWebc {
634 #[serde(rename = "piritaManifest")]
635 pub pirita_manifest: String,
636 #[serde(rename = "isArchived")]
637 pub is_archived: bool,
638 #[serde(rename = "webcUrl")]
639 pub webc_url: url::Url,
640}
641
642impl PackageWebc {
643 fn try_into_summary(self, hash: PackageHash) -> Result<PackageSummary, anyhow::Error> {
644 let manifest: Manifest = serde_json::from_str(&self.pirita_manifest)
645 .context("Unable to deserialize the manifest")?;
646
647 let id = PackageId::Hash(hash.clone());
648
649 let info = PackageInfo::from_manifest(id, &manifest, webc::Version::V3)
650 .context("could not convert the manifest ")?;
651
652 Ok(PackageSummary {
653 pkg: info,
654 dist: DistributionInfo {
655 webc: self.webc_url,
656 webc_sha256: WebcHash(hash.as_sha256().context("invalid hash")?.0),
658 },
659 })
660 }
661}
662
663#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
664pub struct WebQuery {
665 #[serde(rename = "data")]
666 pub data: WebQueryData,
667}
668
669#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
670pub struct WebQueryData {
671 #[serde(rename = "getPackage")]
672 pub get_package: Option<WebQueryGetPackage>,
673 pub info: WebQueryInfo,
674}
675
676#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
677pub struct WebQueryInfo {
678 #[serde(rename = "defaultFrontend")]
679 pub default_frontend: Url,
680}
681
682#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
683pub struct WebQueryGetPackage {
684 #[serde(rename = "packageName")]
685 pub package_name: String,
686 pub namespace: String,
687 pub versions: Vec<WebQueryGetPackageVersion>,
688}
689
690#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
691pub struct WebQueryGetPackageVersion {
692 pub version: String,
693 #[serde(rename = "isArchived", default)]
695 pub is_archived: bool,
696 pub v2: WebQueryGetPackageVersionDistribution,
697 pub v3: WebQueryGetPackageVersionDistribution,
698}
699
700#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
701pub enum WebCVersion {
702 V2,
703 V3,
704}
705
706impl Default for WebCVersion {
707 fn default() -> Self {
708 Self::V2
709 }
710}
711
712impl From<WebCVersion> for webc::Version {
713 fn from(val: WebCVersion) -> Self {
714 match val {
715 WebCVersion::V2 => webc::Version::V2,
716 WebCVersion::V3 => webc::Version::V3,
717 }
718 }
719}
720
721#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
722pub struct WebQueryGetPackageVersionDistribution {
723 #[serde(rename = "piritaDownloadUrl")]
724 pub pirita_download_url: Option<Url>,
725 #[serde(rename = "piritaSha256Hash")]
726 pub pirita_sha256_hash: Option<String>,
727 #[serde(rename = "webcManifest")]
728 pub webc_manifest: Option<String>,
729}
730
731#[cfg(test)]
732mod tests {
733 use std::{str::FromStr, sync::Mutex};
734
735 use http::{HeaderMap, StatusCode};
736
737 use crate::{
738 http::HttpResponse,
739 runtime::resolver::inputs::{DistributionInfo, PackageInfo},
740 };
741
742 use super::*;
743
744 const WASMER_PACK_CLI_REQUEST: &[u8] = br#"
750 {
751 "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}"
752 }
753 "#;
754 const WASMER_PACK_CLI_RESPONSE: &[u8] = br#"
755 {
756 "data": {
757 "getPackage": {
758 "packageName": "wasmer-pack-cli",
759 "namespace": "wasmer",
760 "versions": [
761 {
762 "version": "0.7.1",
763 "isArchived": false,
764 "v2": {
765 "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\"}",
766 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.7.1/wasmer-pack-cli-0.7.1.webc",
767 "piritaSha256Hash": "e821047f446dd20fb6b43a1648fe98b882276dfc480f020df6f00a49f69771fa"
768 },
769 "v3": {
770 "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\"}",
771 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.7.1/wasmer-pack-cli-0.7.1.webc",
772 "piritaSha256Hash": "e821047f446dd20fb6b43a1648fe98b882276dfc480f020df6f00a49f69771fa"
773 }
774 },
775 {
776 "version": "0.7.0",
777 "isArchived": false,
778 "v2": {
779 "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\"}",
780 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.7.0/wasmer-pack-cli-0.7.0.webc",
781 "piritaSha256Hash": "d085869201aa602673f70abbd5e14e5a6936216fa93314c5b103cda3da56e29e"
782 },
783 "v3": {
784 "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\"}",
785 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.7.0/wasmer-pack-cli-0.7.0.webc",
786 "piritaSha256Hash": "d085869201aa602673f70abbd5e14e5a6936216fa93314c5b103cda3da56e29e"
787 }
788 },
789 {
790 "version": "0.6.0",
791 "isArchived": false,
792 "v2": {
793 "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\"}",
794 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.6.0/wasmer-pack-cli-0.6.0.webc",
795 "piritaSha256Hash": "7e1add1640d0037ff6a726cd7e14ea36159ec2db8cb6debd0e42fa2739bea52b"
796 },
797 "v3": {
798 "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\"}",
799 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.6.0/wasmer-pack-cli-0.6.0.webc",
800 "piritaSha256Hash": "7e1add1640d0037ff6a726cd7e14ea36159ec2db8cb6debd0e42fa2739bea52b"
801 }
802 },
803 {
804 "version": "0.5.3",
805 "isArchived": false,
806 "v2": {
807 "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\"}",
808 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.5.3/wasmer-pack-cli-0.5.3.webc",
809 "piritaSha256Hash": "44fdcdde23d34175887243d7c375e4e4a7e6e2cd1ae063ebffbede4d1f68f14a"
810 },
811 "v3": {
812 "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\"}",
813 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.5.3/wasmer-pack-cli-0.5.3.webc",
814 "piritaSha256Hash": "44fdcdde23d34175887243d7c375e4e4a7e6e2cd1ae063ebffbede4d1f68f14a"
815 }
816 },
817 {
818 "version": "0.5.2",
819 "isArchived": false,
820 "v2": {
821 "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\"}",
822 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.5.2/wasmer-pack-cli-0.5.2.webc",
823 "piritaSha256Hash": "d1dbc8168c3a2491a7158017a9c88df9e0c15bed88ebcd6d9d756e4b03adde95"
824 },
825 "v3": {
826 "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\"}",
827 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.5.2/wasmer-pack-cli-0.5.2.webc",
828 "piritaSha256Hash": "d1dbc8168c3a2491a7158017a9c88df9e0c15bed88ebcd6d9d756e4b03adde95"
829 }
830 },
831 {
832 "version": "0.5.1",
833 "isArchived": false,
834 "v2": {
835 "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\"}",
836 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.5.1/wasmer-pack-cli-0.5.1.webc",
837 "piritaSha256Hash": "c42924619660e2befd69b5c72729388985dcdcbf912d51a00015237fec3e1ade"
838 },
839 "v3": {
840 "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\"}",
841 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.5.1/wasmer-pack-cli-0.5.1.webc",
842 "piritaSha256Hash": "c42924619660e2befd69b5c72729388985dcdcbf912d51a00015237fec3e1ade"
843 }
844 },
845 {
846 "version": "0.5.0",
847 "isArchived": false,
848 "v2": {
849 "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\"}",
850 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.5.0/wasmer-pack-cli-0.5.0.webc",
851 "piritaSha256Hash": "d30ca468372faa96469163d2d1546dd34be9505c680677e6ab86a528a268e5f5"
852 },
853 "v3": {
854 "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\"}",
855 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.5.0/wasmer-pack-cli-0.5.0.webc",
856 "piritaSha256Hash": "d30ca468372faa96469163d2d1546dd34be9505c680677e6ab86a528a268e5f5"
857 }
858 },
859 {
860 "version": "0.5.0-rc.1",
861 "isArchived": false,
862 "v2": {
863 "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\"}",
864 "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",
865 "piritaSha256Hash": "0cd5d6e4c33c92c52784afed3a60c056953104d719717948d4663ff2521fe2bb"
866 },
867 "v3": {
868 "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\"}",
869 "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",
870 "piritaSha256Hash": "0cd5d6e4c33c92c52784afed3a60c056953104d719717948d4663ff2521fe2bb"
871 }
872 }
873 ]
874 },
875 "info": {
876 "defaultFrontend": "https://wasmer.io"
877 }
878 }
879 }
880 "#;
881
882 #[derive(Debug)]
883 struct DummyClient {
884 requests: Mutex<Vec<HttpRequest>>,
885 responses: Mutex<Vec<HttpResponse>>,
886 }
887
888 impl DummyClient {
889 fn new(responses: Vec<HttpResponse>) -> Self {
890 DummyClient {
891 requests: Mutex::new(Vec::new()),
892 responses: Mutex::new(responses),
893 }
894 }
895
896 fn take_requests(&self) -> Vec<HttpRequest> {
897 std::mem::take(&mut *self.requests.lock().unwrap())
898 }
899 }
900
901 impl HttpClient for DummyClient {
902 fn request(
903 &self,
904 request: HttpRequest,
905 ) -> futures::future::BoxFuture<'_, Result<HttpResponse, anyhow::Error>> {
906 self.requests.lock().unwrap().push(request);
907 let response = self.responses.lock().unwrap().remove(0);
908 Box::pin(async { Ok(response) })
909 }
910 }
911
912 #[tokio::test]
913 async fn run_known_query() {
914 let response = HttpResponse {
915 body: Some(WASMER_PACK_CLI_RESPONSE.to_vec()),
916 redirected: false,
917 status: StatusCode::OK,
918 headers: HeaderMap::new(),
919 };
920 let client = Arc::new(DummyClient::new(vec![response]));
921 let registry_endpoint = BackendSource::WASMER_PROD_ENDPOINT.parse().unwrap();
922 let request = PackageSource::from_str("wasmer/wasmer-pack-cli@^0.6").unwrap();
923 let source = BackendSource::new(registry_endpoint, client.clone());
924
925 let summaries = source.query(&request).await.unwrap();
926
927 assert_eq!(
928 summaries,
929 [PackageSummary {
930 pkg: PackageInfo {
931 id: PackageId::new_named("wasmer/wasmer-pack-cli", Version::new(0, 6, 0)),
932 dependencies: Vec::new(),
933 commands: vec![crate::runtime::resolver::Command {
934 name: "wasmer-pack".to_string(),
935 },],
936 entrypoint: Some("wasmer-pack".to_string()),
937 filesystem: vec![],
938 },
939 dist: DistributionInfo {
940 webc: "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.6.0/wasmer-pack-cli-0.6.0.webc"
941 .parse()
942 .unwrap(),
943 webc_sha256: WebcHash::from_bytes([
944 126, 26, 221, 22, 64, 208, 3, 127, 246, 167, 38, 205, 126, 20, 234, 54, 21,
945 158, 194, 219, 140, 182, 222, 189, 14, 66, 250, 39, 57, 190, 165, 43,
946 ]),
947 }
948 }]
949 );
950 let requests = client.take_requests();
951 assert_eq!(requests.len(), 1);
952 let request = &requests[0];
953 assert_eq!(request.method, http::Method::POST);
954 assert_eq!(request.url.as_str(), BackendSource::WASMER_PROD_ENDPOINT);
955 assert_eq!(request.headers.len(), 2);
956 assert_eq!(request.headers["User-Agent"], USER_AGENT);
957 assert_eq!(request.headers["Content-Type"], "application/json");
958 let body: serde_json::Value =
959 serde_json::from_slice(request.body.as_deref().unwrap()).unwrap();
960 let expected_body: serde_json::Value =
961 serde_json::from_slice(WASMER_PACK_CLI_REQUEST).unwrap();
962 assert_eq!(body, expected_body);
963 }
964
965 #[tokio::test]
969 async fn skip_package_versions_with_missing_fields() {
970 let body = serde_json::json! {
971 {
972 "data": {
973 "getPackage": {
974 "packageName": "cowsay",
975 "namespace": "_",
976 "versions": [
977 {
978 "version": "0.2.0",
979 "v2": {
980 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/packages/_/cowsay/cowsay-0.2.0.webc",
981 "piritaSha256Hash": "9586938a0a89219dafe4ae97a901c56d4b3e2a9941520d1309ae880c9a1868c9",
982 "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}}}}}",
983 },
984 "v3": {
985 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/packages/_/cowsay/cowsay-0.2.0.webc",
986 "piritaSha256Hash": "9586938a0a89219dafe4ae97a901c56d4b3e2a9941520d1309ae880c9a1868c9",
987 "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}}}}}",
988 }
989 },
990 {
991 "version": "0.1.3",
992 "v2": {
993 "piritaDownloadUrl": "https://example.com/",
994 "piritaSha256Hash": "1234asdf"
995 },
996 "v3": {
997 "piritaDownloadUrl": "https://example.com/",
998 "piritaSha256Hash": "1234asdf"
999 }
1000 },
1001 {
1002 "version": "0.1.2",
1003 "v2": {
1004 "piritaDownloadUrl": "https://example.com/",
1005 "piritaSha256Hash": "1234asdf",
1006 "webcManifest": "{}",
1007 },
1008 "v3": {
1009 "piritaDownloadUrl": "https://example.com/",
1010 "piritaSha256Hash": "1234asdf",
1011 "webcManifest": "{}",
1012 }
1013 },
1014 {
1015 "version": "0.1.3",
1016 "v2": {
1017 "webcManifest": "{}",
1018 "piritaDownloadUrl": "https://example.com/",
1019 "piritaSha256Hash": "1234asdf"
1020 },
1021 "v3": {
1022 "webcManifest": "{}",
1023 "piritaDownloadUrl": "https://example.com/",
1024 "piritaSha256Hash": "1234asdf"
1025 }
1026 }
1027 ]
1028 },
1029 "info": {
1030 "defaultFrontend": "https://wasmer.io/",
1031 },
1032 }
1033 }
1034
1035 };
1036 let response = HttpResponse {
1037 body: Some(serde_json::to_vec(&body).unwrap()),
1038 redirected: false,
1039 status: StatusCode::OK,
1040 headers: HeaderMap::new(),
1041 };
1042 let client = Arc::new(DummyClient::new(vec![response]));
1043 let registry_endpoint = BackendSource::WASMER_PROD_ENDPOINT.parse().unwrap();
1044 let request = PackageSource::from_str("_/cowsay").unwrap();
1045 let source = BackendSource::new(registry_endpoint, client.clone());
1046
1047 let summaries = source.query(&request).await.unwrap();
1048
1049 assert_eq!(summaries.len(), 1);
1050 assert_eq!(
1051 summaries[0].pkg.id.as_named().unwrap().version.to_string(),
1052 "0.2.0"
1053 );
1054 }
1055
1056 #[tokio::test]
1057 async fn skip_archived_package_versions() {
1058 let body = serde_json::json! {
1059 {
1060 "data": {
1061 "getPackage": {
1062 "packageName": "python",
1063 "namespace": "wasmer",
1064 "versions": [
1065 {
1066 "version": "3.12.2",
1067 "isArchived": true,
1068 "v2": {
1069 "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\"}",
1070 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/packages/wasmer/python/python-3.12.0-build.5-a11e0414-c68d-473c-958f-fc96ef7adb20.webc",
1071 "piritaSha256Hash": "7771ed54376c16da86581736fad84fb761a049915902a7070e854965be0d5874"
1072 },
1073 "v3": {
1074 "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\"}",
1075 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/packages/wasmer/python/python-3.12.0-build.5-a11e0414-c68d-473c-958f-fc96ef7adb20.webc",
1076 "piritaSha256Hash": "7771ed54376c16da86581736fad84fb761a049915902a7070e854965be0d5874"
1077 }
1078 },
1079 {
1080 "version": "3.12.1",
1081 "isArchived": false,
1082 "v2": {
1083 "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\"}",
1084 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/packages/wasmer/python/python-3.12.0-build.2-ed98c999-fcda-4f80-96dc-7c0f8be8baa6.webc",
1085 "piritaSha256Hash": "7835401e3ca1977ba05b5e51541363783b8a7700da270dd851f10fe2e4f27f07"
1086 },
1087 "v3": {
1088 "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\"}",
1089 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/packages/wasmer/python/python-3.12.0-build.2-ed98c999-fcda-4f80-96dc-7c0f8be8baa6.webc",
1090 "piritaSha256Hash": "7835401e3ca1977ba05b5e51541363783b8a7700da270dd851f10fe2e4f27f07"
1091 }
1092 },
1093 {
1094 "version": "3.12.0",
1095 "isArchived": true,
1096 "v2": {
1097 "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\"}",
1098 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/packages/wasmer/python/python-3.12.0-32065e5e-84fe-4483-a380-0aa750772a3a.webc",
1099 "piritaSha256Hash": "e5d6e9d16db988eb323e34e2c152ebfb32dc7043d6b7ddc00ad57d3beae24adb"
1100 },
1101 "v3": {
1102 "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\"}",
1103 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/packages/wasmer/python/python-3.12.0-32065e5e-84fe-4483-a380-0aa750772a3a.webc",
1104 "piritaSha256Hash": "e5d6e9d16db988eb323e34e2c152ebfb32dc7043d6b7ddc00ad57d3beae24adb"
1105 }
1106 },
1107 ]
1108 },
1109 "info": {
1110 "defaultFrontend": "https://wasmer.io/",
1111 },
1112 }
1113 }
1114 };
1115 let response = HttpResponse {
1116 body: Some(serde_json::to_vec(&body).unwrap()),
1117 redirected: false,
1118 status: StatusCode::OK,
1119 headers: HeaderMap::new(),
1120 };
1121 let client = Arc::new(DummyClient::new(vec![response]));
1122 let registry_endpoint = BackendSource::WASMER_PROD_ENDPOINT.parse().unwrap();
1123 let request = PackageSource::from_str("wasmer/python").unwrap();
1124 let source = BackendSource::new(registry_endpoint, client.clone());
1125
1126 let summaries = source.query(&request).await.unwrap();
1127
1128 assert_eq!(summaries.len(), 1);
1129 assert_eq!(
1130 summaries[0].pkg.id.as_named().unwrap().version.to_string(),
1131 "3.12.1"
1132 );
1133 }
1134
1135 #[tokio::test]
1136 async fn query_the_backend_again_if_cached_queries_dont_match() {
1137 let cached_value = serde_json::from_value(serde_json::json! {
1138 {
1139 "data": {
1140 "getPackage": {
1141 "packageName": "python",
1142 "namespace": "wasmer",
1143 "versions": [
1144 {
1145 "version": "3.12.0",
1146 "v2": {
1147 "webcManifest": "{\"package\": {\"wapm\": {\"name\": \"wasmer/python\", \"version\": \"3.12.0\", \"description\": \"Python\"}}}",
1148 "piritaDownloadUrl": "https://wasmer.io/wasmer/python@3.12.0",
1149 "piritaSha256Hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
1150 },
1151 "v3": {
1152 "webcManifest": "{\"package\": {\"wapm\": {\"name\": \"wasmer/python\", \"version\": \"3.12.0\", \"description\": \"Python\"}}}",
1153 "piritaDownloadUrl": "https://wasmer.io/wasmer/python@3.12.0",
1154 "piritaSha256Hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
1155 }
1156 },
1157 ]
1158 },
1159 "info": {
1160 "defaultFrontend": "https://wasmer.io/",
1161 },
1162 }
1163 }
1164 }).unwrap();
1165 let body = serde_json::json! {
1166 {
1167 "data": {
1168 "getPackage": {
1169 "packageName": "python",
1170 "namespace": "wasmer",
1171 "versions": [
1172 {
1173 "version": "4.0.0",
1174 "v2": {
1175 "webcManifest": "{\"package\": {\"wapm\": {\"name\": \"wasmer/python\", \"version\": \"4.0.0\", \"description\": \"Python\"}}}",
1176 "piritaDownloadUrl": "https://wasmer.io/wasmer/python@4.0.0",
1177 "piritaSha256Hash": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
1178 },
1179 "v3": {
1180 "webcManifest": "{\"package\": {\"wapm\": {\"name\": \"wasmer/python\", \"version\": \"4.0.0\", \"description\": \"Python\"}}}",
1181 "piritaDownloadUrl": "https://wasmer.io/wasmer/python@4.0.0",
1182 "piritaSha256Hash": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
1183 }
1184 },
1185 {
1186 "version": "3.12.0",
1187 "v2": {
1188 "webcManifest": "{\"package\": {\"wapm\": {\"name\": \"wasmer/python\", \"version\": \"3.12.0\", \"description\": \"Python\"}}}",
1189 "piritaDownloadUrl": "https://wasmer.io/wasmer/python@3.12.0",
1190 "piritaSha256Hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
1191 },
1192 "v3": {
1193 "webcManifest": "{\"package\": {\"wapm\": {\"name\": \"wasmer/python\", \"version\": \"3.12.0\", \"description\": \"Python\"}}}",
1194 "piritaDownloadUrl": "https://wasmer.io/wasmer/python@3.12.0",
1195 "piritaSha256Hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
1196 }
1197 },
1198 ]
1199 },
1200 "info": {
1201 "defaultFrontend": "https://wasmer.io/",
1202 },
1203 }
1204 }
1205 };
1206 let response = HttpResponse {
1207 body: Some(serde_json::to_vec(&body).unwrap()),
1208 redirected: false,
1209 status: StatusCode::OK,
1210 headers: HeaderMap::new(),
1211 };
1212 let client = Arc::new(DummyClient::new(vec![response]));
1213 let registry_endpoint = BackendSource::WASMER_PROD_ENDPOINT.parse().unwrap();
1214 let request = PackageSource::from_str("wasmer/python@4.0.0").unwrap();
1215 let temp = tempfile::tempdir().unwrap();
1216 let source = BackendSource::new(registry_endpoint, client.clone())
1217 .with_local_cache(temp.path(), Duration::from_secs(0));
1218 source
1219 .cache
1220 .as_ref()
1221 .unwrap()
1222 .update("wasmer/python", &cached_value)
1223 .unwrap();
1224
1225 let summaries = source.query(&request).await.unwrap();
1226
1227 assert_eq!(summaries.len(), 1);
1228 assert_eq!(
1229 summaries[0].pkg.id.as_named().unwrap().version.to_string(),
1230 "4.0.0"
1231 );
1232 }
1233}