use crate::{
lock::PackageInLockFile,
registry::index::IndexedPackageDependency,
version::{semver_completeness, SemanticVersion, SemanticVersionCompleteness},
};
use anyhow::bail;
use anyhow::{anyhow, Result};
use itertools::Itertools;
use pubgrub::{range::Range, version::Version};
use serde::de::{self, Deserialize, Deserializer, MapAccess, Visitor};
use serde::Serialize;
use std::{convert::From, fmt};
#[derive(Debug, Clone, Serialize)]
#[serde(rename(serialize = "DependencySpecification"))]
pub struct Dependency<V>
where
V: Version + From<SemanticVersion>,
{
#[serde(rename(serialize = "dependency_name"))]
pub full_name: String,
pub version_requirement: String,
#[serde(skip)]
pub namespace: String,
#[serde(skip)]
pub name: String,
#[serde(skip)]
pub version_range: Range<V>,
}
impl<V> Dependency<V>
where
V: Version + From<SemanticVersion>,
{
pub fn is_version_range_none(&self) -> bool {
self.version_range == Range::none()
}
pub fn is_version_range_any(&self) -> bool {
self.version_range == Range::any()
}
#[allow(clippy::indexing_slicing)]
pub fn derive_range_for_version_request_pair(predicates: &[String]) -> Result<Range<V>>
where
V: Version + From<SemanticVersion>,
{
let left_range = Self::derive_range_for_single_version_request(&predicates[0])?;
let right_range = Self::derive_range_for_single_version_request(&predicates[1])?;
Ok(Range::intersection(&left_range, &right_range))
}
pub fn derive_range_for_single_version_request(version_predicate: &str) -> Result<Range<V>>
where
V: Version + From<SemanticVersion>,
{
use semver::Op::*;
if version_predicate == "*" {
return Ok(Range::any());
}
let semantic_version: SemanticVersion = version_predicate.try_into()?;
let parsed = semver::VersionReq::parse(version_predicate)?;
let first_comparator = &parsed.comparators.get(0);
if let Some(first_comparator) = first_comparator {
let first_operator = first_comparator.op;
return match first_operator {
Exact => Ok(Self::exact_version_to_range(
first_comparator,
semantic_version,
)),
Greater => Ok(Self::greater_version_to_range(
first_comparator,
semantic_version,
)),
GreaterEq => Ok(Self::greater_eq_version_to_range(semantic_version)),
Less => Ok(Self::less_version_to_range(semantic_version)),
LessEq => Ok(Self::less_eq_version_to_range(
first_comparator,
semantic_version,
)),
Caret => Ok(Self::caret_version_to_range(
first_comparator,
semantic_version,
)),
Tilde => Ok(Self::tilde_version_to_range(
first_comparator,
semantic_version,
)),
Wildcard => Ok(Self::wildcard_version_to_range(
first_comparator,
semantic_version,
)),
_ => Err(anyhow!("Unsupported operator.")),
};
}
Err(anyhow!("Unsupported version predicate."))
}
fn exact_version_to_range(comparator: &semver::Comparator, version: SemanticVersion) -> Range<V>
where
V: Version + From<SemanticVersion>,
{
use SemanticVersionCompleteness::*;
match semver_completeness(comparator) {
Complete => Range::exact(version),
OnlyMinorAndMajor => Range::between(version, version.bump_minor()),
OnlyMajor => Range::between(version, version.bump_major()),
}
}
fn greater_version_to_range(
comparator: &semver::Comparator,
version: SemanticVersion,
) -> Range<V>
where
V: Version + From<SemanticVersion>,
{
use SemanticVersionCompleteness::*;
match semver_completeness(comparator) {
Complete => Range::higher_than(version.bump_patch()),
OnlyMinorAndMajor => Range::higher_than(version.bump_minor()),
OnlyMajor => Range::higher_than(version.bump_major()),
}
}
fn greater_eq_version_to_range(version: SemanticVersion) -> Range<V>
where
V: Version + From<SemanticVersion>,
{
Range::higher_than(version)
}
fn less_version_to_range(version: SemanticVersion) -> Range<V>
where
V: Version + From<SemanticVersion>,
{
Range::strictly_lower_than(version)
}
fn less_eq_version_to_range(
comparator: &semver::Comparator,
version: SemanticVersion,
) -> Range<V>
where
V: Version + From<SemanticVersion>,
{
use SemanticVersionCompleteness::*;
match semver_completeness(comparator) {
Complete => Range::strictly_lower_than(version.bump_patch()),
OnlyMinorAndMajor => Range::strictly_lower_than(version.bump_minor()),
OnlyMajor => Range::strictly_lower_than(version.bump_major()),
}
}
fn caret_version_to_range(comparator: &semver::Comparator, version: SemanticVersion) -> Range<V>
where
V: Version + From<SemanticVersion>,
{
use SemanticVersionCompleteness::*;
match semver_completeness(comparator) {
Complete if version.is_major_zero() && !version.is_minor_zero() => {
Range::between(version, version.bump_minor())
}
Complete if version.is_major_zero() && version.is_minor_zero() => Range::exact(version),
OnlyMinorAndMajor if version.is_major_zero() && version.is_minor_zero() => {
Range::between(
SemanticVersion::zero(),
SemanticVersion::zero().bump_minor(),
)
}
Complete | OnlyMinorAndMajor | OnlyMajor => {
Range::between(version, version.bump_major())
}
}
}
fn tilde_version_to_range(comparator: &semver::Comparator, version: SemanticVersion) -> Range<V>
where
V: Version + From<SemanticVersion>,
{
use SemanticVersionCompleteness::*;
match semver_completeness(comparator) {
Complete | OnlyMinorAndMajor => Range::between(version, version.bump_minor()),
OnlyMajor => Range::between(version, version.bump_major()),
}
}
fn wildcard_version_to_range(
comparator: &semver::Comparator,
version: SemanticVersion,
) -> Range<V>
where
V: Version + From<SemanticVersion>,
{
use SemanticVersionCompleteness::*;
match semver_completeness(comparator) {
#[allow(clippy::unreachable)]
Complete => unreachable!(),
OnlyMinorAndMajor => Range::between(version, version.bump_minor()),
OnlyMajor => Range::between(version, version.bump_major()),
}
}
pub fn split_string_dependency_spec(input: &str) -> Result<(String, String)> {
let (dependency_name, mut version_requirement) = input.split(' ').enumerate().fold(
(String::default(), String::default()),
|(mut name, mut version_requirement), (index, part)| {
if index > 0 {
version_requirement.push(' ');
version_requirement.push_str(part);
} else {
name = part.to_owned();
}
(name, version_requirement)
},
);
if version_requirement.is_empty() {
bail!("Invalid dependency specification: {}", input);
}
version_requirement.remove(0);
if dependency_name.is_empty() || version_requirement.is_empty() {
bail!("Invalid dependency specification: {}", input);
}
Ok((dependency_name, version_requirement))
}
pub fn try_new(dependency_name: &str, version_requirement: &str) -> Result<Self> {
let (namespace, name) = dependency_name.split('/').enumerate().fold(
(String::default(), String::default()),
|mut namespace_and_name, (index, part)| {
if index > 0 {
namespace_and_name.1 = part.to_owned();
} else {
let mut namespace = part.to_owned();
namespace.remove(0);
namespace_and_name.0 = namespace;
}
namespace_and_name
},
);
let version_predicates: Vec<String> = if version_requirement.find(',').is_some() {
version_requirement
.replace(' ', "")
.split(',')
.map_into()
.collect()
} else {
version_requirement.split(' ').map_into().collect()
};
match version_predicates.len() {
2 => Ok(Self {
full_name: dependency_name.to_owned(),
version_requirement: version_requirement.to_owned(),
namespace,
name,
version_range: Self::derive_range_for_version_request_pair(&version_predicates)?,
}),
1 => Ok(Self {
full_name: dependency_name.to_owned(),
version_requirement: version_requirement.to_owned(),
namespace,
name,
version_range: Self::derive_range_for_single_version_request(version_requirement)?,
}),
_ => Err(anyhow!(
"Invalid version requirement: {}",
version_requirement
)),
}
}
}
impl<'de, V> Deserialize<'de> for Dependency<V>
where
V: Version + From<SemanticVersion>,
{
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
enum Field {
DependencyName,
VersionRequirement,
}
impl<'de> Deserialize<'de> for Field {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct FieldVisitor;
impl<'de> Visitor<'de> for FieldVisitor {
type Value = Field;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("`dependency_name` or `version_requirement`")
}
fn visit_str<E>(self, value: &str) -> Result<Field, E>
where
E: de::Error,
{
match value {
"dependency_name" => Ok(Field::DependencyName),
"version_requirement" => Ok(Field::VersionRequirement),
_ => Err(de::Error::unknown_field(value, FIELDS)),
}
}
}
deserializer.deserialize_identifier(FieldVisitor)
}
}
struct DependencyVisitor<V>
where
V: Version + From<SemanticVersion>,
{
_v: std::marker::PhantomData<V>,
}
impl<'de, V> Visitor<'de> for DependencyVisitor<V>
where
V: Version + From<SemanticVersion>,
{
type Value = Dependency<V>;
fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
formatter.write_str("struct Dependency")
}
fn visit_map<Vis>(self, mut map: Vis) -> Result<Self::Value, Vis::Error>
where
Vis: MapAccess<'de>,
{
let mut dependency_name = None;
let mut version_requirement = None;
while let Some(key) = map.next_key()? {
match key {
Field::DependencyName => {
if dependency_name.is_some() {
return Err(de::Error::duplicate_field("dependency_name"));
}
dependency_name = Some(map.next_value()?);
}
Field::VersionRequirement => {
if version_requirement.is_some() {
return Err(de::Error::duplicate_field("version_requirement"));
}
version_requirement = Some(map.next_value()?);
}
}
}
let dependency_name =
dependency_name.ok_or_else(|| de::Error::missing_field("dependency_name"))?;
let version_requirement = version_requirement
.ok_or_else(|| de::Error::missing_field("version_requirement"))?;
Dependency::try_new(dependency_name, version_requirement)
.map_or_else(|err| Err(de::Error::custom(format!("{}", err))), Ok)
}
}
const FIELDS: &[&str] = &["dependency_name", "version_requirement"];
deserializer.deserialize_struct(
"Dependency",
FIELDS,
DependencyVisitor {
_v: std::marker::PhantomData,
},
)
}
}
impl<V> ToString for Dependency<V>
where
V: Version + From<SemanticVersion>,
{
fn to_string(&self) -> String {
format!("{} {}", self.full_name, self.version_requirement)
}
}
impl<V> TryFrom<&str> for Dependency<V>
where
V: Version + From<SemanticVersion>,
{
type Error = anyhow::Error;
fn try_from(input: &str) -> Result<Self, Self::Error> {
let (dependency_name, version_requirement) = Self::split_string_dependency_spec(input)?;
Self::try_new(&dependency_name, &version_requirement)
}
}
impl<V> TryFrom<IndexedPackageDependency> for Dependency<V>
where
V: Version + From<SemanticVersion>,
{
type Error = anyhow::Error;
fn try_from(dependency: IndexedPackageDependency) -> Result<Self> {
Self::try_new(&dependency.name, &dependency.req)
}
}
impl<V> TryFrom<PackageInLockFile> for Dependency<V>
where
V: Version + From<SemanticVersion>,
{
type Error = anyhow::Error;
fn try_from(dependency: PackageInLockFile) -> Result<Self> {
Self::try_new(&dependency.name, &format!("={}", dependency.version))
}
}
impl<V> TryFrom<&PackageInLockFile> for Dependency<V>
where
V: Version + From<SemanticVersion>,
{
type Error = anyhow::Error;
fn try_from(dependency: &PackageInLockFile) -> Result<Self> {
Self::try_new(&dependency.name, &format!("={}", dependency.version))
}
}