use crate::{Metadata, Result, Url};
use async_std::path::PathBuf;
mod file;
pub(crate) use file::IndexFile;
mod config;
pub(crate) use config::Config;
mod tree;
pub use tree::{Builder as TreeBuilder, Tree};
mod git;
use git::Identity;
pub use git::Repository;
pub struct Index {
tree: Tree,
repo: Repository,
}
pub struct Builder<'a> {
tree_builder: TreeBuilder,
root: PathBuf,
origin: Option<Url>,
identity: Option<Identity<'a>>,
}
impl<'a> Builder<'a> {
pub fn api(mut self, api: Url) -> Self {
self.tree_builder = self.tree_builder.api(api);
self
}
pub fn origin(mut self, remote: Url) -> Self {
self.origin = Some(remote);
self
}
pub fn allowed_registry(mut self, registry: Url) -> Self {
self.tree_builder = self.tree_builder.allowed_registry(registry);
self
}
pub fn allow_crates_io(mut self) -> Self {
self.tree_builder = self.tree_builder.allow_crates_io();
self
}
pub fn identity(mut self, username: &'a str, email: &'a str) -> Self {
self.identity = Some(Identity { username, email });
self
}
pub async fn build(self) -> Result<Index> {
let tree = self.tree_builder.build().await?;
let repo = Repository::init(self.root)?;
if let Some(url) = self.origin {
repo.add_origin(&url)?;
}
if let Some(identity) = self.identity {
repo.set_username(identity.username)?;
repo.set_email(identity.email)?;
}
repo.create_initial_commit()?;
let index = Index { tree, repo };
Ok(index)
}
}
impl Index {
pub fn initialise<'a>(root: impl Into<PathBuf>, download: impl Into<String>) -> Builder<'a> {
let root = root.into();
let tree_builder = Tree::initialise(&root, download);
let origin = None;
let identity = None;
Builder {
tree_builder,
root,
origin,
identity,
}
}
pub async fn open(root: impl Into<PathBuf>) -> Result<Self> {
let root = root.into();
let tree = Tree::open(&root).await?;
let repo = Repository::open(&root)?;
Ok(Self { tree, repo })
}
pub async fn insert(&mut self, crate_metadata: Metadata) -> Result<()> {
let commit_message = format!(
"updating crate `{}#{}`",
crate_metadata.name(),
crate_metadata.version()
);
self.tree.insert(crate_metadata).await?;
self.repo.add_all()?; self.repo.commit(commit_message)?;
Ok(())
}
pub fn root(&self) -> &PathBuf {
self.tree.root()
}
pub fn download(&self) -> &String {
self.tree.download()
}
pub fn api(&self) -> &Option<Url> {
self.tree.api()
}
pub fn allowed_registries(&self) -> &Vec<Url> {
self.tree.allowed_registries()
}
pub fn into_parts(self) -> (Tree, Repository) {
(self.tree, self.repo)
}
}
#[cfg(test)]
mod tests {
use super::Index;
use crate::{index::Metadata, Url};
use async_std::path::PathBuf;
use semver::Version;
use test_case::test_case;
#[async_std::test]
async fn get_and_set() {
let temp_dir = tempfile::tempdir().unwrap();
let root: PathBuf = temp_dir.path().into();
let origin = Url::parse("https://my-git-server.com/").unwrap();
let api = Url::parse("https://my-crates-server.com/").unwrap();
let download = "https://my-crates-server.com/api/v1/crates/{crate}/{version}/download";
let index = Index::initialise(root.clone(), download)
.origin(origin)
.api(api.clone())
.allowed_registry(Url::parse("https://my-intranet:8080/index").unwrap())
.allow_crates_io()
.identity("dummy username", "dummy@email.com")
.build()
.await
.unwrap();
let expected_allowed_registries = vec![
Url::parse("https://my-intranet:8080/index").unwrap(),
Url::parse("https://github.com/rust-lang/crates.io-index").unwrap(),
];
assert_eq!(index.root().as_path(), &root);
assert_eq!(index.download(), download);
assert_eq!(index.api(), &Some(api));
assert_eq!(index.allowed_registries(), &expected_allowed_registries);
}
#[test_case("Some-Name", "0.1.1" ; "when used properly")]
#[test_case("Some_Name", "0.1.1" => panics "invalid" ; "when crate names differ only by hypens and underscores")]
#[test_case("some_name", "0.1.1" => panics "invalid" ; "when crate names differ only by capitalisation")]
#[test_case("other-name", "0.1.1" ; "when inserting a different crate")]
#[test_case("Some-Name", "0.1.0" => panics "invalid"; "when version is the same")]
#[test_case("Some-Name", "0.0.1" => panics "invalid"; "when version is lower")]
#[test_case("nul", "0.0.1" => panics "invalid"; "when name is reserved word")]
#[test_case("-start-with-hyphen", "0.0.1" => panics "invalid"; "when name starts with non-alphabetical character")]
fn insert(name: &str, version: &str) {
async_std::task::block_on(async move {
let temp_dir = tempfile::tempdir().unwrap();
let root = temp_dir.path();
let download = "https://my-crates-server.com/api/v1/crates/{crate}/{version}/download";
let origin = Url::parse("https://my-git-server.com/").unwrap();
let initial_metadata = metadata("Some-Name", "0.1.0");
let mut index = Index::initialise(root, download)
.origin(origin)
.identity("dummy username", "dummy@email.com")
.build()
.await
.expect("couldn't create index");
index
.insert(initial_metadata)
.await
.expect("couldn't insert initial metadata");
let new_metadata = metadata(name, version);
index.insert(new_metadata).await.expect("invalid");
});
}
fn metadata(name: &str, version: &str) -> Metadata {
Metadata::new(name, Version::parse(version).unwrap(), "checksum")
}
}