use std::collections::BTreeMap;
use std::{cmp, result};
use serde::{Deserialize, Serialize};
use crate::core::{ModuleInstanceId, ModuleKind};
use crate::db::DatabaseVersion;
use crate::encoding::{Decodable, Encodable};
#[derive(
Debug, Copy, Clone, PartialOrd, Ord, Serialize, Deserialize, Encodable, Decodable, PartialEq, Eq,
)]
pub struct CoreConsensusVersion {
pub major: u32,
pub minor: u32,
}
impl CoreConsensusVersion {
pub const fn new(major: u32, minor: u32) -> Self {
Self { major, minor }
}
}
pub const CORE_CONSENSUS_VERSION: CoreConsensusVersion = CoreConsensusVersion::new(2, 1);
#[derive(
Debug,
Hash,
Copy,
Clone,
PartialEq,
Eq,
PartialOrd,
Ord,
Serialize,
Deserialize,
Encodable,
Decodable,
)]
pub struct ModuleConsensusVersion {
pub major: u32,
pub minor: u32,
}
impl ModuleConsensusVersion {
pub const fn new(major: u32, minor: u32) -> Self {
Self { major, minor }
}
}
#[derive(Debug, Copy, Clone, Serialize, Deserialize, PartialEq, Eq, Decodable, Encodable)]
pub struct ApiVersion {
pub major: u32,
pub minor: u32,
}
impl ApiVersion {
pub const fn new(major: u32, minor: u32) -> Self {
Self { major, minor }
}
}
impl cmp::PartialOrd for ApiVersion {
fn partial_cmp(&self, other: &Self) -> Option<cmp::Ordering> {
Some(self.cmp(other))
}
}
impl cmp::Ord for ApiVersion {
fn cmp(&self, other: &Self) -> cmp::Ordering {
self.major
.cmp(&other.major)
.then(self.minor.cmp(&other.minor))
}
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Default, Encodable, Decodable)]
pub struct MultiApiVersion(Vec<ApiVersion>);
impl MultiApiVersion {
pub fn new() -> Self {
Self::default()
}
fn is_consistent(&self) -> bool {
self.0
.iter()
.fold((None, true), |(prev, is_sorted), next| {
(
Some(*next),
is_sorted && prev.is_none_or(|prev| prev.major < next.major),
)
})
.1
}
fn iter(&'_ self) -> MultiApiVersionIter<'_> {
MultiApiVersionIter(self.0.iter())
}
pub fn try_from_iter<T: IntoIterator<Item = ApiVersion>>(
iter: T,
) -> result::Result<Self, ApiVersion> {
Result::from_iter(iter)
}
fn try_insert(&mut self, version: ApiVersion) -> result::Result<(), &mut u32> {
match self
.0
.binary_search_by_key(&version.major, |version| version.major)
{
Ok(found_idx) => Err(self
.0
.get_mut(found_idx)
.map(|v| &mut v.minor)
.expect("element must exist - just checked")),
Err(insert_idx) => {
self.0.insert(insert_idx, version);
Ok(())
}
}
}
pub(crate) fn get_by_major(&self, major: u32) -> Option<ApiVersion> {
self.0
.binary_search_by_key(&major, |version| version.major)
.ok()
.map(|index| {
self.0
.get(index)
.copied()
.expect("Must exist because binary_search_by_key told us so")
})
}
}
impl<'de> Deserialize<'de> for MultiApiVersion {
fn deserialize<D>(deserializer: D) -> result::Result<Self, D::Error>
where
D: serde::de::Deserializer<'de>,
{
use serde::de::Error;
let inner = Vec::<ApiVersion>::deserialize(deserializer)?;
let ret = Self(inner);
if !ret.is_consistent() {
return Err(D::Error::custom(
"Invalid MultiApiVersion value: inconsistent",
));
}
Ok(ret)
}
}
pub struct MultiApiVersionIter<'a>(std::slice::Iter<'a, ApiVersion>);
impl Iterator for MultiApiVersionIter<'_> {
type Item = ApiVersion;
fn next(&mut self) -> Option<Self::Item> {
self.0.next().copied()
}
}
impl<'a> IntoIterator for &'a MultiApiVersion {
type Item = ApiVersion;
type IntoIter = MultiApiVersionIter<'a>;
fn into_iter(self) -> Self::IntoIter {
self.iter()
}
}
impl FromIterator<ApiVersion> for Result<MultiApiVersion, ApiVersion> {
fn from_iter<T: IntoIterator<Item = ApiVersion>>(iter: T) -> Self {
let mut s = MultiApiVersion::new();
for version in iter {
if s.try_insert(version).is_err() {
return Err(version);
}
}
Ok(s)
}
}
#[test]
fn api_version_multi_sanity() {
let mut mav = MultiApiVersion::new();
assert_eq!(mav.try_insert(ApiVersion { major: 2, minor: 3 }), Ok(()));
assert_eq!(mav.get_by_major(0), None);
assert_eq!(mav.get_by_major(2), Some(ApiVersion { major: 2, minor: 3 }));
assert_eq!(
mav.try_insert(ApiVersion { major: 2, minor: 1 }),
Err(&mut 3)
);
*mav.try_insert(ApiVersion { major: 2, minor: 2 })
.expect_err("must be error, just like one line above") += 1;
assert_eq!(mav.try_insert(ApiVersion { major: 1, minor: 2 }), Ok(()));
assert_eq!(mav.try_insert(ApiVersion { major: 3, minor: 4 }), Ok(()));
assert_eq!(
mav.try_insert(ApiVersion { major: 2, minor: 0 }),
Err(&mut 4)
);
assert_eq!(mav.get_by_major(5), None);
assert_eq!(mav.get_by_major(3), Some(ApiVersion { major: 3, minor: 4 }));
debug_assert!(mav.is_consistent());
}
#[test]
fn api_version_multi_from_iter_sanity() {
assert!(result::Result::<MultiApiVersion, ApiVersion>::from_iter([]).is_ok());
assert!(
result::Result::<MultiApiVersion, ApiVersion>::from_iter([ApiVersion {
major: 0,
minor: 0
}])
.is_ok()
);
assert!(
result::Result::<MultiApiVersion, ApiVersion>::from_iter([
ApiVersion { major: 0, minor: 1 },
ApiVersion { major: 1, minor: 2 }
])
.is_ok()
);
assert!(
result::Result::<MultiApiVersion, ApiVersion>::from_iter([
ApiVersion { major: 0, minor: 1 },
ApiVersion { major: 1, minor: 2 },
ApiVersion { major: 0, minor: 1 },
])
.is_err()
);
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Encodable, Decodable)]
pub struct SupportedCoreApiVersions {
pub core_consensus: CoreConsensusVersion,
pub api: MultiApiVersion,
}
impl SupportedCoreApiVersions {
pub fn get_minor_api_version(
&self,
core_consensus: CoreConsensusVersion,
major: u32,
) -> Option<u32> {
if self.core_consensus.major != core_consensus.major {
return None;
}
self.api.get_by_major(major).map(|v| {
debug_assert_eq!(v.major, major);
v.minor
})
}
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Encodable, Decodable)]
pub struct SupportedModuleApiVersions {
pub core_consensus: CoreConsensusVersion,
pub module_consensus: ModuleConsensusVersion,
pub api: MultiApiVersion,
}
impl SupportedModuleApiVersions {
pub fn from_raw(core: (u32, u32), module: (u32, u32), api_versions: &[(u32, u32)]) -> Self {
Self {
core_consensus: CoreConsensusVersion::new(core.0, core.1),
module_consensus: ModuleConsensusVersion::new(module.0, module.1),
api: api_versions
.iter()
.copied()
.map(|(major, minor)| ApiVersion { major, minor })
.collect::<result::Result<MultiApiVersion, ApiVersion>>()
.expect(
"overlapping (conflicting) api versions when declaring SupportedModuleApiVersions",
),
}
}
pub fn get_minor_api_version(
&self,
core_consensus: CoreConsensusVersion,
module_consensus: ModuleConsensusVersion,
major: u32,
) -> Option<u32> {
if self.core_consensus.major != core_consensus.major {
return None;
}
if self.module_consensus.major != module_consensus.major {
return None;
}
self.api.get_by_major(major).map(|v| {
debug_assert_eq!(v.major, major);
v.minor
})
}
}
#[derive(Debug, Clone, Eq, PartialEq, Serialize, Deserialize, Decodable, Encodable)]
pub struct SupportedApiVersionsSummary {
pub core: SupportedCoreApiVersions,
pub modules: BTreeMap<ModuleInstanceId, SupportedModuleApiVersions>,
}
#[derive(Serialize)]
pub struct ServerApiVersionsSummary {
pub core: MultiApiVersion,
pub modules: BTreeMap<ModuleKind, MultiApiVersion>,
}
#[derive(Serialize)]
pub struct ServerDbVersionsSummary {
pub modules: BTreeMap<ModuleKind, DatabaseVersion>,
}