1pub mod cache;
5#[cfg(all(feature = "__git", feature = "sparse"))]
6mod combo;
7#[allow(missing_docs)]
8pub mod git;
9#[cfg(feature = "__git")]
10pub(crate) mod git_remote;
11#[cfg(feature = "local")]
12pub mod local;
13pub mod location;
14#[allow(missing_docs)]
15pub mod sparse;
16#[cfg(feature = "sparse")]
17mod sparse_remote;
18
19pub use cache::IndexCache;
20#[cfg(all(feature = "__git", feature = "sparse"))]
21pub use combo::ComboIndex;
22pub use git::GitIndex;
23#[cfg(feature = "__git")]
24pub use git_remote::RemoteGitIndex;
25#[cfg(feature = "local")]
26pub use local::LocalRegistry;
27pub use location::{IndexLocation, IndexPath, IndexUrl};
28pub use sparse::SparseIndex;
29#[cfg(feature = "sparse")]
30pub use sparse_remote::{AsyncRemoteSparseIndex, RemoteSparseIndex};
31
32pub use crate::utils::flock::FileLock;
33
34#[derive(Eq, PartialEq, PartialOrd, Ord, Clone, Debug, serde::Deserialize, serde::Serialize)]
36pub struct IndexConfig {
37 pub dl: String,
39 pub api: Option<String>,
41}
42
43impl IndexConfig {
44 pub fn download_url(&self, name: crate::KrateName<'_>, version: &str) -> String {
49 if self.dl == "https://crates.io/api/v1/crates" {
56 return format!("https://static.crates.io/crates/{name}/{name}-{version}.crate");
57 }
58
59 let mut dl = self.dl.clone();
60
61 if dl.contains('{') {
62 while let Some(start) = dl.find("{crate}") {
63 dl.replace_range(start..start + 7, name.0);
64 }
65
66 while let Some(start) = dl.find("{version}") {
67 dl.replace_range(start..start + 9, version);
68 }
69
70 if dl.contains("{prefix}") || dl.contains("{lowerprefix}") {
71 let mut prefix = String::with_capacity(6);
72 name.prefix(&mut prefix, '/');
73
74 while let Some(start) = dl.find("{prefix}") {
75 dl.replace_range(start..start + 8, &prefix);
76 }
77
78 if dl.contains("{lowerprefix}") {
79 prefix.make_ascii_lowercase();
80
81 while let Some(start) = dl.find("{lowerprefix}") {
82 dl.replace_range(start..start + 13, &prefix);
83 }
84 }
85 }
86 } else {
87 if !dl.ends_with('/') {
89 dl.push('/');
90 }
91
92 dl.push_str(name.0);
93 dl.push('/');
94 dl.push_str(version);
95 dl.push('/');
96 dl.push_str("download");
97 }
98
99 dl
100 }
101}
102
103use crate::Error;
104
105#[non_exhaustive]
107pub enum ComboIndexCache {
108 Git(GitIndex),
110 Sparse(SparseIndex),
112 #[cfg(feature = "local")]
114 Local(LocalRegistry),
115}
116
117impl ComboIndexCache {
118 #[inline]
120 pub fn cached_krate(
121 &self,
122 name: crate::KrateName<'_>,
123 lock: &FileLock,
124 ) -> Result<Option<crate::IndexKrate>, Error> {
125 match self {
126 Self::Git(index) => index.cached_krate(name, lock),
127 Self::Sparse(index) => index.cached_krate(name, lock),
128 #[cfg(feature = "local")]
129 Self::Local(lr) => lr.cached_krate(name, lock),
130 }
131 }
132
133 pub fn cache_path(&self, name: crate::KrateName<'_>) -> crate::PathBuf {
135 match self {
136 Self::Git(index) => index.cache.cache_path(name),
137 Self::Sparse(index) => index.cache().cache_path(name),
138 #[cfg(feature = "local")]
139 Self::Local(lr) => lr.krate_path(name),
140 }
141 }
142
143 pub fn new(il: IndexLocation<'_>) -> Result<Self, Error> {
148 #[cfg(feature = "local")]
149 {
150 if let IndexUrl::Local(path) = il.url {
151 return Ok(Self::Local(LocalRegistry::open(path.into(), true)?));
152 }
153 }
154
155 let index = if il.url.is_sparse() {
156 let sparse = SparseIndex::new(il)?;
157 Self::Sparse(sparse)
158 } else {
159 let git = GitIndex::new(il)?;
160 Self::Git(git)
161 };
162
163 Ok(index)
164 }
165}
166
167impl From<SparseIndex> for ComboIndexCache {
168 #[inline]
169 fn from(si: SparseIndex) -> Self {
170 Self::Sparse(si)
171 }
172}
173
174impl From<GitIndex> for ComboIndexCache {
175 #[inline]
176 fn from(gi: GitIndex) -> Self {
177 Self::Git(gi)
178 }
179}
180
181#[cfg(test)]
182mod test {
183 use super::IndexConfig;
184 use crate::kn;
185
186 #[test]
188 fn download_url_crates_io() {
189 let crates_io = IndexConfig {
190 dl: "https://crates.io/api/v1/crates".into(),
191 api: Some("https://crates.io".into()),
192 };
193
194 assert_eq!(
195 crates_io.download_url(kn!("a"), "1.0.0"),
196 "https://static.crates.io/crates/a/a-1.0.0.crate"
197 );
198 assert_eq!(
199 crates_io.download_url(kn!("aB"), "0.1.0"),
200 "https://static.crates.io/crates/aB/aB-0.1.0.crate"
201 );
202 assert_eq!(
203 crates_io.download_url(kn!("aBc"), "0.1.0"),
204 "https://static.crates.io/crates/aBc/aBc-0.1.0.crate"
205 );
206 assert_eq!(
207 crates_io.download_url(kn!("aBc-123"), "0.1.0"),
208 "https://static.crates.io/crates/aBc-123/aBc-123-0.1.0.crate"
209 );
210 }
211
212 #[test]
214 fn download_url_non_crates_io() {
215 let ic = IndexConfig {
216 dl: "https://dl.cloudsmith.io/public/embark/deny/cargo/{crate}-{version}.crate".into(),
217 api: Some("https://cargo.cloudsmith.io/embark/deny".into()),
218 };
219
220 assert_eq!(
221 ic.download_url(kn!("a"), "1.0.0"),
222 "https://dl.cloudsmith.io/public/embark/deny/cargo/a-1.0.0.crate"
223 );
224 assert_eq!(
225 ic.download_url(kn!("aB"), "0.1.0"),
226 "https://dl.cloudsmith.io/public/embark/deny/cargo/aB-0.1.0.crate"
227 );
228 assert_eq!(
229 ic.download_url(kn!("aBc"), "0.1.0"),
230 "https://dl.cloudsmith.io/public/embark/deny/cargo/aBc-0.1.0.crate"
231 );
232 assert_eq!(
233 ic.download_url(kn!("aBc-123"), "0.1.0"),
234 "https://dl.cloudsmith.io/public/embark/deny/cargo/aBc-123-0.1.0.crate"
235 );
236 }
237
238 #[test]
241 fn download_url_complex() {
242 let ic = IndexConfig {
243 dl: "https://complex.io/ohhi/embark/rust/cargo/{lowerprefix}/{crate}/{crate}/{prefix}-{version}".into(),
244 api: None,
245 };
246
247 assert_eq!(
248 ic.download_url(kn!("a"), "1.0.0"),
249 "https://complex.io/ohhi/embark/rust/cargo/1/a/a/1-1.0.0"
250 );
251 assert_eq!(
252 ic.download_url(kn!("aB"), "0.1.0"),
253 "https://complex.io/ohhi/embark/rust/cargo/2/aB/aB/2-0.1.0"
254 );
255 assert_eq!(
256 ic.download_url(kn!("ABc"), "0.1.0"),
257 "https://complex.io/ohhi/embark/rust/cargo/3/a/ABc/ABc/3/A-0.1.0"
258 );
259 assert_eq!(
260 ic.download_url(kn!("aBc-123"), "0.1.0"),
261 "https://complex.io/ohhi/embark/rust/cargo/ab/c-/aBc-123/aBc-123/aB/c--0.1.0"
262 );
263 }
264}