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 Box::new(package_candidates.filter(move |p| p.manifest_path.starts_with(&path)))
98 } else {
99 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}