crate_paths_cli_core/backend/docrs/
mod.rs1pub mod error;
2
3use super::BackendError;
4use crate::error::CoreError;
5use crate::item::ItemEntry;
6use crate::parser;
7use error::DocsrsBackendError;
8use reqwest::blocking::Client;
9use std::io::Read as _;
10use zstd::Decoder as ZstdDecoder;
11
12fn fetch_crate_all_items_html(
13 crate_name: &str,
14 crate_version: &str,
15 app_user_agent: &str,
16) -> Result<String, DocsrsBackendError> {
17 let base_url = format!("https://docs.rs/{}/{}/", crate_name, crate_version,);
18
19 let client = Client::builder()
20 .user_agent(app_user_agent)
21 .build()
22 .map_err(DocsrsBackendError::ClientBuild)?;
23
24 let response = client
26 .head(&base_url)
27 .send()
28 .map_err(|e| DocsrsBackendError::HttpGet(base_url.clone(), e))?
29 .error_for_status()
30 .map_err(|e| DocsrsBackendError::HttpStatus(base_url.clone(), e))?;
31
32 let final_url = response.url();
33 let html_url = final_url
34 .join("all.html")
35 .expect("failed to construct all.html URL");
36 let html_url_str = html_url.to_string();
37
38 let response = client
39 .get(html_url)
40 .send()
41 .map_err(|e| DocsrsBackendError::HttpGet(html_url_str.clone(), e))?
42 .error_for_status()
43 .map_err(|e| DocsrsBackendError::HttpStatus(html_url_str.clone(), e))?;
44
45 eprintln!("final URL: {}", response.url());
46 if let Some(ct) = response.headers().get(reqwest::header::CONTENT_TYPE) {
47 eprintln!("Content-Type: {:?}", ct);
48 }
49 if let Some(enc) = response.headers().get(reqwest::header::CONTENT_ENCODING) {
50 eprintln!("Content-Encoding: {:?}", enc);
51 }
52
53 let content_encoding = response
54 .headers()
55 .get(reqwest::header::CONTENT_ENCODING)
56 .and_then(|h| h.to_str().ok())
57 .map(|s| s.to_ascii_lowercase());
58
59 let raw_bytes = response
60 .bytes()
61 .map_err(|e| DocsrsBackendError::HttpGet(html_url_str.clone(), e))?;
62
63 let html_bytes: Vec<u8> = match content_encoding.as_deref() {
64 Some("zstd") => {
65 let mut decoder =
66 ZstdDecoder::new(raw_bytes.as_ref()).map_err(DocsrsBackendError::Decompress)?;
67 let mut buf = Vec::new();
68 decoder
69 .read_to_end(&mut buf)
70 .map_err(DocsrsBackendError::Decompress)?;
71 buf
72 },
73 _ => raw_bytes.to_vec(),
74 };
75
76 std::str::from_utf8(&html_bytes)
77 .map(|s| s.to_string())
78 .map_err(DocsrsBackendError::Utf8)
79}
80
81pub fn process(
82 crate_name: &str,
83 crate_version: &str,
84 app_user_agent: &str,
85) -> Result<Vec<ItemEntry>, CoreError> {
86 let html_content = fetch_crate_all_items_html(crate_name, crate_version, app_user_agent)
87 .map_err(BackendError::from)
88 .map_err(CoreError::from)?;
89 parser::parse_html_to_items(crate_name, &html_content).map_err(CoreError::from)
90}