rust_crate_src_mcp/
extract.rs

1use 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}