cargo_release/ops/
index.rs1use 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 #[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 #[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 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}