trustfall_rustdoc/
lib.rs

1#![forbid(unsafe_code)]
2
3use std::path::Path;
4
5use serde::Deserialize;
6use thiserror::Error;
7
8mod parser;
9mod query;
10mod versioned;
11
12use versioned::supported_versions;
13pub use {
14    parser::load_rustdoc,
15    versioned::{VersionedIndex, VersionedRustdocAdapter, VersionedStorage},
16};
17
18#[non_exhaustive]
19#[derive(Debug, Error)]
20pub enum LoadingError {
21    #[error("failed to parse 'cargo metadata' output: {0}")]
22    MetadataParsing(String),
23
24    #[error("failed to read rustdoc JSON file: {0}")]
25    RustdocIoError(String, std::io::Error),
26
27    #[error("unable to detect rustdoc 'format_version' key in file: {0}")]
28    RustdocFormatDetection(String, anyhow::Error),
29
30    #[error("failed to parse rustdoc JSON format v{0} file: {1}")]
31    RustdocParsing(u32, String, anyhow::Error),
32
33    #[error("unsupported rustdoc format v{0} for file: {1}\n(supported formats are {list})",
34        list = supported_versions().iter().map(|v| format!("v{v}")).collect::<Vec<_>>().join(", "))]
35    UnsupportedFormat(u32, String),
36
37    #[error("unexpected error: {0}")]
38    Other(#[from] anyhow::Error),
39}
40
41#[derive(Deserialize)]
42struct RustdocFormatVersion {
43    format_version: u32,
44}
45
46fn detect_rustdoc_format_version(path: &Path, file_data: &str) -> Result<u32, LoadingError> {
47    let version = serde_json::from_str::<RustdocFormatVersion>(file_data).map_err(|e| {
48        LoadingError::RustdocFormatDetection(path.display().to_string(), anyhow::Error::from(e))
49    })?;
50
51    Ok(version.format_version)
52}
53
54fn parse_or_report_error<T>(
55    path: &Path,
56    file_data: &str,
57    format_version: u32,
58) -> Result<T, LoadingError>
59where
60    T: for<'a> Deserialize<'a>,
61{
62    serde_json::from_str(file_data).map_err(|e| {
63        LoadingError::RustdocParsing(
64            format_version,
65            path.display().to_string(),
66            anyhow::Error::from(e),
67        )
68    })
69}
70
71fn get_package_metadata(
72    metadata: cargo_metadata::Metadata,
73) -> Result<cargo_metadata::Package, LoadingError> {
74    let dependencies = &metadata
75        .root_package()
76        .ok_or_else(|| {
77            LoadingError::MetadataParsing("no root package found in 'cargo metadata' output".into())
78        })?
79        .dependencies;
80    if dependencies.len() != 1 {
81        return Err(LoadingError::MetadataParsing("the metadata unexpectedly contained more than one dependency; we expected our target package to be the only dependency".into()));
82    }
83    let dependency = dependencies
84        .first()
85        .expect("no first dependency, even though we just checked the count");
86    let dependency_name = dependency.name.clone();
87    let dependency_path = dependency.path.clone();
88    let dependency_version = dependency.req.clone();
89
90    let package_candidates = metadata
91        .packages
92        .into_iter()
93        .filter(|p| p.name == dependency_name);
94
95    let mut package_candidates: Box<dyn Iterator<Item = _>> = if let Some(path) = dependency_path {
96        // We're using a path dependency.
97        Box::new(package_candidates.filter(move |p| p.manifest_path.starts_with(&path)))
98    } else {
99        // We're using a version number dependency.
100        Box::new(package_candidates.filter(move |p| dependency_version.matches(&p.version)))
101    };
102
103    let Some(package) = package_candidates.next() else {
104        return Err(LoadingError::MetadataParsing(format!(
105            "failed to find package metadata for package {dependency_name}"
106        )));
107    };
108    if package_candidates.next().is_some() {
109        return Err(LoadingError::MetadataParsing(format!(
110            "ambiguous package metadata found for {dependency_name}"
111        )));
112    }
113
114    Ok(package)
115}