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 && 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.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, Default)]
701pub enum WebCVersion {
702 #[default]
703 V2,
704 V3,
705}
706
707impl From<WebCVersion> for webc::Version {
708 fn from(val: WebCVersion) -> Self {
709 match val {
710 WebCVersion::V2 => webc::Version::V2,
711 WebCVersion::V3 => webc::Version::V3,
712 }
713 }
714}
715
716#[derive(Debug, serde::Serialize, serde::Deserialize, Clone)]
717pub struct WebQueryGetPackageVersionDistribution {
718 #[serde(rename = "piritaDownloadUrl")]
719 pub pirita_download_url: Option<Url>,
720 #[serde(rename = "piritaSha256Hash")]
721 pub pirita_sha256_hash: Option<String>,
722 #[serde(rename = "webcManifest")]
723 pub webc_manifest: Option<String>,
724}
725
726#[cfg(test)]
727mod tests {
728 use std::{str::FromStr, sync::Mutex};
729
730 use http::{HeaderMap, StatusCode};
731
732 use crate::{
733 http::HttpResponse,
734 runtime::resolver::inputs::{DistributionInfo, PackageInfo},
735 };
736
737 use super::*;
738
739 const WASMER_PACK_CLI_REQUEST: &[u8] = br#"
745 {
746 "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}"
747 }
748 "#;
749 const WASMER_PACK_CLI_RESPONSE: &[u8] = br#"
750 {
751 "data": {
752 "getPackage": {
753 "packageName": "wasmer-pack-cli",
754 "namespace": "wasmer",
755 "versions": [
756 {
757 "version": "0.7.1",
758 "isArchived": false,
759 "v2": {
760 "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\"}",
761 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.7.1/wasmer-pack-cli-0.7.1.webc",
762 "piritaSha256Hash": "e821047f446dd20fb6b43a1648fe98b882276dfc480f020df6f00a49f69771fa"
763 },
764 "v3": {
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 },
770 {
771 "version": "0.7.0",
772 "isArchived": false,
773 "v2": {
774 "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\"}",
775 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.7.0/wasmer-pack-cli-0.7.0.webc",
776 "piritaSha256Hash": "d085869201aa602673f70abbd5e14e5a6936216fa93314c5b103cda3da56e29e"
777 },
778 "v3": {
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 },
784 {
785 "version": "0.6.0",
786 "isArchived": false,
787 "v2": {
788 "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\"}",
789 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.6.0/wasmer-pack-cli-0.6.0.webc",
790 "piritaSha256Hash": "7e1add1640d0037ff6a726cd7e14ea36159ec2db8cb6debd0e42fa2739bea52b"
791 },
792 "v3": {
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 },
798 {
799 "version": "0.5.3",
800 "isArchived": false,
801 "v2": {
802 "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\"}",
803 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.5.3/wasmer-pack-cli-0.5.3.webc",
804 "piritaSha256Hash": "44fdcdde23d34175887243d7c375e4e4a7e6e2cd1ae063ebffbede4d1f68f14a"
805 },
806 "v3": {
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 },
812 {
813 "version": "0.5.2",
814 "isArchived": false,
815 "v2": {
816 "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\"}",
817 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.5.2/wasmer-pack-cli-0.5.2.webc",
818 "piritaSha256Hash": "d1dbc8168c3a2491a7158017a9c88df9e0c15bed88ebcd6d9d756e4b03adde95"
819 },
820 "v3": {
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 },
826 {
827 "version": "0.5.1",
828 "isArchived": false,
829 "v2": {
830 "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\"}",
831 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.5.1/wasmer-pack-cli-0.5.1.webc",
832 "piritaSha256Hash": "c42924619660e2befd69b5c72729388985dcdcbf912d51a00015237fec3e1ade"
833 },
834 "v3": {
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 },
840 {
841 "version": "0.5.0",
842 "isArchived": false,
843 "v2": {
844 "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\"}",
845 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.5.0/wasmer-pack-cli-0.5.0.webc",
846 "piritaSha256Hash": "d30ca468372faa96469163d2d1546dd34be9505c680677e6ab86a528a268e5f5"
847 },
848 "v3": {
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 },
854 {
855 "version": "0.5.0-rc.1",
856 "isArchived": false,
857 "v2": {
858 "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\"}",
859 "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",
860 "piritaSha256Hash": "0cd5d6e4c33c92c52784afed3a60c056953104d719717948d4663ff2521fe2bb"
861 },
862 "v3": {
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 }
868 ]
869 },
870 "info": {
871 "defaultFrontend": "https://wasmer.io"
872 }
873 }
874 }
875 "#;
876
877 #[derive(Debug)]
878 struct DummyClient {
879 requests: Mutex<Vec<HttpRequest>>,
880 responses: Mutex<Vec<HttpResponse>>,
881 }
882
883 impl DummyClient {
884 fn new(responses: Vec<HttpResponse>) -> Self {
885 DummyClient {
886 requests: Mutex::new(Vec::new()),
887 responses: Mutex::new(responses),
888 }
889 }
890
891 fn take_requests(&self) -> Vec<HttpRequest> {
892 std::mem::take(&mut *self.requests.lock().unwrap())
893 }
894 }
895
896 impl HttpClient for DummyClient {
897 fn request(
898 &self,
899 request: HttpRequest,
900 ) -> futures::future::BoxFuture<'_, Result<HttpResponse, anyhow::Error>> {
901 self.requests.lock().unwrap().push(request);
902 let response = self.responses.lock().unwrap().remove(0);
903 Box::pin(async { Ok(response) })
904 }
905 }
906
907 #[tokio::test]
908 async fn run_known_query() {
909 let response = HttpResponse {
910 body: Some(WASMER_PACK_CLI_RESPONSE.to_vec()),
911 redirected: false,
912 status: StatusCode::OK,
913 headers: HeaderMap::new(),
914 };
915 let client = Arc::new(DummyClient::new(vec![response]));
916 let registry_endpoint = BackendSource::WASMER_PROD_ENDPOINT.parse().unwrap();
917 let request = PackageSource::from_str("wasmer/wasmer-pack-cli@^0.6").unwrap();
918 let source = BackendSource::new(registry_endpoint, client.clone());
919
920 let summaries = source.query(&request).await.unwrap();
921
922 assert_eq!(
923 summaries,
924 [PackageSummary {
925 pkg: PackageInfo {
926 id: PackageId::new_named("wasmer/wasmer-pack-cli", Version::new(0, 6, 0)),
927 dependencies: Vec::new(),
928 commands: vec![crate::runtime::resolver::Command {
929 name: "wasmer-pack".to_string(),
930 },],
931 entrypoint: Some("wasmer-pack".to_string()),
932 filesystem: vec![],
933 },
934 dist: DistributionInfo {
935 webc: "https://storage.googleapis.com/wapm-registry-prod/webc/wasmer/wasmer-pack-cli/0.6.0/wasmer-pack-cli-0.6.0.webc"
936 .parse()
937 .unwrap(),
938 webc_sha256: WebcHash::from_bytes([
939 126, 26, 221, 22, 64, 208, 3, 127, 246, 167, 38, 205, 126, 20, 234, 54, 21,
940 158, 194, 219, 140, 182, 222, 189, 14, 66, 250, 39, 57, 190, 165, 43,
941 ]),
942 }
943 }]
944 );
945 let requests = client.take_requests();
946 assert_eq!(requests.len(), 1);
947 let request = &requests[0];
948 assert_eq!(request.method, http::Method::POST);
949 assert_eq!(request.url.as_str(), BackendSource::WASMER_PROD_ENDPOINT);
950 assert_eq!(request.headers.len(), 2);
951 assert_eq!(request.headers["User-Agent"], USER_AGENT);
952 assert_eq!(request.headers["Content-Type"], "application/json");
953 let body: serde_json::Value =
954 serde_json::from_slice(request.body.as_deref().unwrap()).unwrap();
955 let expected_body: serde_json::Value =
956 serde_json::from_slice(WASMER_PACK_CLI_REQUEST).unwrap();
957 assert_eq!(body, expected_body);
958 }
959
960 #[tokio::test]
964 async fn skip_package_versions_with_missing_fields() {
965 let body = serde_json::json! {
966 {
967 "data": {
968 "getPackage": {
969 "packageName": "cowsay",
970 "namespace": "_",
971 "versions": [
972 {
973 "version": "0.2.0",
974 "v2": {
975 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/packages/_/cowsay/cowsay-0.2.0.webc",
976 "piritaSha256Hash": "9586938a0a89219dafe4ae97a901c56d4b3e2a9941520d1309ae880c9a1868c9",
977 "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}}}}}",
978 },
979 "v3": {
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 },
985 {
986 "version": "0.1.3",
987 "v2": {
988 "piritaDownloadUrl": "https://example.com/",
989 "piritaSha256Hash": "1234asdf"
990 },
991 "v3": {
992 "piritaDownloadUrl": "https://example.com/",
993 "piritaSha256Hash": "1234asdf"
994 }
995 },
996 {
997 "version": "0.1.2",
998 "v2": {
999 "piritaDownloadUrl": "https://example.com/",
1000 "piritaSha256Hash": "1234asdf",
1001 "webcManifest": "{}",
1002 },
1003 "v3": {
1004 "piritaDownloadUrl": "https://example.com/",
1005 "piritaSha256Hash": "1234asdf",
1006 "webcManifest": "{}",
1007 }
1008 },
1009 {
1010 "version": "0.1.3",
1011 "v2": {
1012 "webcManifest": "{}",
1013 "piritaDownloadUrl": "https://example.com/",
1014 "piritaSha256Hash": "1234asdf"
1015 },
1016 "v3": {
1017 "webcManifest": "{}",
1018 "piritaDownloadUrl": "https://example.com/",
1019 "piritaSha256Hash": "1234asdf"
1020 }
1021 }
1022 ]
1023 },
1024 "info": {
1025 "defaultFrontend": "https://wasmer.io/",
1026 },
1027 }
1028 }
1029
1030 };
1031 let response = HttpResponse {
1032 body: Some(serde_json::to_vec(&body).unwrap()),
1033 redirected: false,
1034 status: StatusCode::OK,
1035 headers: HeaderMap::new(),
1036 };
1037 let client = Arc::new(DummyClient::new(vec![response]));
1038 let registry_endpoint = BackendSource::WASMER_PROD_ENDPOINT.parse().unwrap();
1039 let request = PackageSource::from_str("_/cowsay").unwrap();
1040 let source = BackendSource::new(registry_endpoint, client.clone());
1041
1042 let summaries = source.query(&request).await.unwrap();
1043
1044 assert_eq!(summaries.len(), 1);
1045 assert_eq!(
1046 summaries[0].pkg.id.as_named().unwrap().version.to_string(),
1047 "0.2.0"
1048 );
1049 }
1050
1051 #[tokio::test]
1052 async fn skip_archived_package_versions() {
1053 let body = serde_json::json! {
1054 {
1055 "data": {
1056 "getPackage": {
1057 "packageName": "python",
1058 "namespace": "wasmer",
1059 "versions": [
1060 {
1061 "version": "3.12.2",
1062 "isArchived": true,
1063 "v2": {
1064 "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\"}",
1065 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/packages/wasmer/python/python-3.12.0-build.5-a11e0414-c68d-473c-958f-fc96ef7adb20.webc",
1066 "piritaSha256Hash": "7771ed54376c16da86581736fad84fb761a049915902a7070e854965be0d5874"
1067 },
1068 "v3": {
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 },
1074 {
1075 "version": "3.12.1",
1076 "isArchived": false,
1077 "v2": {
1078 "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\"}",
1079 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/packages/wasmer/python/python-3.12.0-build.2-ed98c999-fcda-4f80-96dc-7c0f8be8baa6.webc",
1080 "piritaSha256Hash": "7835401e3ca1977ba05b5e51541363783b8a7700da270dd851f10fe2e4f27f07"
1081 },
1082 "v3": {
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 },
1088 {
1089 "version": "3.12.0",
1090 "isArchived": true,
1091 "v2": {
1092 "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\"}",
1093 "piritaDownloadUrl": "https://storage.googleapis.com/wapm-registry-prod/packages/wasmer/python/python-3.12.0-32065e5e-84fe-4483-a380-0aa750772a3a.webc",
1094 "piritaSha256Hash": "e5d6e9d16db988eb323e34e2c152ebfb32dc7043d6b7ddc00ad57d3beae24adb"
1095 },
1096 "v3": {
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 },
1102 ]
1103 },
1104 "info": {
1105 "defaultFrontend": "https://wasmer.io/",
1106 },
1107 }
1108 }
1109 };
1110 let response = HttpResponse {
1111 body: Some(serde_json::to_vec(&body).unwrap()),
1112 redirected: false,
1113 status: StatusCode::OK,
1114 headers: HeaderMap::new(),
1115 };
1116 let client = Arc::new(DummyClient::new(vec![response]));
1117 let registry_endpoint = BackendSource::WASMER_PROD_ENDPOINT.parse().unwrap();
1118 let request = PackageSource::from_str("wasmer/python").unwrap();
1119 let source = BackendSource::new(registry_endpoint, client.clone());
1120
1121 let summaries = source.query(&request).await.unwrap();
1122
1123 assert_eq!(summaries.len(), 1);
1124 assert_eq!(
1125 summaries[0].pkg.id.as_named().unwrap().version.to_string(),
1126 "3.12.1"
1127 );
1128 }
1129
1130 #[tokio::test]
1131 async fn query_the_backend_again_if_cached_queries_dont_match() {
1132 let cached_value = serde_json::from_value(serde_json::json! {
1133 {
1134 "data": {
1135 "getPackage": {
1136 "packageName": "python",
1137 "namespace": "wasmer",
1138 "versions": [
1139 {
1140 "version": "3.12.0",
1141 "v2": {
1142 "webcManifest": "{\"package\": {\"wapm\": {\"name\": \"wasmer/python\", \"version\": \"3.12.0\", \"description\": \"Python\"}}}",
1143 "piritaDownloadUrl": "https://wasmer.io/wasmer/python@3.12.0",
1144 "piritaSha256Hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
1145 },
1146 "v3": {
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 },
1152 ]
1153 },
1154 "info": {
1155 "defaultFrontend": "https://wasmer.io/",
1156 },
1157 }
1158 }
1159 }).unwrap();
1160 let body = serde_json::json! {
1161 {
1162 "data": {
1163 "getPackage": {
1164 "packageName": "python",
1165 "namespace": "wasmer",
1166 "versions": [
1167 {
1168 "version": "4.0.0",
1169 "v2": {
1170 "webcManifest": "{\"package\": {\"wapm\": {\"name\": \"wasmer/python\", \"version\": \"4.0.0\", \"description\": \"Python\"}}}",
1171 "piritaDownloadUrl": "https://wasmer.io/wasmer/python@4.0.0",
1172 "piritaSha256Hash": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb"
1173 },
1174 "v3": {
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 },
1180 {
1181 "version": "3.12.0",
1182 "v2": {
1183 "webcManifest": "{\"package\": {\"wapm\": {\"name\": \"wasmer/python\", \"version\": \"3.12.0\", \"description\": \"Python\"}}}",
1184 "piritaDownloadUrl": "https://wasmer.io/wasmer/python@3.12.0",
1185 "piritaSha256Hash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa"
1186 },
1187 "v3": {
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 },
1193 ]
1194 },
1195 "info": {
1196 "defaultFrontend": "https://wasmer.io/",
1197 },
1198 }
1199 }
1200 };
1201 let response = HttpResponse {
1202 body: Some(serde_json::to_vec(&body).unwrap()),
1203 redirected: false,
1204 status: StatusCode::OK,
1205 headers: HeaderMap::new(),
1206 };
1207 let client = Arc::new(DummyClient::new(vec![response]));
1208 let registry_endpoint = BackendSource::WASMER_PROD_ENDPOINT.parse().unwrap();
1209 let request = PackageSource::from_str("wasmer/python@4.0.0").unwrap();
1210 let temp = tempfile::tempdir().unwrap();
1211 let source = BackendSource::new(registry_endpoint, client.clone())
1212 .with_local_cache(temp.path(), Duration::from_secs(0));
1213 source
1214 .cache
1215 .as_ref()
1216 .unwrap()
1217 .update("wasmer/python", &cached_value)
1218 .unwrap();
1219
1220 let summaries = source.query(&request).await.unwrap();
1221
1222 assert_eq!(summaries.len(), 1);
1223 assert_eq!(
1224 summaries[0].pkg.id.as_named().unwrap().version.to_string(),
1225 "4.0.0"
1226 );
1227 }
1228}