use serde::{Deserialize, Serialize};
use crate::meta::{PluginCapabilities, PluginCapability};
pub const ABI_VERSION_MAJOR: u32 = 1;
pub const ABI_VERSION_MINOR: u32 = 1;
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct AbiVersion {
pub major: u32,
pub minor: u32,
}
impl AbiVersion {
pub const fn current() -> Self {
Self {
major: ABI_VERSION_MAJOR,
minor: ABI_VERSION_MINOR,
}
}
#[allow(clippy::absurd_extreme_comparisons)] pub fn is_compatible_with_host(&self) -> bool {
self.major == ABI_VERSION_MAJOR && self.minor <= ABI_VERSION_MINOR
}
}
impl std::fmt::Display for AbiVersion {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{}.{}", self.major, self.minor)
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "camelCase")]
pub struct SchemaVersionRange {
pub min_version: String,
pub max_version: String,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum CompatibilityStatus {
Compatible,
AbiIncompatible {
host: AbiVersion,
plugin: AbiVersion,
},
HostTooOld {
required: AbiVersion,
actual: AbiVersion,
},
SchemaUnsupported {
requested: String,
supported: Vec<SchemaVersionRange>,
},
MissingCapability(PluginCapability),
}
impl CompatibilityStatus {
pub fn is_compatible(&self) -> bool {
matches!(self, Self::Compatible)
}
}
pub fn check_compatibility(
capabilities: &PluginCapabilities,
requested_schema_version: Option<&str>,
required_capabilities: &[PluginCapability],
) -> CompatibilityStatus {
if !capabilities.abi_version.is_compatible_with_host() {
return CompatibilityStatus::AbiIncompatible {
host: AbiVersion::current(),
plugin: capabilities.abi_version,
};
}
if let Some(ref min_host) = capabilities.min_host_version {
let current = AbiVersion::current();
if current.major < min_host.major
|| (current.major == min_host.major && current.minor < min_host.minor)
{
return CompatibilityStatus::HostTooOld {
required: *min_host,
actual: current,
};
}
}
if let Some(requested) = requested_schema_version {
let req = semver::Version::parse(requested).ok();
let supported = capabilities.supported_schemas.iter().any(|range| {
match (
req.as_ref(),
semver::Version::parse(&range.min_version),
semver::Version::parse(&range.max_version),
) {
(Some(r), Ok(l), Ok(h)) => r >= &l && r <= &h,
_ => false,
}
});
if !supported {
return CompatibilityStatus::SchemaUnsupported {
requested: requested.to_owned(),
supported: capabilities.supported_schemas.clone(),
};
}
}
for required in required_capabilities {
if !capabilities.capabilities.contains(required) {
return CompatibilityStatus::MissingCapability(required.clone());
}
}
CompatibilityStatus::Compatible
}