use camino::Utf8PathBuf;
use cargo_metadata::MetadataCommand;
use std::convert::TryFrom;
use rust_releases::{Release, ReleaseIndex};
use crate::check::Check;
use crate::context::{EnvironmentContext, VerifyContext};
use crate::error::{CargoMSRVError, TResult};
use crate::manifest::bare_version::BareVersion;
use crate::manifest::CargoManifest;
use crate::outcome::Outcome;
use crate::reporter::event::VerifyResult;
use crate::reporter::Reporter;
use crate::sub_command::SubCommand;
use crate::toolchain::ToolchainSpec;
pub struct Verify<'index, C: Check> {
release_index: &'index ReleaseIndex,
runner: C,
}
impl<'index, C: Check> Verify<'index, C> {
pub fn new(release_index: &'index ReleaseIndex, runner: C) -> Self {
Self {
release_index,
runner,
}
}
}
impl<'index, C: Check> SubCommand for Verify<'index, C> {
type Context = VerifyContext;
type Output = ();
fn run(&self, ctx: &Self::Context, reporter: &impl Reporter) -> TResult<Self::Output> {
let rust_version = ctx.rust_version.clone();
verify_msrv(
reporter,
ctx,
self.release_index,
rust_version,
&self.runner,
)?;
Ok(())
}
}
fn verify_msrv(
reporter: &impl Reporter,
ctx: &VerifyContext,
release_index: &ReleaseIndex,
rust_version: RustVersion,
runner: &impl Check,
) -> TResult<()> {
let bare_version = rust_version.version();
let version =
bare_version.try_to_semver(release_index.releases().iter().map(Release::version))?;
let target = ctx.toolchain.target;
let components = ctx.toolchain.components;
let toolchain = ToolchainSpec::new(version.clone(), target, components);
match runner.check(&toolchain)? {
Outcome::Success(_) => success(reporter, toolchain),
Outcome::Failure(f) => failure(reporter, toolchain, rust_version, Some(f.error_message)),
}
}
fn success(reporter: &impl Reporter, toolchain: ToolchainSpec) -> TResult<()> {
reporter.report_event(VerifyResult::compatible(toolchain))?;
Ok(())
}
fn failure(
reporter: &impl Reporter,
toolchain: ToolchainSpec,
rust_version: RustVersion,
error: Option<String>,
) -> TResult<()> {
reporter.report_event(VerifyResult::incompatible(toolchain, error))?;
Err(CargoMSRVError::SubCommandVerify(Error::VerifyFailed(
VerifyFailed::from(rust_version),
)))
}
#[derive(Debug, thiserror::Error)]
pub enum Error {
#[error(
"Crate source was found to be incompatible with Rust version '{}' specified {}", .0.rust_version, .0.source
)]
VerifyFailed(VerifyFailed),
}
#[derive(Debug)]
pub struct VerifyFailed {
rust_version: BareVersion,
source: RustVersionSource,
}
impl From<RustVersion> for VerifyFailed {
fn from(value: RustVersion) -> Self {
VerifyFailed {
rust_version: value.rust_version,
source: value.source,
}
}
}
#[derive(Clone, Debug)]
pub struct RustVersion {
rust_version: BareVersion,
source: RustVersionSource,
}
impl RustVersion {
pub fn from_arg(rust_version: BareVersion) -> Self {
Self {
rust_version,
source: RustVersionSource::Arg,
}
}
pub fn try_from_environment(env: &EnvironmentContext) -> TResult<Self> {
let manifest_path = env.manifest();
let metadata = MetadataCommand::new()
.manifest_path(&manifest_path)
.exec()?;
CargoManifest::try_from(metadata)?
.minimum_rust_version()
.ok_or_else(|| CargoMSRVError::NoMSRVKeyInCargoToml(manifest_path.clone()))
.map(|v| RustVersion {
rust_version: v.clone(),
source: RustVersionSource::Manifest(manifest_path.clone()),
})
}
pub fn version(&self) -> &BareVersion {
&self.rust_version
}
pub fn into_version(self) -> BareVersion {
self.rust_version
}
}
#[derive(Clone, Debug, thiserror::Error)]
enum RustVersionSource {
#[error("as --rust-version argument")]
Arg,
#[error("as MSRV in the Cargo manifest located at '{0}'")]
Manifest(Utf8PathBuf),
}