feat: implement AddonScript versioning
This commit is contained in:
parent
70a85e9796
commit
985c3af906
8 changed files with 227 additions and 6 deletions
|
@ -7,3 +7,4 @@ edition = "2018"
|
||||||
serde = { version = "1.0", features = ["derive"] }
|
serde = { version = "1.0", features = ["derive"] }
|
||||||
serde_json = "1.0"
|
serde_json = "1.0"
|
||||||
url = {version = "2.2.2", features = ["serde"]}
|
url = {version = "2.2.2", features = ["serde"]}
|
||||||
|
addonscript-versioning = { path = "../versioning" }
|
|
@ -1,5 +1,6 @@
|
||||||
use std::collections::HashSet;
|
use std::collections::HashSet;
|
||||||
|
|
||||||
|
use addonscript_versioning::{Version, VersionRestriction};
|
||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
pub use self::{
|
pub use self::{
|
||||||
|
@ -16,7 +17,7 @@ pub struct Manifest {
|
||||||
pub addonscript: AddonScript,
|
pub addonscript: AddonScript,
|
||||||
pub id: String,
|
pub id: String,
|
||||||
pub namespace: String,
|
pub namespace: String,
|
||||||
pub version: String,
|
pub version: Version,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub files: Option<Vec<File>>,
|
pub files: Option<Vec<File>>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
@ -62,7 +63,7 @@ pub struct Relation {
|
||||||
pub id: String,
|
pub id: String,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub namespace: Option<String>,
|
pub namespace: Option<String>,
|
||||||
pub version: String,
|
pub version: VersionRestriction,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
pub repositories: Option<Vec<String>>,
|
pub repositories: Option<Vec<String>>,
|
||||||
#[serde(skip_serializing_if = "Option::is_none")]
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
|
|
@ -5,3 +5,5 @@ edition = "2018"
|
||||||
|
|
||||||
[dependencies]
|
[dependencies]
|
||||||
thiserror = "1.0.24"
|
thiserror = "1.0.24"
|
||||||
|
semver = "1.0"
|
||||||
|
serde = "1.0"
|
|
@ -11,5 +11,9 @@ pub enum Error {
|
||||||
IllegalVersionRange,
|
IllegalVersionRange,
|
||||||
#[error("Version range overlap")]
|
#[error("Version range overlap")]
|
||||||
RangeOverlap,
|
RangeOverlap,
|
||||||
|
#[error("Version contains invalid characters")]
|
||||||
|
InvalidVersion,
|
||||||
|
#[error("SemVer error: {0}")]
|
||||||
|
Semver(#[from] semver::Error),
|
||||||
}
|
}
|
||||||
pub type Result<T> = std::result::Result<T, Error>;
|
pub type Result<T> = std::result::Result<T, Error>;
|
||||||
|
|
|
@ -1,6 +1,10 @@
|
||||||
pub mod errors;
|
pub mod errors;
|
||||||
pub mod maven;
|
pub mod maven;
|
||||||
|
mod serde;
|
||||||
mod util;
|
mod util;
|
||||||
|
pub mod version;
|
||||||
|
|
||||||
|
pub use version::{Version, VersionRestriction};
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
|
|
|
@ -4,12 +4,12 @@ use crate::{
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::version::{ComparableVersion, VersionOption};
|
use super::version::{ComparableVersion, VersionOption};
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct VersionRange {
|
pub struct VersionRange {
|
||||||
pub(crate) restrictions: Vec<Restriction>,
|
pub(crate) restrictions: Vec<Restriction>,
|
||||||
pub(crate) recommended_version: Option<ComparableVersion>,
|
pub(crate) recommended_version: Option<ComparableVersion>,
|
||||||
}
|
}
|
||||||
#[derive(Debug)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Restriction {
|
pub struct Restriction {
|
||||||
pub lower_bound: Option<ComparableVersion>,
|
pub lower_bound: Option<ComparableVersion>,
|
||||||
pub lower_bound_inclusive: bool,
|
pub lower_bound_inclusive: bool,
|
||||||
|
|
68
crates/versioning/src/serde.rs
Normal file
68
crates/versioning/src/serde.rs
Normal 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)
|
||||||
|
}
|
||||||
|
}
|
141
crates/versioning/src/version.rs
Normal file
141
crates/versioning/src/version.rs
Normal 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()
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in a new issue