rust_crate_src_mcp/
extract.rs1use snafu::{ResultExt, Snafu};
2use std::path::PathBuf;
3
4pub async fn extract_crate(crate_name: &str, version: &str) -> Result<PathBuf, ExtractError> {
5 use extract_error::*;
6
7 let home = std::env::var("HOME").unwrap_or_else(|_| ".".to_string());
8 let cache_base = PathBuf::from(home).join(".cargo/registry/src");
9 let cache_dir = std::fs::read_dir(&cache_base)
10 .ok()
11 .and_then(|mut entries| {
12 entries.find_map(|e| {
13 e.ok().and_then(|e| {
14 let name = e.file_name();
15 let name_str = name.to_string_lossy();
16 if name_str.starts_with("index.crates.io-") {
17 Some(e.path())
18 } else {
19 None
20 }
21 })
22 })
23 })
24 .unwrap_or_else(|| cache_base.join("index.crates.io-6f17d22bba15001f"));
25
26 let extract_path = cache_dir.join(format!("{}-{}", crate_name, version));
27
28 if extract_path.exists() {
29 return Ok(extract_path);
30 }
31
32 let url = format!(
33 "https://static.crates.io/crates/{}/{}-{}.crate",
34 crate_name, crate_name, version
35 );
36 let client = reqwest::Client::new();
37 let response = client
38 .get(&url)
39 .header("User-Agent", "rust-crate-src")
40 .send()
41 .await
42 .context(DownloadSnafu {
43 crate_name,
44 version,
45 })?
46 .bytes()
47 .await
48 .context(DownloadSnafu {
49 crate_name,
50 version,
51 })?;
52
53 let decoder = flate2::read::GzDecoder::new(&response[..]);
54 let mut archive = tar::Archive::new(decoder);
55
56 std::fs::create_dir_all(&cache_dir).context(CacheDirSnafu)?;
57 archive.unpack(&cache_dir).context(UntarSnafu)?;
58
59 Ok(extract_path)
60}
61
62#[derive(Debug, Snafu)]
63#[snafu(module)]
64pub enum ExtractError {
65 #[snafu(display("failed to download crate '{crate_name}' version '{version}'"))]
66 Download {
67 crate_name: String,
68 version: String,
69 source: reqwest::Error,
70 },
71 #[snafu(display("failed to decompress crate archive"))]
72 Decompress { source: std::io::Error },
73 #[snafu(display("failed to untar crate archive"))]
74 Untar { source: std::io::Error },
75 #[snafu(display("failed to access cache directory"))]
76 CacheDir { source: std::io::Error },
77}