wasm_pkg_client/caching/
mod.rs

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