cargo_release/ops/
index.rs

1use crate::config::CertsSource;
2use tame_index::krate::IndexKrate;
3use tame_index::utils::flock::FileLock;
4
5#[derive(Default)]
6pub struct CratesIoIndex {
7    index: Option<RemoteIndex>,
8    cache: std::collections::HashMap<String, Option<IndexKrate>>,
9}
10
11impl CratesIoIndex {
12    #[inline]
13    pub fn new() -> Self {
14        Self {
15            index: None,
16            cache: std::collections::HashMap::new(),
17        }
18    }
19
20    /// Determines if the specified crate exists in the crates.io index
21    #[inline]
22    pub fn has_krate(
23        &mut self,
24        registry: Option<&str>,
25        name: &str,
26        certs_source: CertsSource,
27    ) -> Result<bool, crate::error::CliError> {
28        Ok(self
29            .krate(registry, name, certs_source)?
30            .map(|_| true)
31            .unwrap_or(false))
32    }
33
34    /// Determines if the specified crate version exists in the crates.io index
35    #[inline]
36    pub fn has_krate_version(
37        &mut self,
38        registry: Option<&str>,
39        name: &str,
40        version: &str,
41        certs_source: CertsSource,
42    ) -> Result<Option<bool>, crate::error::CliError> {
43        let krate = self.krate(registry, name, certs_source)?;
44        Ok(krate.map(|ik| ik.versions.iter().any(|iv| iv.version == version)))
45    }
46
47    #[inline]
48    pub fn update_krate(&mut self, registry: Option<&str>, name: &str) {
49        if registry.is_some() {
50            return;
51        }
52
53        self.cache.remove(name);
54    }
55
56    pub(crate) fn krate(
57        &mut self,
58        registry: Option<&str>,
59        name: &str,
60        certs_source: CertsSource,
61    ) -> Result<Option<IndexKrate>, crate::error::CliError> {
62        if let Some(registry) = registry {
63            log::trace!("Cannot connect to registry `{registry}`");
64            return Ok(None);
65        }
66
67        if let Some(entry) = self.cache.get(name) {
68            log::trace!("Reusing index for {name}");
69            return Ok(entry.clone());
70        }
71
72        if self.index.is_none() {
73            log::trace!("Connecting to index");
74            self.index = Some(RemoteIndex::open(certs_source)?);
75        }
76        let index = self.index.as_mut().unwrap();
77        log::trace!("Downloading index for {name}");
78        let entry = index.krate(name)?;
79        self.cache.insert(name.to_owned(), entry.clone());
80        Ok(entry)
81    }
82}
83
84pub struct RemoteIndex {
85    index: tame_index::SparseIndex,
86    client: tame_index::external::reqwest::blocking::Client,
87    lock: FileLock,
88    etags: Vec<(String, String)>,
89}
90
91impl RemoteIndex {
92    #[inline]
93    pub fn open(certs_source: CertsSource) -> Result<Self, crate::error::CliError> {
94        let index = tame_index::SparseIndex::new(tame_index::IndexLocation::new(
95            tame_index::IndexUrl::CratesIoSparse,
96        ))?;
97
98        let client = {
99            let builder = tame_index::external::reqwest::blocking::ClientBuilder::new();
100
101            let builder = match certs_source {
102                CertsSource::Webpki => builder.tls_built_in_webpki_certs(true),
103                CertsSource::Native => builder.tls_built_in_native_certs(true),
104            };
105
106            builder.build()?
107        };
108
109        let lock = FileLock::unlocked();
110
111        Ok(Self {
112            index,
113            client,
114            lock,
115            etags: Vec::new(),
116        })
117    }
118
119    pub(crate) fn krate(
120        &mut self,
121        name: &str,
122    ) -> Result<Option<IndexKrate>, crate::error::CliError> {
123        let etag = self
124            .etags
125            .iter()
126            .find_map(|(krate, etag)| (krate == name).then_some(etag.as_str()))
127            .unwrap_or("");
128
129        let krate_name = name.try_into()?;
130        let req = self
131            .index
132            .make_remote_request(krate_name, Some(etag), &self.lock)?;
133        let (
134            tame_index::external::http::request::Parts {
135                method,
136                uri,
137                version,
138                headers,
139                ..
140            },
141            _,
142        ) = req.into_parts();
143        let mut req = self.client.request(method, uri.to_string());
144        req = req.version(version);
145        req = req.headers(headers);
146        let res = self.client.execute(req.build()?)?;
147
148        // Grab the etag if it exists for future requests
149        if let Some(etag) = res
150            .headers()
151            .get(tame_index::external::reqwest::header::ETAG)
152            && let Ok(etag) = etag.to_str()
153        {
154            if let Some(i) = self.etags.iter().position(|(krate, _)| krate == name) {
155                etag.clone_into(&mut self.etags[i].1);
156            } else {
157                self.etags.push((name.to_owned(), etag.to_owned()));
158            }
159        }
160
161        let mut builder = tame_index::external::http::Response::builder()
162            .status(res.status())
163            .version(res.version());
164
165        builder
166            .headers_mut()
167            .unwrap()
168            .extend(res.headers().iter().map(|(k, v)| (k.clone(), v.clone())));
169
170        let body = res.bytes()?;
171        let response = builder
172            .body(body.to_vec())
173            .map_err(|e| tame_index::Error::from(tame_index::error::HttpError::from(e)))?;
174
175        self.index
176            .parse_remote_response(krate_name, response, false, &self.lock)
177            .map_err(Into::into)
178    }
179}