use anyhow::{Context, Result, anyhow};
use rustdoc_json::BuildError;
use std::path::{Path, PathBuf};
use public_api::{MINIMUM_NIGHTLY_RUST_VERSION, PublicApi};
use crate::{Args, ArgsAndToolchain, Subcommand, git_utils};
pub trait ApiSource {
fn obtain_api(&self, argst: &ArgsAndToolchain) -> Result<PublicApi>;
fn changes_commit(&self) -> bool {
false
}
fn boxed(self) -> Box<dyn ApiSource>
where
Self: Sized + 'static,
{
Box::new(self)
}
}
pub struct CurrentDir;
impl ApiSource for CurrentDir {
fn obtain_api(&self, argst: &ArgsAndToolchain) -> Result<PublicApi> {
public_api_for_current_dir(argst)
}
}
pub struct PublishedCrate {
version: Option<String>,
}
impl PublishedCrate {
pub fn new(version: Option<&str>) -> Self {
Self {
version: version.map(ToOwned::to_owned),
}
}
}
impl ApiSource for PublishedCrate {
fn obtain_api(&self, argst: &ArgsAndToolchain) -> Result<public_api::PublicApi> {
let rustdoc_json =
crate::published_crate::build_rustdoc_json(self.version.as_deref(), argst)?;
public_api_from_rustdoc_json(rustdoc_json, &argst.args)
}
}
pub struct Commit {
commit: String,
}
impl Commit {
pub fn new(args: &Args, commit_ref: &str) -> Result<Self> {
Ok(Self {
commit: git_utils::resolve_ref(args.git_root()?, commit_ref)?,
})
}
}
impl ApiSource for Commit {
fn obtain_api(&self, argst: &ArgsAndToolchain) -> Result<PublicApi> {
crate::git_checkout(&argst.args, &self.commit)?;
public_api_for_current_dir(argst)
}
fn changes_commit(&self) -> bool {
true
}
}
pub struct RustdocJson {
path: PathBuf,
}
impl RustdocJson {
pub fn new(path: PathBuf) -> Self {
Self { path }
}
}
impl ApiSource for RustdocJson {
fn obtain_api(&self, argst: &ArgsAndToolchain) -> Result<PublicApi> {
public_api_from_rustdoc_json(&self.path, &argst.args)
}
}
fn public_api_for_current_dir(argst: &ArgsAndToolchain) -> Result<PublicApi> {
let json_path = rustdoc_json_for_current_dir(argst)?;
public_api_from_rustdoc_json(json_path, &argst.args)
}
fn rustdoc_json_for_current_dir(argst: &ArgsAndToolchain) -> Result<PathBuf> {
let builder = builder_from_args(argst);
build_rustdoc_json(builder)
}
pub fn build_rustdoc_json(builder: rustdoc_json::Builder) -> Result<PathBuf> {
match builder.build() {
Err(BuildError::VirtualManifest(manifest_path)) => virtual_manifest_error(&manifest_path),
res => Ok(res?),
}
}
fn public_api_builder_from_args(rustdoc_json: &Path, args: &Args) -> public_api::Builder {
public_api::Builder::from_rustdoc_json(rustdoc_json)
.debug_sorting(args.debug_sorting)
.omit_blanket_impls(args.omit_blanket_impls())
.omit_auto_trait_impls(args.omit_auto_trait_impls())
.omit_auto_derived_impls(args.omit_auto_derived_impls())
.include_function_parameter_names(args.include_function_parameter_names())
}
pub fn builder_from_args(argst: &ArgsAndToolchain) -> rustdoc_json::Builder {
let args = &argst.args;
let mut builder = rustdoc_json::Builder::default()
.manifest_path(&args.manifest_path)
.all_features(args.all_features)
.no_default_features(args.no_default_features)
.features(&args.features);
if let Some(toolchain) = &argst.toolchain {
builder = builder.toolchain(toolchain);
}
if let Some(target_dir) = &args.target_dir {
builder = builder.target_dir(target_dir.clone());
}
if let Some(target) = &args.target {
builder = builder.target(target.clone());
}
if let Some(package) = &args.package {
builder = builder.package(package);
}
if let Some(cap_lints) = &args.cap_lints {
builder = builder.cap_lints(Some(cap_lints));
} else if let Some(Subcommand::Diff(_)) = args.subcommand {
builder = builder.cap_lints(Some("allow"));
}
builder
}
fn public_api_from_rustdoc_json(path: impl AsRef<Path>, args: &Args) -> Result<PublicApi> {
let json_path = path.as_ref();
if args.debug_processing {
println!("Processing {json_path:?}");
}
let public_api = public_api_builder_from_args(json_path, args)
.build()
.with_context(|| {
format!(
"Failed to parse rustdoc JSON at {json_path:?}.
This version of `cargo public-api` requires at least:
{MINIMUM_NIGHTLY_RUST_VERSION}
Ensure your nightly toolchain is up to date with:
rustup install nightly --profile minimal
If that does not help, it might be `cargo public-api` that is out of date. Try
to install the latest version with
cargo install cargo-public-api
If the issue remains, please report at
https://github.com/cargo-public-api/cargo-public-api/issues",
)
})?;
if args.debug_processing {
public_api.missing_item_ids().for_each(|i| {
println!("NOTE: rustdoc JSON missing referenced item with ID \"{i}\"");
});
}
Ok(public_api)
}
fn virtual_manifest_error(manifest_path: &Path) -> Result<PathBuf> {
Err(anyhow!(
"`{:?}` is a virtual manifest.
Listing or diffing the public API of an entire workspace is not supported.
Try
cargo public-api -p specific-crate
",
manifest_path
))
}