use std::collections::HashMap;
use crate::error::ClientError;
use crate::request::ProtocolRequest;
#[derive(Debug, Clone, Default)]
pub struct ApiVersionTable {
by_key: HashMap<i16, (i16, i16)>,
}
impl ApiVersionTable {
#[must_use]
pub fn from_entries(entries: impl IntoIterator<Item = (i16, i16, i16)>) -> Self {
let mut by_key = HashMap::new();
for (k, lo, hi) in entries {
by_key.insert(k, (lo, hi));
}
Self { by_key }
}
pub fn negotiate<R: ProtocolRequest>(&self) -> Result<i16, ClientError> {
let api_key = R::API_KEY;
let client_min = R::MIN_VERSION;
let client_max = R::MAX_VERSION;
let (broker_min, broker_max) = self.by_key.get(&api_key).copied().unwrap_or((0, 0));
let chosen = client_max.min(broker_max);
if chosen < client_min || chosen < broker_min {
return Err(ClientError::IncompatibleVersion {
api_key,
broker_min,
broker_max,
client_min,
client_max,
});
}
Ok(chosen)
}
#[must_use]
pub fn broker_range(&self, api_key: i16) -> Option<(i16, i16)> {
self.by_key.get(&api_key).copied()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.by_key.is_empty()
}
}
#[cfg(test)]
mod tests {
use super::*;
use assert2::assert;
use crabka_protocol::owned::api_versions_request::ApiVersionsRequest;
#[test]
fn negotiate_takes_min_of_max() {
let t = ApiVersionTable::from_entries([(
ApiVersionsRequest::API_KEY,
0,
ApiVersionsRequest::MAX_VERSION,
)]);
let _ = t.negotiate::<ApiVersionsRequest>().unwrap();
}
#[test]
fn negotiate_errors_when_disjoint() {
let t = ApiVersionTable::from_entries([(ApiVersionsRequest::API_KEY, 99, 100)]);
assert!(matches!(
t.negotiate::<ApiVersionsRequest>(),
Err(ClientError::IncompatibleVersion { .. })
));
}
#[test]
fn negotiate_picks_lowest_supported_when_broker_caps_low() {
let t = ApiVersionTable::from_entries([(ApiVersionsRequest::API_KEY, 0, 0)]);
assert!(t.negotiate::<ApiVersionsRequest>().unwrap() == 0);
}
}