wasm_pkg_loader/
lib.rs

1mod release;
2pub mod source;
3
4use std::collections::HashMap;
5
6use anyhow::anyhow;
7use bytes::Bytes;
8use futures_util::stream::BoxStream;
9
10use wasm_pkg_common::metadata::RegistryMetadata;
11
12use crate::source::{
13    local::LocalSource, oci::OciSource, warg::WargSource, PackageSource, VersionInfo,
14};
15
16/// Re-exported to ease configuration.
17pub use oci_distribution::client as oci_client;
18
19pub use wasm_pkg_common::{
20    config::Config,
21    package::{PackageRef, Version},
22    registry::Registry,
23    Error,
24};
25
26pub use crate::release::{ContentDigest, Release};
27
28/// A read-only registry client.
29pub struct Client {
30    config: Config,
31    sources: HashMap<Registry, Box<dyn PackageSource>>,
32}
33
34impl Client {
35    /// Returns a new client with the given [`ClientConfig`].
36    pub fn new(config: Config) -> Self {
37        Self {
38            config,
39            sources: Default::default(),
40        }
41    }
42
43    /// Returns a new client configured from default global config.
44    pub fn with_global_defaults() -> Result<Self, Error> {
45        let config = Config::global_defaults()?;
46        Ok(Self::new(config))
47    }
48
49    /// Returns a list of all package [`Version`]s available for the given package.
50    pub async fn list_all_versions(
51        &mut self,
52        package: &PackageRef,
53    ) -> Result<Vec<VersionInfo>, Error> {
54        let source = self.resolve_source(package).await?;
55        source.list_all_versions(package).await
56    }
57
58    /// Returns a [`Release`] for the given package version.
59    pub async fn get_release(
60        &mut self,
61        package: &PackageRef,
62        version: &Version,
63    ) -> Result<Release, Error> {
64        let source = self.resolve_source(package).await?;
65        source.get_release(package, version).await
66    }
67
68    /// Returns a [`BoxStream`] of content chunks. Contents are validated
69    /// against the given [`Release::content_digest`].
70    pub async fn stream_content(
71        &mut self,
72        package: &PackageRef,
73        release: &Release,
74    ) -> Result<BoxStream<Result<Bytes, Error>>, Error> {
75        let source = self.resolve_source(package).await?;
76        source.stream_content(package, release).await
77    }
78
79    async fn resolve_source(
80        &mut self,
81        package: &PackageRef,
82    ) -> Result<&mut dyn PackageSource, Error> {
83        let registry = self
84            .config
85            .resolve_registry(package)
86            .ok_or_else(|| Error::NoRegistryForNamespace(package.namespace().clone()))?
87            .to_owned();
88        if !self.sources.contains_key(&registry) {
89            let registry_config = self
90                .config
91                .registry_config(&registry)
92                .cloned()
93                .unwrap_or_default();
94
95            // Skip fetching metadata for "local" source
96            let should_fetch_meta = registry_config.backend_type() != Some("local");
97            let registry_meta = if should_fetch_meta {
98                RegistryMetadata::fetch_or_default(&registry).await
99            } else {
100                RegistryMetadata::default()
101            };
102
103            // Resolve backend type
104            let backend_type = match registry_config.backend_type() {
105                // If the local config specifies a backend type, use it
106                Some(backend_type) => Some(backend_type),
107                None => {
108                    // If the registry metadata indicates a preferred protocol, use it
109                    let preferred_protocol = registry_meta.preferred_protocol();
110                    // ...except registry metadata cannot force a local backend
111                    if preferred_protocol == Some("local") {
112                        return Err(Error::InvalidRegistryMetadata(anyhow!(
113                            "registry metadata with 'local' protocol not allowed"
114                        )));
115                    }
116                    preferred_protocol
117                }
118            }
119            // Otherwise use the default backend
120            .unwrap_or("oci");
121            tracing::debug!(?backend_type, "Resolved backend type");
122
123            let source: Box<dyn PackageSource> = match backend_type {
124                "local" => Box::new(LocalSource::new(registry_config)?),
125                "oci" => Box::new(OciSource::new(&registry, &registry_config, &registry_meta)?),
126                "warg" => {
127                    Box::new(WargSource::new(&registry, &registry_config, &registry_meta).await?)
128                }
129                other => {
130                    return Err(Error::InvalidConfig(anyhow!(
131                        "unknown backend type {other:?}"
132                    )));
133                }
134            };
135            self.sources.insert(registry.clone(), source);
136        }
137        Ok(self.sources.get_mut(&registry).unwrap().as_mut())
138    }
139}