Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
87 changes: 79 additions & 8 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,11 @@ pub use {
versioned::{VersionedIndex, VersionedRustdocAdapter, VersionedStorage},
};

#[derive(Deserialize)]
struct RustdocFormatVersion {
format_version: u32,
}

#[non_exhaustive]
#[derive(Debug, Error)]
pub enum LoadingError {
Expand All @@ -38,17 +43,59 @@ pub enum LoadingError {
Other(#[from] anyhow::Error),
}

#[derive(Deserialize)]
struct RustdocFormatVersion {
format_version: u32,
}
/// The last characters of a rustdoc file should be: ,"format_version":NUM}.
/// In this case, we can rapidly extract the version by simply reading the last
/// few bytes.
///
/// Returns an error if the last characters do not match the prescribed format, otherwise
/// returns the version.
fn detect_rustdoc_format_version_fast_path(
path: &Path,
file_data: &str,
) -> Result<u32, LoadingError> {
let error_closure = |s: &'static str| {
LoadingError::RustdocFormatDetection(path.display().to_string(), anyhow::anyhow!(s))
};

fn detect_rustdoc_format_version(path: &Path, file_data: &str) -> Result<u32, LoadingError> {
let version = serde_json::from_str::<RustdocFormatVersion>(file_data).map_err(|e| {
LoadingError::RustdocFormatDetection(path.display().to_string(), anyhow::Error::from(e))
let start = file_data[file_data.len() - 23..]
.rfind(",")
.ok_or_else(|| error_closure("Fast path failed: comma not found in last 23 bytes."))?;

let version_string: &str = &file_data[start..];
let sep_idx = version_string
.rfind(":")
.ok_or_else(|| error_closure("Fast path failed: no colon follows the final comma."))?;

let final_idx = version_string.rfind("}").ok_or_else(|| {
error_closure("Fast path failed: file does not end with a close bracket.")
})?;

Ok(version.format_version)
if !version_string[..sep_idx].ends_with("\"format_version\"") {
Err(error_closure(
"Fast path failed: final key is not \"format_version\"",
))
} else {
version_string[sep_idx + 1..final_idx]
.parse::<u32>()
.map_err(|_| error_closure("Fast path failed: version number is invalid."))
}
}

fn detect_rustdoc_format_version(path: &Path, file_data: &str) -> Result<u32, LoadingError> {
let version = detect_rustdoc_format_version_fast_path(path, file_data);

match version {
Ok(version_num) => Ok(version_num),
Err(_) => {
let version = serde_json::from_str::<RustdocFormatVersion>(file_data).map_err(|e| {
LoadingError::RustdocFormatDetection(
path.display().to_string(),
anyhow::Error::from(e),
)
})?;
Ok(version.format_version)
}
}
}

fn parse_or_report_error<T>(
Expand Down Expand Up @@ -113,3 +160,27 @@ fn get_package_metadata(

Ok(package)
}

#[cfg(test)]
mod tests {
use std::path::PathBuf;

use super::*;

// Test that format version succeeds both with and without the fast path.
#[test]
fn test_rustdoc_format_version() {
let fast_file_data = r#"{"test":10,"format_version":10}"#;
let test_path = PathBuf::from("");
match detect_rustdoc_format_version(&test_path, fast_file_data) {
Ok(version_num) => assert_eq!(version_num, 10),
Err(err) => panic!("Format version detection failed with error {err}"),
}

let slow_file_data = r#"{"format_version":10,"test":10}"#;
match detect_rustdoc_format_version(&test_path, slow_file_data) {
Ok(version_num) => assert_eq!(version_num, 10),
Err(err) => panic!("Format version detection failed with error {err}"),
}
}
}
Loading