use std::collections::HashSet;
#[cfg(any(feature = "protocol-2026-07-28", feature = "stateless"))]
use crate::protocol::PROTOCOL_VERSION_2026_07_28;
use crate::protocol::SUPPORTED_PROTOCOL_VERSIONS;
#[cfg(any(feature = "protocol-2026-07-28", feature = "stateless"))]
pub const COMPILED_PROTOCOL_VERSIONS: &[&str] =
&[PROTOCOL_VERSION_2026_07_28, "2025-11-25", "2025-03-26"];
#[cfg(not(any(feature = "protocol-2026-07-28", feature = "stateless")))]
pub const COMPILED_PROTOCOL_VERSIONS: &[&str] = SUPPORTED_PROTOCOL_VERSIONS;
pub fn is_protocol_version_compiled(version: &str) -> bool {
COMPILED_PROTOCOL_VERSIONS.contains(&version)
}
#[derive(Debug, Clone, PartialEq, Eq, thiserror::Error)]
pub enum ProtocolSupportError {
#[error("at least one protocol version must be enabled")]
Empty,
#[error(
"protocol version `{version}` is not compiled into this build; compiled versions: {compiled:?}"
)]
NotCompiled {
version: String,
compiled: &'static [&'static str],
},
#[error("protocol version `{0}` is configured more than once")]
Duplicate(String),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ProtocolSupport {
versions: Vec<String>,
}
impl ProtocolSupport {
pub fn compiled() -> Self {
Self {
versions: COMPILED_PROTOCOL_VERSIONS
.iter()
.map(|version| (*version).to_string())
.collect(),
}
}
pub fn stable() -> Self {
Self {
versions: SUPPORTED_PROTOCOL_VERSIONS
.iter()
.map(|version| (*version).to_string())
.collect(),
}
}
pub fn try_new<I, S>(versions: I) -> Result<Self, ProtocolSupportError>
where
I: IntoIterator<Item = S>,
S: Into<String>,
{
let mut configured = Vec::new();
let mut seen = HashSet::new();
for version in versions {
let version = version.into();
if !is_protocol_version_compiled(&version) {
return Err(ProtocolSupportError::NotCompiled {
version,
compiled: COMPILED_PROTOCOL_VERSIONS,
});
}
if !seen.insert(version.clone()) {
return Err(ProtocolSupportError::Duplicate(version));
}
configured.push(version);
}
if configured.is_empty() {
return Err(ProtocolSupportError::Empty);
}
Ok(Self {
versions: configured,
})
}
pub fn versions(&self) -> &[String] {
&self.versions
}
pub fn contains(&self, version: &str) -> bool {
self.versions.iter().any(|candidate| candidate == version)
}
pub fn preferred(&self) -> &str {
&self.versions[0]
}
}
impl Default for ProtocolSupport {
fn default() -> Self {
Self::compiled()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn stable_policy_excludes_opt_in_versions() {
let support = ProtocolSupport::stable();
assert_eq!(support.versions(), SUPPORTED_PROTOCOL_VERSIONS);
assert_eq!(support.preferred(), "2025-11-25");
}
#[test]
fn rejects_empty_duplicate_and_uncompiled_sets() {
assert_eq!(
ProtocolSupport::try_new(Vec::<String>::new()).unwrap_err(),
ProtocolSupportError::Empty
);
assert_eq!(
ProtocolSupport::try_new(["2025-11-25", "2025-11-25"]).unwrap_err(),
ProtocolSupportError::Duplicate("2025-11-25".to_string())
);
assert!(matches!(
ProtocolSupport::try_new(["2099-01-01"]).unwrap_err(),
ProtocolSupportError::NotCompiled { .. }
));
}
#[cfg(any(feature = "protocol-2026-07-28", feature = "stateless"))]
#[test]
fn opt_in_feature_adds_2026_implementation() {
assert!(is_protocol_version_compiled(PROTOCOL_VERSION_2026_07_28));
assert!(ProtocolSupport::compiled().contains(PROTOCOL_VERSION_2026_07_28));
}
#[cfg(not(any(feature = "protocol-2026-07-28", feature = "stateless")))]
#[test]
fn default_build_does_not_compile_2026_implementation() {
assert!(!is_protocol_version_compiled("2026-07-28"));
}
}