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_json = "1.0"
|
||||
url = {version = "2.2.2", features = ["serde"]}
|
||||
addonscript-versioning = { path = "../versioning" }
|
|
@ -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")]
|
||||
|
|
|
@ -5,3 +5,5 @@ edition = "2018"
|
|||
|
||||
[dependencies]
|
||||
thiserror = "1.0.24"
|
||||
semver = "1.0"
|
||||
serde = "1.0"
|
|
@ -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>;
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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,
|
||||
|
|
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