1use crate::{Metadata, Result, Url};
7use async_std::path::PathBuf;
8
9mod file;
10pub(crate) use file::IndexFile;
11
12mod config;
13pub(crate) use config::Config;
14
15mod tree;
16pub use tree::{Builder as TreeBuilder, Tree};
17
18mod git;
19
20use git::Identity;
21pub use git::Repository;
22
23pub struct Index {
33 tree: Tree,
34 repo: Repository,
35}
36
37pub struct Builder<'a> {
39 tree_builder: TreeBuilder,
40 root: PathBuf,
41 origin: Option<Url>,
42 identity: Option<Identity<'a>>,
43}
44
45impl<'a> Builder<'a> {
46 pub fn api(mut self, api: Url) -> Self {
51 self.tree_builder = self.tree_builder.api(api);
52 self
53 }
54
55 pub fn origin(mut self, remote: Url) -> Self {
57 self.origin = Some(remote);
58 self
59 }
60
61 pub fn allowed_registry(mut self, registry: Url) -> Self {
68 self.tree_builder = self.tree_builder.allowed_registry(registry);
69 self
70 }
71
72 pub fn allow_crates_io(mut self) -> Self {
76 self.tree_builder = self.tree_builder.allow_crates_io();
77 self
78 }
79
80 pub fn identity(mut self, username: &'a str, email: &'a str) -> Self {
82 self.identity = Some(Identity { username, email });
83 self
84 }
85
86 pub async fn build(self) -> Result<Index> {
93 let tree = self.tree_builder.build().await?;
94 let repo = Repository::init(self.root)?;
95
96 if let Some(url) = self.origin {
97 repo.add_origin(&url)?;
98 }
99
100 if let Some(identity) = self.identity {
101 repo.set_username(identity.username)?;
102 repo.set_email(identity.email)?;
103 }
104
105 repo.create_initial_commit()?;
106
107 let index = Index { tree, repo };
108
109 Ok(index)
110 }
111}
112
113impl Index {
114 pub fn initialise<'a>(root: impl Into<PathBuf>, download: impl Into<String>) -> Builder<'a> {
156 let root = root.into();
157 let tree_builder = Tree::initialise(&root, download);
158 let origin = None;
159 let identity = None;
160
161 Builder {
162 tree_builder,
163 root,
164 origin,
165 identity,
166 }
167 }
168
169 pub async fn open(root: impl Into<PathBuf>) -> Result<Self> {
183 let root = root.into();
184 let tree = Tree::open(&root).await?;
185 let repo = Repository::open(&root)?;
186
187 Ok(Self { tree, repo })
188 }
189
190 pub async fn insert(&mut self, crate_metadata: Metadata) -> Result<()> {
197 let commit_message = format!(
198 "updating crate `{}#{}`",
199 crate_metadata.name(),
200 crate_metadata.version()
201 );
202 self.tree.insert(crate_metadata).await?;
203 self.repo.add_all()?; self.repo.commit(commit_message)?;
205 Ok(())
206 }
207
208 pub fn root(&self) -> &PathBuf {
210 self.tree.root()
211 }
212
213 pub fn download(&self) -> &String {
215 self.tree.download()
216 }
217
218 pub fn api(&self) -> &Option<Url> {
220 self.tree.api()
221 }
222
223 pub fn allowed_registries(&self) -> &Vec<Url> {
226 self.tree.allowed_registries()
227 }
228
229 pub fn into_parts(self) -> (Tree, Repository) {
231 (self.tree, self.repo)
232 }
233}
234
235#[cfg(test)]
236mod tests {
237 use super::Index;
238 use crate::{index::Metadata, Url};
239 use async_std::path::PathBuf;
240 use semver::Version;
241 use test_case::test_case;
242
243 #[async_std::test]
244 async fn get_and_set() {
245 let temp_dir = tempfile::tempdir().unwrap();
246 let root: PathBuf = temp_dir.path().into();
247 let origin = Url::parse("https://my-git-server.com/").unwrap();
248
249 let api = Url::parse("https://my-crates-server.com/").unwrap();
250
251 let download = "https://my-crates-server.com/api/v1/crates/{crate}/{version}/download";
252
253 let index = Index::initialise(root.clone(), download)
254 .origin(origin)
255 .api(api.clone())
256 .allowed_registry(Url::parse("https://my-intranet:8080/index").unwrap())
257 .allow_crates_io()
258 .identity("dummy username", "dummy@email.com")
259 .build()
260 .await
261 .unwrap();
262
263 let expected_allowed_registries = vec![
264 Url::parse("https://my-intranet:8080/index").unwrap(),
265 Url::parse("https://github.com/rust-lang/crates.io-index").unwrap(),
266 ];
267
268 assert_eq!(index.root().as_path(), &root);
269 assert_eq!(index.download(), download);
270 assert_eq!(index.api(), &Some(api));
271 assert_eq!(index.allowed_registries(), &expected_allowed_registries);
272 }
273
274 #[test_case("Some-Name", "0.1.1" ; "when used properly")]
275 #[test_case("Some_Name", "0.1.1" => panics "invalid" ; "when crate names differ only by hypens and underscores")]
276 #[test_case("some_name", "0.1.1" => panics "invalid" ; "when crate names differ only by capitalisation")]
277 #[test_case("other-name", "0.1.1" ; "when inserting a different crate")]
278 #[test_case("Some-Name", "0.1.0" => panics "invalid"; "when version is the same")]
279 #[test_case("Some-Name", "0.0.1" => panics "invalid"; "when version is lower")]
280 #[test_case("nul", "0.0.1" => panics "invalid"; "when name is reserved word")]
281 #[test_case("-start-with-hyphen", "0.0.1" => panics "invalid"; "when name starts with non-alphabetical character")]
282 fn insert(name: &str, version: &str) {
283 async_std::task::block_on(async move {
284 let temp_dir = tempfile::tempdir().unwrap();
286 let root = temp_dir.path();
287 let download = "https://my-crates-server.com/api/v1/crates/{crate}/{version}/download";
288 let origin = Url::parse("https://my-git-server.com/").unwrap();
289
290 let initial_metadata = metadata("Some-Name", "0.1.0");
291
292 let mut index = Index::initialise(root, download)
294 .origin(origin)
295 .identity("dummy username", "dummy@email.com")
296 .build()
297 .await
298 .expect("couldn't create index");
299
300 index
301 .insert(initial_metadata)
302 .await
303 .expect("couldn't insert initial metadata");
304
305 let new_metadata = metadata(name, version);
307 index.insert(new_metadata).await.expect("invalid");
308 });
309 }
310
311 fn metadata(name: &str, version: &str) -> Metadata {
312 Metadata::new(name, Version::parse(version).unwrap(), "checksum")
313 }
314}