dpp_plugin_traits/
version.rs1use serde::{Deserialize, Serialize};
4
5use crate::meta::{PluginCapabilities, PluginCapability};
6
7pub const ABI_VERSION_MAJOR: u32 = 1;
12pub const ABI_VERSION_MINOR: u32 = 1;
15
16#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
18#[serde(rename_all = "camelCase")]
19pub struct AbiVersion {
20 pub major: u32,
21 pub minor: u32,
22}
23
24impl AbiVersion {
25 pub const fn current() -> Self {
26 Self {
27 major: ABI_VERSION_MAJOR,
28 minor: ABI_VERSION_MINOR,
29 }
30 }
31
32 #[allow(clippy::absurd_extreme_comparisons)] pub fn is_compatible_with_host(&self) -> bool {
38 self.major == ABI_VERSION_MAJOR && self.minor <= ABI_VERSION_MINOR
39 }
40}
41
42impl std::fmt::Display for AbiVersion {
43 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
44 write!(f, "{}.{}", self.major, self.minor)
45 }
46}
47
48#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
54#[serde(rename_all = "camelCase")]
55pub struct SchemaVersionRange {
56 pub min_version: String,
58 pub max_version: String,
60}
61
62#[derive(Debug, Clone, PartialEq, Eq)]
64pub enum CompatibilityStatus {
65 Compatible,
67 AbiIncompatible {
69 host: AbiVersion,
70 plugin: AbiVersion,
71 },
72 HostTooOld {
74 required: AbiVersion,
75 actual: AbiVersion,
76 },
77 SchemaUnsupported {
79 requested: String,
80 supported: Vec<SchemaVersionRange>,
81 },
82 MissingCapability(PluginCapability),
84}
85
86impl CompatibilityStatus {
87 pub fn is_compatible(&self) -> bool {
88 matches!(self, Self::Compatible)
89 }
90}
91
92pub fn check_compatibility(
95 capabilities: &PluginCapabilities,
96 requested_schema_version: Option<&str>,
97 required_capabilities: &[PluginCapability],
98) -> CompatibilityStatus {
99 if !capabilities.abi_version.is_compatible_with_host() {
101 return CompatibilityStatus::AbiIncompatible {
102 host: AbiVersion::current(),
103 plugin: capabilities.abi_version,
104 };
105 }
106
107 if let Some(ref min_host) = capabilities.min_host_version {
109 let current = AbiVersion::current();
110 if current.major < min_host.major
111 || (current.major == min_host.major && current.minor < min_host.minor)
112 {
113 return CompatibilityStatus::HostTooOld {
114 required: *min_host,
115 actual: current,
116 };
117 }
118 }
119
120 if let Some(requested) = requested_schema_version {
122 let req = semver::Version::parse(requested).ok();
123 let supported = capabilities.supported_schemas.iter().any(|range| {
124 let lo = semver::Version::parse(&range.min_version).ok();
125 let hi = semver::Version::parse(&range.max_version).ok();
126 match (req.as_ref(), lo, hi) {
127 (Some(r), Some(l), Some(h)) => r >= &l && r <= &h,
128 _ => {
129 requested >= range.min_version.as_str()
130 && requested <= range.max_version.as_str()
131 }
132 }
133 });
134 if !supported {
135 return CompatibilityStatus::SchemaUnsupported {
136 requested: requested.to_owned(),
137 supported: capabilities.supported_schemas.clone(),
138 };
139 }
140 }
141
142 for required in required_capabilities {
144 if !capabilities.capabilities.contains(required) {
145 return CompatibilityStatus::MissingCapability(required.clone());
146 }
147 }
148
149 CompatibilityStatus::Compatible
150}