1mod item;
2mod to_markdown;
3
4pub use item::*;
5pub use to_markdown::convert_rustdoc_to_markdown;
6
7use std::path::PathBuf;
8use std::sync::Arc;
9
10use anyhow::{bail, Context, Result};
11use async_trait::async_trait;
12use fs::Fs;
13use futures::AsyncReadExt;
14use http::{AsyncBody, HttpClient, HttpClientWithUrl};
15
16use crate::indexer::IndexedDocsProvider;
17use crate::PackageName;
18
19#[derive(Debug, Clone, Copy)]
20pub enum RustdocSource {
21 /// The docs were sourced from Zed's rustdoc index.
22 Index,
23 /// The docs were sourced from local `cargo doc` output.
24 Local,
25 /// The docs were sourced from `docs.rs`.
26 DocsDotRs,
27}
28
29pub struct LocalProvider {
30 fs: Arc<dyn Fs>,
31 cargo_workspace_root: PathBuf,
32}
33
34impl LocalProvider {
35 pub fn new(fs: Arc<dyn Fs>, cargo_workspace_root: PathBuf) -> Self {
36 Self {
37 fs,
38 cargo_workspace_root,
39 }
40 }
41}
42
43#[async_trait]
44impl IndexedDocsProvider for LocalProvider {
45 async fn fetch_page(
46 &self,
47 crate_name: &PackageName,
48 item: Option<&RustdocItem>,
49 ) -> Result<Option<String>> {
50 let mut local_cargo_doc_path = self.cargo_workspace_root.join("target/doc");
51 local_cargo_doc_path.push(crate_name.as_ref());
52 if let Some(item) = item {
53 local_cargo_doc_path.push(item.url_path());
54 } else {
55 local_cargo_doc_path.push("index.html");
56 }
57
58 let Ok(contents) = self.fs.load(&local_cargo_doc_path).await else {
59 return Ok(None);
60 };
61
62 Ok(Some(contents))
63 }
64}
65
66pub struct DocsDotRsProvider {
67 http_client: Arc<HttpClientWithUrl>,
68}
69
70impl DocsDotRsProvider {
71 pub fn new(http_client: Arc<HttpClientWithUrl>) -> Self {
72 Self { http_client }
73 }
74}
75
76#[async_trait]
77impl IndexedDocsProvider for DocsDotRsProvider {
78 async fn fetch_page(
79 &self,
80 crate_name: &PackageName,
81 item: Option<&RustdocItem>,
82 ) -> Result<Option<String>> {
83 let version = "latest";
84 let path = format!(
85 "{crate_name}/{version}/{crate_name}{item_path}",
86 item_path = item
87 .map(|item| format!("/{}", item.url_path()))
88 .unwrap_or_default()
89 );
90
91 let mut response = self
92 .http_client
93 .get(
94 &format!("https://docs.rs/{path}"),
95 AsyncBody::default(),
96 true,
97 )
98 .await?;
99
100 let mut body = Vec::new();
101 response
102 .body_mut()
103 .read_to_end(&mut body)
104 .await
105 .context("error reading docs.rs response body")?;
106
107 if response.status().is_client_error() {
108 let text = String::from_utf8_lossy(body.as_slice());
109 bail!(
110 "status error {}, response: {text:?}",
111 response.status().as_u16()
112 );
113 }
114
115 Ok(Some(String::from_utf8(body)?))
116 }
117}