mod release;
pub mod source;
use std::collections::HashMap;
use anyhow::anyhow;
use bytes::Bytes;
use futures_util::stream::BoxStream;
use wasm_pkg_common::{
metadata::RegistryMetadata,
package::{PackageRef, Version},
registry::Registry,
Error,
};
use crate::source::{
local::LocalSource, oci::OciSource, warg::WargSource, PackageSource, VersionInfo,
};
pub use oci_distribution::client as oci_client;
pub use wasm_pkg_common::config::Config;
pub use crate::release::{ContentDigest, Release};
pub struct Client {
config: Config,
sources: HashMap<Registry, Box<dyn PackageSource>>,
}
impl Client {
pub fn new(config: Config) -> Self {
Self {
config,
sources: Default::default(),
}
}
pub fn with_global_defaults() -> Result<Self, Error> {
let config = Config::global_defaults()?;
Ok(Self::new(config))
}
pub async fn list_all_versions(
&mut self,
package: &PackageRef,
) -> Result<Vec<VersionInfo>, Error> {
let source = self.resolve_source(package).await?;
source.list_all_versions(package).await
}
pub async fn get_release(
&mut self,
package: &PackageRef,
version: &Version,
) -> Result<Release, Error> {
let source = self.resolve_source(package).await?;
source.get_release(package, version).await
}
pub async fn stream_content(
&mut self,
package: &PackageRef,
release: &Release,
) -> Result<BoxStream<Result<Bytes, Error>>, Error> {
let source = self.resolve_source(package).await?;
source.stream_content(package, release).await
}
async fn resolve_source(
&mut self,
package: &PackageRef,
) -> Result<&mut dyn PackageSource, Error> {
let registry = self
.config
.resolve_registry(package)
.ok_or_else(|| Error::NoRegistryForNamespace(package.namespace().clone()))?
.to_owned();
if !self.sources.contains_key(®istry) {
let registry_config = self
.config
.registry_config(®istry)
.cloned()
.unwrap_or_default();
let should_fetch_meta = registry_config.backend_type() != Some("local");
let registry_meta = if should_fetch_meta {
RegistryMetadata::fetch_or_default(®istry).await
} else {
RegistryMetadata::default()
};
let backend_type = match registry_config.backend_type() {
Some(backend_type) => Some(backend_type),
None => {
let preferred_protocol = registry_meta.preferred_protocol();
if preferred_protocol == Some("local") {
return Err(Error::InvalidRegistryMetadata(anyhow!(
"registry metadata with 'local' protocol not allowed"
)));
}
preferred_protocol
}
}
.unwrap_or("oci");
tracing::debug!(?backend_type, "Resolved backend type");
let source: Box<dyn PackageSource> = match backend_type {
"local" => Box::new(LocalSource::new(registry_config)?),
"oci" => Box::new(OciSource::new(®istry, ®istry_config, ®istry_meta)?),
"warg" => {
Box::new(WargSource::new(®istry, ®istry_config, ®istry_meta).await?)
}
other => {
return Err(Error::InvalidConfig(anyhow!(
"unknown backend type {other:?}"
)));
}
};
self.sources.insert(registry.clone(), source);
}
Ok(self.sources.get_mut(®istry).unwrap().as_mut())
}
}