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")))
    }
}