zenoh_plugin_trait/
compatibility.rs

1//
2// Copyright (c) 2023 ZettaScale Technology
3//
4// This program and the accompanying materials are made available under the
5// terms of the Eclipse Public License 2.0 which is available at
6// http://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
7// which is available at https://www.apache.org/licenses/LICENSE-2.0.
8//
9// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
10//
11// Contributors:
12//   ZettaScale Zenoh Team, <zenoh@zettascale.tech>
13//
14
15use std::fmt::Display;
16
17use crate::{Plugin, PluginInstance, PluginStartArgs, PluginVTable};
18
19pub trait StructVersion {
20    /// The version of the structure which implements this trait. After any change in the structure or its dependencies
21    /// which may affect the ABI, this version should be incremented.
22    fn struct_version() -> u64;
23    /// The features enabled during compilation of the structure implementing this trait.
24    /// Different features between the plugin and the host may cause ABI incompatibility even if the structure version is the same.
25    /// Use `concat_enabled_features!` to generate this string
26    fn struct_features() -> &'static str;
27}
28
29#[repr(C)]
30#[derive(Debug, PartialEq, Eq, Clone)]
31pub struct PluginStructVersion {
32    pub version: u64,
33    pub name: &'static str,
34    pub features: &'static str,
35}
36
37impl PluginStructVersion {
38    pub fn new<T: StructVersion>() -> Self {
39        Self {
40            version: T::struct_version(),
41            name: std::any::type_name::<T>(),
42            features: T::struct_features(),
43        }
44    }
45}
46
47impl Display for PluginStructVersion {
48    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
49        write!(
50            f,
51            "  version: {}\n  type: {}\n  features: {}\n",
52            self.version, self.name, self.features
53        )
54    }
55}
56
57#[repr(C)]
58#[derive(Debug, PartialEq, Eq, Clone)]
59pub struct Compatibility {
60    rust_version: Option<RustVersion>,
61    vtable_version: Option<PluginStructVersion>,
62    start_args_version: Option<PluginStructVersion>,
63    instance_version: Option<PluginStructVersion>,
64    plugin_version: Option<&'static str>,
65    plugin_long_version: Option<&'static str>,
66}
67
68impl Compatibility {
69    pub fn with_plugin_version<
70        StartArgsType: PluginStartArgs,
71        InstanceType: PluginInstance,
72        PluginType: Plugin<StartArgs = StartArgsType, Instance = InstanceType>,
73    >() -> Self {
74        let rust_version = Some(RustVersion::new());
75        let vtable_version = Some(PluginStructVersion::new::<
76            PluginVTable<StartArgsType, InstanceType>,
77        >());
78        let start_args_version = Some(PluginStructVersion::new::<StartArgsType>());
79        let instance_version = Some(PluginStructVersion::new::<InstanceType>());
80        let plugin_version = Some(PluginType::PLUGIN_VERSION);
81        let plugin_long_version = Some(PluginType::PLUGIN_LONG_VERSION);
82        Self {
83            rust_version,
84            vtable_version,
85            start_args_version,
86            instance_version,
87            plugin_version,
88            plugin_long_version,
89        }
90    }
91    pub fn with_empty_plugin_version<
92        StartArgsType: PluginStartArgs,
93        InstanceType: PluginInstance,
94    >() -> Self {
95        let rust_version = Some(RustVersion::new());
96        let vtable_version = Some(PluginStructVersion::new::<
97            PluginVTable<StartArgsType, InstanceType>,
98        >());
99        let start_args_version = Some(PluginStructVersion::new::<StartArgsType>());
100        let instance_version = Some(PluginStructVersion::new::<InstanceType>());
101        Self {
102            rust_version,
103            vtable_version,
104            start_args_version,
105            instance_version,
106            plugin_version: None,
107            plugin_long_version: None,
108        }
109    }
110    pub fn plugin_version(&self) -> Option<&'static str> {
111        self.plugin_version
112    }
113    pub fn plugin_long_version(&self) -> Option<&'static str> {
114        self.plugin_long_version
115    }
116    /// Compares fields if both are Some, otherwise skips the comparison.
117    /// Returns true if all the comparisons returned true, otherwise false.
118    /// If comparison passed or skipped, the corresponding field in both structs is set to None.
119    /// If comparison failed, the corresponding field in both structs is kept as is.
120    /// This allows not only to check compatibility, but also point to exact reasons of incompatibility.
121    pub fn compare(&mut self, other: &mut Self) -> bool {
122        let mut result = true;
123        Self::compare_field_fn(
124            &mut result,
125            &mut self.rust_version,
126            &mut other.rust_version,
127            RustVersion::are_compatible,
128        );
129        Self::compare_field(
130            &mut result,
131            &mut self.vtable_version,
132            &mut other.vtable_version,
133        );
134        Self::compare_field(
135            &mut result,
136            &mut self.start_args_version,
137            &mut other.start_args_version,
138        );
139        Self::compare_field(
140            &mut result,
141            &mut self.instance_version,
142            &mut other.instance_version,
143        );
144        // TODO: here we can later implement check for plugin version range compatibility
145        Self::compare_field(
146            &mut result,
147            &mut self.plugin_version,
148            &mut other.plugin_version,
149        );
150        Self::compare_field(
151            &mut result,
152            &mut self.plugin_long_version,
153            &mut other.plugin_long_version,
154        );
155        result
156    }
157
158    // Utility function for compare single field
159    fn compare_field_fn<T, F: Fn(&T, &T) -> bool>(
160        result: &mut bool,
161        a: &mut Option<T>,
162        b: &mut Option<T>,
163        compare: F,
164    ) {
165        let compatible = if let (Some(a), Some(b)) = (&a, &b) {
166            compare(a, b)
167        } else {
168            true
169        };
170        if compatible {
171            *a = None;
172            *b = None;
173        } else {
174            *result = false;
175        }
176    }
177    fn compare_field<T: PartialEq>(result: &mut bool, a: &mut Option<T>, b: &mut Option<T>) {
178        Self::compare_field_fn(result, a, b, |a, b| a == b);
179    }
180}
181
182impl Display for Compatibility {
183    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
184        if let Some(rust_version) = &self.rust_version {
185            writeln!(f, "Rust version:\n{}", rust_version)?;
186        }
187        if let Some(vtable_version) = &self.vtable_version {
188            writeln!(f, "VTable version:\n{}", vtable_version)?;
189        }
190        if let Some(start_args_version) = &self.start_args_version {
191            writeln!(f, "StartArgs version:\n{}", start_args_version)?;
192        }
193        if let Some(instance_version) = &self.instance_version {
194            writeln!(f, "Instance version:\n{}", instance_version)?;
195        }
196        if let Some(plugin_version) = &self.plugin_version {
197            writeln!(f, "Plugin version: {}", plugin_version)?;
198        }
199        Ok(())
200    }
201}
202
203#[repr(C)]
204#[derive(Debug, PartialEq, Eq, Clone)]
205pub struct RustVersion {
206    major: u32,
207    minor: u32,
208    patch: u32,
209    stable: bool,
210    commit: &'static str,
211}
212
213impl Display for RustVersion {
214    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
215        write!(
216            f,
217            "Rust {}.{}.{}{} commit {}",
218            self.major,
219            self.minor,
220            self.patch,
221            if self.stable { "" } else { "-nightly" },
222            self.commit
223        )
224    }
225}
226
227const RELEASE_AND_COMMIT: (&str, &str) = zenoh_macros::rustc_version_release!();
228impl RustVersion {
229    pub fn new() -> Self {
230        let (release, commit) = RELEASE_AND_COMMIT;
231        let (release, stable) = if let Some(p) = release.chars().position(|c| c == '-') {
232            (&release[..p], false)
233        } else {
234            (release, true)
235        };
236        let mut split = release.split('.').map(|s| s.trim());
237        RustVersion {
238            major: split.next().unwrap().parse().unwrap(),
239            minor: split.next().unwrap().parse().unwrap(),
240            patch: split.next().unwrap().parse().unwrap(),
241            stable,
242            commit,
243        }
244    }
245    pub fn are_compatible(a: &Self, b: &Self) -> bool {
246        if a.stable && b.stable {
247            a.major == b.major && a.minor == b.minor && a.patch == b.patch
248        } else {
249            a == b
250        }
251    }
252}
253
254impl Default for RustVersion {
255    fn default() -> Self {
256        Self::new()
257    }
258}