Skip to main content

wasm_pkg_client/caching/
mod.rs

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