use { crate::{ client::Client, crate_handler::{Dependency, Error, PackageMetaData, Program, UnpackedCrate}, response_builder, }, log::info, serde::{Deserialize, Serialize}, std::{ collections::BTreeMap, sync::{Arc, RwLock}, }, }; #[derive(Debug, Default, Deserialize, Serialize)] struct RegistryConfig { dl: String, api: Option, } pub(crate) struct RegistryIndex { pub(crate) index_root: String, config: String, index: RwLock>, } #[derive(Serialize)] pub(crate) struct IndexEntryDep { pub name: String, pub req: String, pub features: Vec, pub optional: bool, pub default_features: bool, pub target: Option, pub kind: String, pub registry: Option, pub package: Option, } impl From for IndexEntryDep { fn from(v: Dependency) -> Self { IndexEntryDep { name: v.name, req: v.version_req, features: v.features, optional: v.optional, default_features: v.default_features, target: v.target, kind: serde_json::to_string(&v.kind).expect("Failed to stringify dep kind"), registry: v.registry, package: None, } } } #[derive(Serialize)] pub(crate) struct IndexEntry { pub name: String, pub vers: String, pub deps: Vec, pub cksum: String, pub features: BTreeMap>, pub yanked: bool, pub links: Option, } impl From for IndexEntry { fn from(v: PackageMetaData) -> Self { IndexEntry { name: v.name, vers: v.vers, deps: v.deps.into_iter().map(|v| v.into()).collect(), cksum: String::new(), features: v.features, yanked: false, links: v.links, } } } impl RegistryIndex { pub(crate) fn new(root: &str, server_url: &str) -> Self { let registry_config = RegistryConfig { dl: format!("{}/api/v1/crates", server_url), api: Some(server_url.to_string()), }; let config = serde_json::to_string(®istry_config).expect("Failed to create registry config"); info!("Registry index is available at {}{}/", server_url, root); Self { index_root: root.to_string(), config, index: RwLock::new(BTreeMap::new()), } } pub(crate) fn handler( &self, request: hyper::Request, client: Arc, ) -> hyper::Response { let path = request.uri().path(); let expected_root = self.index_root.as_str(); if !path.starts_with(expected_root) { return response_builder::error_response( hyper::StatusCode::BAD_REQUEST, "Invalid path for index", ); } let Some((_, path)) = path.split_once(expected_root) else { return response_builder::error_response( hyper::StatusCode::BAD_REQUEST, "Invalid path for index", ); }; if path == "/config.json" { return response_builder::success_response_str(&self.config); } self.handle_crate_lookup_request(path, client) } pub(crate) fn insert_entry(&self, entry: IndexEntry) -> Result<(), Error> { let mut write_index = self .index .write() .map_err(|e| format!("Failed to lock the index for writing: {}", e))?; info!("Inserting {}-{} in registry index", entry.name, entry.vers); write_index.insert(entry.name.clone(), entry); Ok(()) } fn get_crate_name_from_path(path: &str) -> Option<&str> { let (path, crate_name) = path.rsplit_once('/')?; // The index for deployed crates follow the path naming described here // https://doc.rust-lang.org/cargo/reference/registry-index.html#index-files match crate_name.len() { 0 => false, 1 => path == "/1", 2 => path == "/2", 3 => { let first_char = crate_name.chars().next()?; path == format!("/3/{}", first_char) } _ => { let (first_two_char, rest) = crate_name.split_at(2); let (next_two_char, _) = rest.split_at(2); path == format!("/{}/{}", first_two_char, next_two_char) } } .then_some(crate_name) } fn handle_crate_lookup_request( &self, path: &str, client: Arc, ) -> hyper::Response { let Some(crate_name) = Self::get_crate_name_from_path(path) else { return response_builder::error_response( hyper::StatusCode::BAD_REQUEST, "Invalid path for the request", ); }; info!("Looking up index for {:?}", crate_name); let Ok(read_index) = self.index.read() else { return response_builder::error_response( hyper::StatusCode::INTERNAL_SERVER_ERROR, "Internal error. Failed to lock the index for reading", ); }; let response = if let Some(entry) = read_index.get(crate_name) { Some(serde_json::to_string(entry)) } else { // The index currently doesn't contain the program entry. // Fetch the program information from the network using RPC client. Program::crate_name_to_program_id(crate_name) .and_then(|id| UnpackedCrate::fetch_index(id, client).ok()) .map(|entry| serde_json::to_string(&entry)) }; let Some(Ok(response)) = response else { return response_builder::error_response( hyper::StatusCode::INTERNAL_SERVER_ERROR, "Internal error. index entry is corrupted", ); }; response_builder::success_response_str(response.as_str()) } } #[cfg(test)] mod test { use super::*; #[test] fn test_get_crate_name_from_path() { assert_eq!(RegistryIndex::get_crate_name_from_path(""), None); assert_eq!(RegistryIndex::get_crate_name_from_path("/"), None); // Single character crate name assert_eq!(RegistryIndex::get_crate_name_from_path("/a"), None); assert_eq!(RegistryIndex::get_crate_name_from_path("/1/a"), Some("a")); assert_eq!(RegistryIndex::get_crate_name_from_path("/2/a"), None); assert_eq!(RegistryIndex::get_crate_name_from_path("/a/a"), None); // Two character crate name assert_eq!(RegistryIndex::get_crate_name_from_path("/ab"), None); assert_eq!(RegistryIndex::get_crate_name_from_path("/1/ab"), None); assert_eq!(RegistryIndex::get_crate_name_from_path("/2/ab"), Some("ab")); assert_eq!(RegistryIndex::get_crate_name_from_path("/3/ab"), None); assert_eq!(RegistryIndex::get_crate_name_from_path("/ab/ab"), None); // Three character crate name assert_eq!(RegistryIndex::get_crate_name_from_path("/abc"), None); assert_eq!(RegistryIndex::get_crate_name_from_path("/1/abc"), None); assert_eq!(RegistryIndex::get_crate_name_from_path("/2/abc"), None); assert_eq!(RegistryIndex::get_crate_name_from_path("/3/abc"), None); assert_eq!( RegistryIndex::get_crate_name_from_path("/3/a/abc"), Some("abc") ); assert_eq!(RegistryIndex::get_crate_name_from_path("/ab/abc"), None); assert_eq!(RegistryIndex::get_crate_name_from_path("/ab/c/abc"), None); // Four character crate name assert_eq!(RegistryIndex::get_crate_name_from_path("/abcd"), None); assert_eq!(RegistryIndex::get_crate_name_from_path("/1/abcd"), None); assert_eq!(RegistryIndex::get_crate_name_from_path("/2/abcd"), None); assert_eq!(RegistryIndex::get_crate_name_from_path("/3/abcd"), None); assert_eq!(RegistryIndex::get_crate_name_from_path("/3/a/abcd"), None); assert_eq!(RegistryIndex::get_crate_name_from_path("/4/abcd"), None); assert_eq!( RegistryIndex::get_crate_name_from_path("/ab/cd/abcd"), Some("abcd") ); assert_eq!(RegistryIndex::get_crate_name_from_path("/ab/cd/abc"), None); // More character crate name assert_eq!(RegistryIndex::get_crate_name_from_path("/abcdefgh"), None); assert_eq!(RegistryIndex::get_crate_name_from_path("/1/abcdefgh"), None); assert_eq!(RegistryIndex::get_crate_name_from_path("/2/abcdefgh"), None); assert_eq!(RegistryIndex::get_crate_name_from_path("/3/abcdefgh"), None); assert_eq!( RegistryIndex::get_crate_name_from_path("/3/a/abcdefgh"), None ); assert_eq!(RegistryIndex::get_crate_name_from_path("/4/abcdefgh"), None); assert_eq!( RegistryIndex::get_crate_name_from_path("/ab/cd/abcdefgh"), Some("abcdefgh") ); } }