//! Generates the Git folder/file tree that's returned back to the user //! containing the config & crate manifests. Only contains crates that //! the user has access to. use std::{collections::BTreeMap, sync::Arc}; use arrayvec::ArrayVec; use bytes::Bytes; use chartered_db::crates::Crate; use packfile::high_level::GitRepository; use ustr::ustr; #[derive(serde::Serialize)] pub struct CrateFileEntry<'a> { #[serde(flatten)] inner: &'a chartered_types::cargo::CrateVersion<'a>, cksum: &'a str, yanked: bool, } pub struct Tree { crates: BTreeMap, Bytes>, } impl Tree { /// Grabs all the crates that the user has access to and writes out the manifests to /// `self.crates`. pub async fn build(db: chartered_db::ConnectionPool, user_id: i32, org_name: String) -> Self { let mut crates = BTreeMap::new(); for (crate_def, versions) in Crate::list_with_versions(db, user_id, org_name) .await .unwrap() { // the manifest we'll be returning to the user let mut file = String::new(); // loop over all versions for the crate, serialising each version to json // and writing them to the manifest split by newline. for version in versions { let cksum = version.checksum.clone(); let yanked = version.yanked; let version = version.into_cargo_format(&crate_def); let entry = CrateFileEntry { inner: &version, cksum: &cksum, yanked, }; file.push_str(&serde_json::to_string(&entry).unwrap()); file.push('\n'); } // insert the crate into `self.crates` crates.insert(crate_def.name.into(), file.into()); } Self { crates } } /// Writes all the crate manifests from `self.crates` out to the given `GitRepository`. pub fn write_to_packfile(&self, repo: &mut GitRepository) -> Result<(), anyhow::Error> { for (name, content) in &self.crates { let crate_folder = get_crate_folder(name); repo.insert(&crate_folder, name.clone(), content.clone())?; } Ok(()) } } /// Crates with a total of 1, 2 or 3 characters in the same are written out to directories named /// 1, 2 or 3 respectively as per the cargo spec. Anything else we'll build out a normal tree for /// using the frist four characters of the crate name, 2 for the first directory and the other 2 /// for the second. fn get_crate_folder(crate_name: &str) -> ArrayVec<&'static str, 2> { let mut folders = ArrayVec::new(); match crate_name.len() { 0 => {} 1 => folders.push("1"), 2 => folders.push("2"), 3 => { folders.push("3"); folders.push(ustr(&crate_name[..1]).as_str()); } _ => { folders.push(ustr(&crate_name[..2]).as_str()); folders.push(ustr(&crate_name[2..4]).as_str()); } } folders } #[cfg(test)] mod test { #[test] fn get_crate_folder() { let folder = super::get_crate_folder(""); let mut folder = folder.iter(); assert_eq!(folder.next(), None); let folder = super::get_crate_folder("a"); let mut folder = folder.iter(); assert_eq!(folder.next(), Some(&"1")); assert_eq!(folder.next(), None); let folder = super::get_crate_folder("ab"); let mut folder = folder.iter(); assert_eq!(folder.next(), Some(&"2")); assert_eq!(folder.next(), None); let folder = super::get_crate_folder("abc"); let mut folder = folder.iter(); assert_eq!(folder.next(), Some(&"3")); assert_eq!(folder.next(), Some(&"a")); let folder = super::get_crate_folder("abcd"); let mut folder = folder.iter(); assert_eq!(folder.next(), Some(&"ab")); assert_eq!(folder.next(), Some(&"cd")); } }