2023-10-12 15:58:01 -07:00
|
|
|
use {
|
2023-10-13 07:54:37 -07:00
|
|
|
crate::{
|
2023-10-20 15:41:24 -07:00
|
|
|
client::Client,
|
|
|
|
crate_handler::{Dependency, Error, PackageMetaData, Program, UnpackedCrate},
|
2023-10-13 07:54:37 -07:00
|
|
|
response_builder,
|
|
|
|
},
|
2023-10-12 15:58:01 -07:00
|
|
|
log::info,
|
|
|
|
serde::{Deserialize, Serialize},
|
2023-10-20 15:41:24 -07:00
|
|
|
std::{
|
|
|
|
collections::BTreeMap,
|
|
|
|
sync::{Arc, RwLock},
|
|
|
|
},
|
2023-10-12 15:58:01 -07:00
|
|
|
};
|
|
|
|
|
|
|
|
#[derive(Debug, Default, Deserialize, Serialize)]
|
|
|
|
struct RegistryConfig {
|
|
|
|
dl: String,
|
|
|
|
api: Option<String>,
|
|
|
|
}
|
|
|
|
|
2023-10-13 07:54:37 -07:00
|
|
|
pub(crate) struct RegistryIndex {
|
2023-10-12 15:58:01 -07:00
|
|
|
pub(crate) index_root: String,
|
|
|
|
config: String,
|
2023-10-13 07:54:37 -07:00
|
|
|
index: RwLock<BTreeMap<String, IndexEntry>>,
|
|
|
|
}
|
|
|
|
|
|
|
|
#[derive(Serialize)]
|
|
|
|
pub(crate) struct IndexEntryDep {
|
|
|
|
pub name: String,
|
|
|
|
pub req: String,
|
|
|
|
pub features: Vec<String>,
|
|
|
|
pub optional: bool,
|
|
|
|
pub default_features: bool,
|
|
|
|
pub target: Option<String>,
|
|
|
|
pub kind: String,
|
|
|
|
pub registry: Option<String>,
|
|
|
|
pub package: Option<String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<Dependency> 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<IndexEntryDep>,
|
|
|
|
pub cksum: String,
|
|
|
|
pub features: BTreeMap<String, Vec<String>>,
|
|
|
|
pub yanked: bool,
|
|
|
|
pub links: Option<String>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl From<PackageMetaData> 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,
|
|
|
|
}
|
|
|
|
}
|
2023-10-12 15:58:01 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
impl RegistryIndex {
|
2023-10-13 07:54:37 -07:00
|
|
|
pub(crate) fn new(root: &str, server_url: &str) -> Self {
|
2023-10-12 15:58:01 -07:00
|
|
|
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,
|
2023-10-13 07:54:37 -07:00
|
|
|
index: RwLock::new(BTreeMap::new()),
|
2023-10-12 15:58:01 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2023-10-13 07:54:37 -07:00
|
|
|
pub(crate) fn handler(
|
|
|
|
&self,
|
|
|
|
request: hyper::Request<hyper::Body>,
|
2023-10-20 15:41:24 -07:00
|
|
|
client: Arc<Client>,
|
2023-10-13 07:54:37 -07:00
|
|
|
) -> hyper::Response<hyper::Body> {
|
2023-10-12 15:58:01 -07:00
|
|
|
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);
|
|
|
|
}
|
|
|
|
|
2023-10-20 15:41:24 -07:00
|
|
|
self.handle_crate_lookup_request(path, client)
|
2023-10-13 07:54:37 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
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(())
|
2023-10-12 15:58:01 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
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)
|
|
|
|
}
|
|
|
|
|
2023-10-20 15:41:24 -07:00
|
|
|
fn handle_crate_lookup_request(
|
|
|
|
&self,
|
|
|
|
path: &str,
|
|
|
|
client: Arc<Client>,
|
|
|
|
) -> hyper::Response<hyper::Body> {
|
2023-10-12 15:58:01 -07:00
|
|
|
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",
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2023-10-13 07:54:37 -07:00
|
|
|
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",
|
|
|
|
);
|
|
|
|
};
|
|
|
|
|
2023-10-20 15:41:24 -07:00
|
|
|
let response = if let Some(entry) = read_index.get(crate_name) {
|
|
|
|
Some(serde_json::to_string(entry))
|
|
|
|
} else {
|
2023-10-13 07:54:37 -07:00
|
|
|
// The index currently doesn't contain the program entry.
|
|
|
|
// Fetch the program information from the network using RPC client.
|
2023-10-20 15:41:24 -07:00
|
|
|
Program::crate_name_to_program_id(crate_name)
|
|
|
|
.and_then(|id| UnpackedCrate::fetch_index(id, client).ok())
|
|
|
|
.map(|entry| serde_json::to_string(&entry))
|
2023-10-13 07:54:37 -07:00
|
|
|
};
|
|
|
|
|
2023-10-20 15:41:24 -07:00
|
|
|
let Some(Ok(response)) = response else {
|
2023-10-13 07:54:37 -07:00
|
|
|
return response_builder::error_response(
|
|
|
|
hyper::StatusCode::INTERNAL_SERVER_ERROR,
|
|
|
|
"Internal error. index entry is corrupted",
|
|
|
|
);
|
|
|
|
};
|
2023-10-12 15:58:01 -07:00
|
|
|
|
2023-10-13 07:54:37 -07:00
|
|
|
response_builder::success_response_str(response.as_str())
|
2023-10-12 15:58:01 -07:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
#[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")
|
|
|
|
);
|
|
|
|
}
|
|
|
|
}
|