cni_plugin/
version.rs

1//! Mostly internal types for handling versions.
2
3use std::{collections::HashSet, str::FromStr};
4
5use semver::{Version, VersionReq};
6use serde::{Deserialize, Deserializer, Serialize, Serializer};
7
8use crate::{
9	error::CniError,
10	reply::{reply, ReplyPayload},
11	Cni,
12};
13
14pub const COMPATIBLE_VERSIONS: &str = "=0.4.0||^1.0.0";
15pub const SUPPORTED_VERSIONS: &[&str] = &["0.4.0", "1.0.0"];
16
17#[derive(Clone, Deserialize)]
18#[serde(rename_all = "camelCase")]
19pub(crate) struct VersionPayload {
20	#[serde(deserialize_with = "deserialize_version")]
21	#[serde(serialize_with = "serialize_version")]
22	pub cni_version: Version,
23}
24
25impl Cni {
26	pub(crate) fn check_version(version: &Version) -> Result<(), CniError> {
27		if !VersionReq::parse(COMPATIBLE_VERSIONS)
28			.unwrap()
29			.matches(version)
30		{
31			Err(CniError::Incompatible(version.clone()))
32		} else {
33			Ok(())
34		}
35	}
36
37	pub(crate) fn handle_version(version: Version) -> ! {
38		let mut supported_versions = SUPPORTED_VERSIONS
39			.iter()
40			.map(|v| Version::parse(*v))
41			.collect::<Result<HashSet<_>, _>>()
42			.unwrap();
43
44		let supported = Self::check_version(&version).is_ok();
45		if supported {
46			supported_versions.insert(version.clone());
47		}
48
49		reply(VersionReply {
50			cni_version: version,
51			supported_versions: supported_versions.into_iter().collect(),
52		});
53	}
54}
55
56/// The reply structure used when returning for a `VERSION` command.
57///
58/// The spec currently mandates that supported versions are provided as an
59/// exhaustive array, but this library hopes to do support according to semver
60/// compatibility, so it cheats a bit when rendering this reply within
61/// [`Cni::load()`][crate::Cni::load()] and adds the runtime-requested version
62/// number to the `supported_versions` field when it is semver-compatible.
63#[derive(Clone, Debug, Deserialize, Serialize)]
64#[serde(rename_all = "camelCase")]
65pub struct VersionReply {
66	/// The CNI version of the plugin input config.
67	#[serde(deserialize_with = "deserialize_version")]
68	#[serde(serialize_with = "serialize_version")]
69	pub cni_version: Version,
70
71	/// The versions this plugin supports.
72	#[serde(deserialize_with = "deserialize_version_list")]
73	#[serde(serialize_with = "serialize_version_list")]
74	pub supported_versions: Vec<Version>,
75}
76
77impl<'de> ReplyPayload<'de> for VersionReply {}
78
79pub(crate) fn serialize_version<S>(version: &Version, serializer: S) -> Result<S::Ok, S::Error>
80where
81	S: Serializer,
82{
83	version.to_string().serialize(serializer)
84}
85
86pub(crate) fn serialize_version_list<S>(list: &[Version], serializer: S) -> Result<S::Ok, S::Error>
87where
88	S: Serializer,
89{
90	list.iter()
91		.map(Version::to_string)
92		.collect::<Vec<String>>()
93		.serialize(serializer)
94}
95
96pub(crate) fn deserialize_version<'de, D>(deserializer: D) -> Result<Version, D::Error>
97where
98	D: Deserializer<'de>,
99{
100	use serde::de::Error;
101	let j = String::deserialize(deserializer)?;
102	Version::from_str(&j).map_err(Error::custom)
103}
104
105pub(crate) fn deserialize_version_list<'de, D>(deserializer: D) -> Result<Vec<Version>, D::Error>
106where
107	D: Deserializer<'de>,
108{
109	use serde::de::Error;
110	let j = Vec::<String>::deserialize(deserializer)?;
111	j.iter()
112		.map(|s| Version::from_str(s).map_err(Error::custom))
113		.collect()
114}