Skip to main content

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 has_krate_version_req(
49        &mut self,
50        registry: Option<&str>,
51        name: &str,
52        version_req: &semver::VersionReq,
53        certs_source: CertsSource,
54    ) -> Result<Option<bool>, crate::error::CliError> {
55        let krate = self.krate(registry, name, certs_source)?;
56        Ok(krate.map(|ik| {
57            ik.versions.iter().any(|iv| {
58                iv.version
59                    .parse::<semver::Version>()
60                    .is_ok_and(|version| version_req.matches(&version))
61            })
62        }))
63    }
64
65    #[inline]
66    pub fn update_krate(&mut self, registry: Option<&str>, name: &str) {
67        if registry.is_some() {
68            return;
69        }
70
71        self.cache.remove(name);
72    }
73
74    pub(crate) fn krate(
75        &mut self,
76        registry: Option<&str>,
77        name: &str,
78        certs_source: CertsSource,
79    ) -> Result<Option<IndexKrate>, crate::error::CliError> {
80        if let Some(registry) = registry {
81            log::trace!("Cannot connect to registry `{registry}`");
82            return Ok(None);
83        }
84
85        if let Some(entry) = self.cache.get(name) {
86            log::trace!("Reusing index for {name}");
87            return Ok(entry.clone());
88        }
89
90        if self.index.is_none() {
91            log::trace!("Connecting to index");
92            self.index = Some(RemoteIndex::open(certs_source)?);
93        }
94        let index = self.index.as_mut().unwrap();
95        log::trace!("Downloading index for {name}");
96        let entry = index.krate(name)?;
97        self.cache.insert(name.to_owned(), entry.clone());
98        Ok(entry)
99    }
100}
101
102pub struct RemoteIndex {
103    index: tame_index::SparseIndex,
104    client: tame_index::external::reqwest::blocking::Client,
105    lock: FileLock,
106    etags: Vec<(String, String)>,
107}
108
109impl RemoteIndex {
110    #[inline]
111    pub fn open(certs_source: CertsSource) -> Result<Self, crate::error::CliError> {
112        let index = tame_index::SparseIndex::new(tame_index::IndexLocation::new(
113            tame_index::IndexUrl::CratesIoSparse,
114        ))?;
115
116        let client = {
117            let builder = tame_index::external::reqwest::blocking::ClientBuilder::new();
118
119            let builder = match certs_source {
120                CertsSource::Webpki => builder.tls_built_in_webpki_certs(true),
121                CertsSource::Native => builder.tls_built_in_native_certs(true),
122            };
123
124            builder.build()?
125        };
126
127        let lock = FileLock::unlocked();
128
129        Ok(Self {
130            index,
131            client,
132            lock,
133            etags: Vec::new(),
134        })
135    }
136
137    pub(crate) fn krate(
138        &mut self,
139        name: &str,
140    ) -> Result<Option<IndexKrate>, crate::error::CliError> {
141        let etag = self
142            .etags
143            .iter()
144            .find_map(|(krate, etag)| (krate == name).then_some(etag.as_str()))
145            .unwrap_or("");
146
147        let krate_name = name.try_into()?;
148        let req = self
149            .index
150            .make_remote_request(krate_name, Some(etag), &self.lock)?;
151        let (
152            tame_index::external::http::request::Parts {
153                method,
154                uri,
155                version,
156                headers,
157                ..
158            },
159            _,
160        ) = req.into_parts();
161        let mut req = self.client.request(method, uri.to_string());
162        req = req.version(version);
163        req = req.headers(headers);
164        let res = self.client.execute(req.build()?)?;
165
166        // Grab the etag if it exists for future requests
167        if let Some(etag) = res
168            .headers()
169            .get(tame_index::external::reqwest::header::ETAG)
170            && let Ok(etag) = etag.to_str()
171        {
172            if let Some(i) = self.etags.iter().position(|(krate, _)| krate == name) {
173                etag.clone_into(&mut self.etags[i].1);
174            } else {
175                self.etags.push((name.to_owned(), etag.to_owned()));
176            }
177        }
178
179        let mut builder = tame_index::external::http::Response::builder()
180            .status(res.status())
181            .version(res.version());
182
183        builder
184            .headers_mut()
185            .unwrap()
186            .extend(res.headers().iter().map(|(k, v)| (k.clone(), v.clone())));
187
188        let body = res.bytes()?;
189        let response = builder
190            .body(body.to_vec())
191            .map_err(|e| tame_index::Error::from(tame_index::error::HttpError::from(e)))?;
192
193        self.index
194            .parse_remote_response(krate_name, response, false, &self.lock)
195            .map_err(Into::into)
196    }
197}