rustdoc.rs

  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}