wasm_pkg_client/caching/mod.rs
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
use std::future::Future;
use std::sync::Arc;
use wasm_pkg_common::{
digest::ContentDigest,
package::{PackageRef, Version},
Error,
};
use crate::{Client, ContentStream, Release, VersionInfo};
mod file;
pub use file::FileCache;
/// A trait for a cache of data.
pub trait Cache {
/// Puts the data with the given hash into the cache
fn put_data(
&self,
digest: ContentDigest,
data: ContentStream,
) -> impl Future<Output = Result<(), Error>> + Send;
/// Gets the data with the given hash from the cache. Returns None if the data is not in the cache.
fn get_data(
&self,
digest: &ContentDigest,
) -> impl Future<Output = Result<Option<ContentStream>, Error>> + Send;
/// Puts the release data into the cache.
fn put_release(
&self,
package: &PackageRef,
release: &Release,
) -> impl Future<Output = Result<(), Error>> + Send;
/// Gets the release data from the cache. Returns None if the data is not in the cache.
fn get_release(
&self,
package: &PackageRef,
version: &Version,
) -> impl Future<Output = Result<Option<Release>, Error>> + Send;
}
/// A client that caches response data using the given cache implementation. Can be used without an
/// underlying client to be used as a read-only cache.
pub struct CachingClient<T> {
client: Option<Client>,
cache: Arc<T>,
}
impl<T: Cache> Clone for CachingClient<T> {
fn clone(&self) -> Self {
Self {
client: self.client.clone(),
cache: self.cache.clone(),
}
}
}
impl<T: Cache> CachingClient<T> {
/// Creates a new caching client from the given client and cache implementation. If no client is
/// given, the client will be in offline or read-only mode, meaning it will only be able to return
/// things that are already in the cache.
pub fn new(client: Option<Client>, cache: T) -> Self {
Self {
client,
cache: Arc::new(cache),
}
}
/// Returns whether or not the client is in read-only mode.
pub fn is_readonly(&self) -> bool {
self.client.is_none()
}
/// Returns a list of all package [`VersionInfo`]s available for the given package. This will
/// always fail if no client was provided.
pub async fn list_all_versions(&self, package: &PackageRef) -> Result<Vec<VersionInfo>, Error> {
let client = self.client()?;
client.list_all_versions(package).await
}
/// Returns a [`Release`] for the given package version.
pub async fn get_release(
&self,
package: &PackageRef,
version: &Version,
) -> Result<Release, Error> {
if let Some(data) = self.cache.get_release(package, version).await? {
return Ok(data);
}
let client = self.client()?;
let release = client.get_release(package, version).await?;
self.cache.put_release(package, &release).await?;
Ok(release)
}
/// Returns a [`ContentStream`] of content chunks. If the data is in the cache, it will be returned,
/// otherwise it will be fetched from an upstream registry and then cached. This is the same as
/// [`Client::stream_content`] but named differently to avoid confusion when trying to use this
/// as a normal [`Client`].
pub async fn get_content(
&self,
package: &PackageRef,
release: &Release,
) -> Result<ContentStream, Error> {
if let Some(data) = self.cache.get_data(&release.content_digest).await? {
return Ok(data);
}
let client = self.client()?;
let stream = client.stream_content(package, release).await?;
self.cache
.put_data(release.content_digest.clone(), stream)
.await?;
self.cache
.get_data(&release.content_digest)
.await?
.ok_or_else(|| {
Error::CacheError(anyhow::anyhow!(
"Cached data was deleted after putting the data in cache"
))
})
}
/// Returns a reference to the underlying client. Returns an error if the client is in read-only
/// mode.
///
/// Please note that using the client directly will bypass the cache.
pub fn client(&self) -> Result<&Client, Error> {
self.client
.as_ref()
.ok_or_else(|| Error::CacheError(anyhow::anyhow!("Client is in read only mode")))
}
/// Consumes the caching client and returns the underlying client. Returns an error if the
/// client is in read-only mode.
pub fn into_client(self) -> Result<Client, Error> {
self.client
.ok_or_else(|| Error::CacheError(anyhow::anyhow!("Client is in read only mode")))
}
}