feat: implement AddonScript versioning

This commit is contained in:
Timo Ley 2022-12-13 12:02:56 +01:00
parent 70a85e9796
commit 985c3af906
8 changed files with 227 additions and 6 deletions

View file

@ -6,4 +6,5 @@ edition = "2018"
[dependencies]
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
url = {version = "2.2.2", features = ["serde"]}
url = {version = "2.2.2", features = ["serde"]}
addonscript-versioning = { path = "../versioning" }

View file

@ -1,5 +1,6 @@
use std::collections::HashSet;
use addonscript_versioning::{Version, VersionRestriction};
use serde::{Deserialize, Serialize};
pub use self::{
@ -16,7 +17,7 @@ pub struct Manifest {
pub addonscript: AddonScript,
pub id: String,
pub namespace: String,
pub version: String,
pub version: Version,
#[serde(skip_serializing_if = "Option::is_none")]
pub files: Option<Vec<File>>,
#[serde(skip_serializing_if = "Option::is_none")]
@ -62,7 +63,7 @@ pub struct Relation {
pub id: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub namespace: Option<String>,
pub version: String,
pub version: VersionRestriction,
#[serde(skip_serializing_if = "Option::is_none")]
pub repositories: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]

View file

@ -4,4 +4,6 @@ version = "0.1.0"
edition = "2018"
[dependencies]
thiserror = "1.0.24"
thiserror = "1.0.24"
semver = "1.0"
serde = "1.0"

View file

@ -11,5 +11,9 @@ pub enum Error {
IllegalVersionRange,
#[error("Version range overlap")]
RangeOverlap,
#[error("Version contains invalid characters")]
InvalidVersion,
#[error("SemVer error: {0}")]
Semver(#[from] semver::Error),
}
pub type Result<T> = std::result::Result<T, Error>;

View file

@ -1,6 +1,10 @@
pub mod errors;
pub mod maven;
mod serde;
mod util;
pub mod version;
pub use version::{Version, VersionRestriction};
#[cfg(test)]
mod tests {

View file

@ -4,12 +4,12 @@ use crate::{
};
use super::version::{ComparableVersion, VersionOption};
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct VersionRange {
pub(crate) restrictions: Vec<Restriction>,
pub(crate) recommended_version: Option<ComparableVersion>,
}
#[derive(Debug)]
#[derive(Debug, Clone)]
pub struct Restriction {
pub lower_bound: Option<ComparableVersion>,
pub lower_bound_inclusive: bool,

View file

@ -0,0 +1,68 @@
use ::serde::{Deserialize, Deserializer, Serialize};
use serde::de::Visitor;
use crate::{Version, VersionRestriction};
impl Serialize for Version {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl Serialize for VersionRestriction {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
serializer.serialize_str(&self.to_string())
}
}
impl<'de> Deserialize<'de> for Version {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct Vis;
impl<'de> Visitor<'de> for Vis {
type Value = Version;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("an AddonScript version")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
v.parse::<Version>()
.map_err(|e| E::custom(format!("invalid version: {}", e)))
}
}
deserializer.deserialize_str(Vis)
}
}
impl<'de> Deserialize<'de> for VersionRestriction {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct Vis;
impl<'de> Visitor<'de> for Vis {
type Value = VersionRestriction;
fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("an AddonScript version restriction")
}
fn visit_str<E>(self, v: &str) -> Result<Self::Value, E>
where
E: serde::de::Error,
{
v.parse::<VersionRestriction>()
.map_err(|e| E::custom(format!("invalid version restriction: {}", e)))
}
}
deserializer.deserialize_str(Vis)
}
}

View file

@ -0,0 +1,141 @@
use std::str::FromStr;
use semver::VersionReq;
use crate::{
errors::Error,
maven::{restriction::VersionRange, version::ComparableVersion},
};
#[derive(Clone, Eq, Debug)]
pub struct Version {
raw: String,
maven: ComparableVersion,
semver: Option<semver::Version>,
}
#[derive(Clone, Debug)]
pub struct VersionRestriction {
raw: String,
exact: Option<Version>,
restriction: VersionRestrictionInner,
}
#[derive(Clone, Debug)]
enum VersionRestrictionInner {
Semver(VersionReq),
Maven(VersionRange),
}
impl VersionRestriction {
pub fn parse(raw: String) -> Result<Self, Error> {
if !raw.is_ascii() {
return Err(Error::IllegalVersionRange);
}
let trimmed = raw.trim().to_string();
let is_semver =
trimmed.starts_with('=') || trimmed.starts_with('<') || trimmed.starts_with('>');
let mut exact: Option<Version> = None;
let inner = if is_semver {
let semver = VersionReq::parse(trimmed.as_str())?;
VersionRestrictionInner::Semver(semver)
} else {
let maven = VersionRange::parse(trimmed)?;
if maven.restrictions.is_empty() {
let recomm = maven.get_recommended().ok_or(Error::IllegalVersionRange)?;
exact = Some(Version::parse(recomm.to_string())?);
}
VersionRestrictionInner::Maven(maven)
};
Ok(VersionRestriction {
raw,
exact,
restriction: inner,
})
}
pub fn contains_version(&self, version: &Version) -> bool {
match &self.restriction {
VersionRestrictionInner::Semver(semver) => {
if let Some(sv) = &version.semver {
semver.matches(sv)
} else {
false
}
}
VersionRestrictionInner::Maven(maven) => maven.contains_version(&version.maven),
}
}
pub fn is_exact_version(&self) -> bool {
self.exact.is_some()
}
pub fn get_exact_version(&self) -> Option<Version> {
self.exact.clone()
}
}
impl FromStr for VersionRestriction {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s.to_string())
}
}
impl ToString for VersionRestriction {
fn to_string(&self) -> String {
self.raw.clone()
}
}
impl PartialEq for Version {
fn eq(&self, other: &Self) -> bool {
self.maven == other.maven
}
}
impl PartialOrd for Version {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
self.maven.partial_cmp(&other.maven)
}
}
impl Ord for Version {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.maven.cmp(&other.maven)
}
}
impl Version {
pub fn parse(raw: String) -> Result<Self, Error> {
if raw.contains(char::is_whitespace) || !raw.is_ascii() {
return Err(Error::InvalidVersion);
}
let maven = ComparableVersion::parse(raw.clone())?;
let semver = semver::Version::parse(raw.as_str()).ok();
Ok(Self { raw, maven, semver })
}
pub fn is_semver(&self) -> bool {
self.semver.is_some()
}
}
impl FromStr for Version {
type Err = Error;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Self::parse(s.to_string())
}
}
impl ToString for Version {
fn to_string(&self) -> String {
self.raw.clone()
}
}