use std::sync::Arc;
use crate::{driver::transport::EndpointKey, options::Region};
use url::Url;
#[derive(Debug, PartialEq, Eq, Hash)]
struct CosmosEndpointData {
region: Option<Region>,
gateway_url: Url,
gateway20_url: Option<Url>,
endpoint_key: EndpointKey,
}
#[derive(Clone, Debug)]
pub(crate) struct CosmosEndpoint(Arc<CosmosEndpointData>);
impl PartialEq for CosmosEndpoint {
fn eq(&self, other: &Self) -> bool {
*self.0 == *other.0
}
}
impl Eq for CosmosEndpoint {}
impl std::hash::Hash for CosmosEndpoint {
fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
self.0.hash(state);
}
}
impl CosmosEndpoint {
pub fn global(url: Url) -> Self {
let endpoint_key = EndpointKey::try_from(&url)
.expect("CosmosEndpoint URL must have a valid host and port");
Self(Arc::new(CosmosEndpointData {
region: None,
gateway_url: url,
gateway20_url: None,
endpoint_key,
}))
}
pub fn regional(region: Region, url: Url) -> Self {
let endpoint_key = EndpointKey::try_from(&url)
.expect("CosmosEndpoint URL must have a valid host and port");
Self(Arc::new(CosmosEndpointData {
region: Some(region),
gateway_url: url,
gateway20_url: None,
endpoint_key,
}))
}
pub fn regional_with_gateway20(region: Region, gateway_url: Url, gateway20_url: Url) -> Self {
let endpoint_key = EndpointKey::try_from(&gateway_url)
.expect("CosmosEndpoint URL must have a valid host and port");
Self(Arc::new(CosmosEndpointData {
region: Some(region),
gateway_url,
gateway20_url: Some(gateway20_url),
endpoint_key,
}))
}
pub fn region(&self) -> Option<&Region> {
self.0.region.as_ref()
}
pub fn url(&self) -> &Url {
&self.0.gateway_url
}
pub(crate) fn endpoint_key(&self) -> EndpointKey {
self.0.endpoint_key.clone()
}
#[cfg(test)]
pub fn gateway20_url(&self) -> Option<&Url> {
self.0.gateway20_url.as_ref()
}
pub(crate) fn uses_gateway20(&self, prefer_gateway20: bool) -> bool {
prefer_gateway20 && self.0.gateway20_url.is_some()
}
pub(crate) fn selected_url(&self, prefer_gateway20: bool) -> &Url {
if prefer_gateway20 {
if let Some(url) = &self.0.gateway20_url {
return url;
}
}
&self.0.gateway_url
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub(crate) struct LocationIndex {
index: usize,
generation: u64,
}
impl LocationIndex {
pub fn initial(generation: u64) -> Self {
Self {
index: 0,
generation,
}
}
pub fn next(self, list_len: usize) -> Self {
debug_assert!(list_len > 0, "endpoint list should never be empty");
if list_len == 0 {
return self;
}
Self {
index: (self.index + 1) % list_len,
generation: self.generation,
}
}
pub fn next_for_generation(self, list_len: usize, generation: u64) -> Self {
debug_assert!(list_len > 0, "endpoint list should never be empty");
if list_len == 0 {
return self;
}
if self.generation == generation {
return self.next(list_len);
}
let base_index = 0;
Self {
index: (base_index + 1) % list_len,
generation,
}
}
pub fn index(self) -> usize {
self.index
}
pub fn is_current(self, generation: u64) -> bool {
self.generation == generation
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) enum UnavailableReason {
WriteForbidden,
ServiceUnavailable,
InternalServerError,
TransportError,
}
#[cfg(test)]
mod tests {
use super::LocationIndex;
#[test]
fn next_for_generation_rebases_stale_index_before_advancing() {
let stale = LocationIndex::initial(3).next(3).next(3);
let next = stale.next_for_generation(3, 4);
assert_eq!(next.index(), 1);
assert!(next.is_current(4));
}
}