use smart_default::SmartDefault;
use super::list::Tag;
use super::{Version, VersionedSource};
use crate::{Error, Result, error::ErrorWithData};
mod version;
#[derive(Debug, Clone, PartialEq)]
pub struct ResolvedSource {
pub source: VersionedSource,
pub wanted: Option<Version>,
pub latest: Version,
}
impl ResolvedSource {
pub fn new(source: VersionedSource, latest: impl Into<Version>) -> Self {
Self {
source,
wanted: None,
latest: latest.into(),
}
}
pub fn with_wanted(self, wanted: impl Into<Version>) -> Self {
Self {
wanted: Some(wanted.into()),
..self
}
}
}
#[derive(Debug, Clone, SmartDefault)]
pub struct ResolveOptions {}
pub fn resolve_tags(
source: &VersionedSource,
tags: &[Tag],
_options: &ResolveOptions,
) -> Result<Option<ResolvedSource>, ErrorWithData<VersionedSource>> {
use version::{Version, VersionReq};
let versions: Vec<_> = tags
.iter()
.filter_map(|tag| {
let version = Version::parse(&source.source, tag.name()).ok()?;
Some(Tagged(tag, version))
})
.collect();
let Tagged(latest_tag, latest) = versions
.iter()
.max_by(|Tagged(_, a), Tagged(_, b)| a.cmp(b))
.cloned()
.ok_or_else(|| Error::NoVersionAvailable.with_data(source.clone()))?;
let req = if let Some(req) = &source.required {
VersionReq::parse(&source.source, req).map_err(|err| err.with_data(source.clone()))?
} else {
return Ok(Some(ResolvedSource {
source: source.clone(),
wanted: None,
latest: latest_tag.name().into(),
}));
};
let wanted = versions
.iter()
.filter(|Tagged(_, version)| req.matches(version))
.max_by(|Tagged(_, a), Tagged(_, b)| a.cmp(b))
.cloned();
if req.matches(&latest) {
return Ok(None);
}
Ok(Some(ResolvedSource {
source: source.clone(),
wanted: wanted.map(|Tagged(tag, _)| tag.name().into()),
latest: latest_tag.name().into(),
}))
}
#[derive(Debug, Clone)]
struct Tagged<'a, T>(&'a Tag, T);
#[cfg(test)]
mod tests {
#[test]
fn test_simple() {
use crate::sources::{VersionedSource, data};
let source = VersionedSource::new(data::TerraformSource::new_provider("gitlabhq/gitlab"))
.with_current("~> 1.2.1")
.with_required("~> 1.2.1");
let tags = tags![
"1.0.0", "1.2.0", "1.2.55", "1.2.8", "1.3.0", "1.3.9", "2.0.0"
];
let expected = super::ResolvedSource {
source: source.clone(),
wanted: Some("1.2.55".into()),
latest: "2.0.0".into(),
};
let result = super::resolve_tags(&source, &tags, &super::ResolveOptions::default());
assert_eq!(result.unwrap(), Some(expected));
}
#[test]
fn test_operator_up_to_date() {
use crate::sources::{VersionedSource, data};
let source = VersionedSource::new(data::TerraformSource::new_provider("gitlabhq/gitlab"))
.with_current(">= 1.0.0")
.with_required(">= 1.0.0");
let tags = tags!["0.1.0", "0.2.4", "1.0.0"];
let result = super::resolve_tags(&source, &tags, &super::ResolveOptions::default());
assert_matches!(result, Ok(None));
}
#[test]
fn test_suffix_up_to_date() {
use crate::sources::{VersionedSource, data};
let source = VersionedSource::new(data::TerraformSource::new_provider("gitlabhq/gitlab"))
.with_current("= 1.0.0-beta")
.with_required("= 1.0.0-beta");
let tags = tags!["0.1.0", "0.2.4", "1.0.0-beta"];
let result = super::resolve_tags(&source, &tags, &super::ResolveOptions::default());
assert_matches!(result, Ok(None));
}
#[test]
fn test_descending_order() {
use crate::sources::{VersionedSource, data};
let source = VersionedSource::new(data::TerraformSource::new_provider("gitlabhq/gitlab"))
.with_current("~> 1.2.1")
.with_required("~> 1.2.1");
let tags = tags![
"2.0.0", "1.3.9", "1.3.0", "1.2.8", "1.2.55", "1.2.0", "1.0.0"
];
let expected = super::ResolvedSource {
source: source.clone(),
wanted: Some("1.2.55".into()),
latest: "2.0.0".into(),
};
let result = super::resolve_tags(&source, &tags, &super::ResolveOptions::default());
assert_eq!(result.unwrap(), Some(expected));
}
#[test]
fn test_arbitrary_order() {
use crate::sources::{Version, VersionedSource, data};
let source = VersionedSource::new(data::TerraformSource::new_provider("gitlabhq/gitlab"))
.with_current("~> 1.2.1")
.with_required("~> 1.2.1");
let tags = tags![
"1.2.55", "2.0.0", "1.0.0", "1.3.9", "1.2.0", "1.2.8", "1.3.0"
];
let result = super::resolve_tags(&source, &tags, &super::ResolveOptions::default());
let resolved = result.unwrap().expect("an update is available");
assert_eq!(resolved.latest, Version::from("2.0.0"));
assert_eq!(resolved.wanted, Some(Version::from("1.2.55")));
}
#[test]
fn test_different_requirement() {
use crate::sources::{VersionedSource, data};
let source = VersionedSource::new(data::TerraformSource::new_provider("gitlabhq/gitlab"))
.with_current("!= 0.2.4")
.with_required("!= 0.2.4");
let tags = tags!["0.1.0", "0.2.4", "1.0.0"];
let result = super::resolve_tags(&source, &tags, &super::ResolveOptions::default());
assert_matches!(result, Ok(None));
}
}