use std::num::{NonZeroU32, NonZeroU64, NonZeroUsize};
use std::time::Duration;
const DEFAULT_OPEN_TIMEOUT: Duration = Duration::from_secs(10);
const DEFAULT_KEEPALIVE_SLACK: Duration = Duration::from_secs(3);
const DEFAULT_RETRY_INITIAL: Duration = Duration::from_millis(500);
const DEFAULT_RETRY_MAX: Duration = Duration::from_secs(30);
const DEFAULT_RETRY_ATTEMPTS: u32 = 8;
const DEFAULT_SESSION_EVENT_CAPACITY: usize = 256;
const DEFAULT_UPDATE_CAPACITY: usize = 1024;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
#[repr(u8)]
#[non_exhaustive]
pub enum Transport {
#[default]
WebSocket,
HttpStreaming,
HttpPolling,
}
impl Transport {
#[must_use]
#[inline]
pub const fn as_str(self) -> &'static str {
match self {
Self::WebSocket => "websocket",
Self::HttpStreaming => "http-streaming",
Self::HttpPolling => "http-polling",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct RetryPolicy {
initial_delay: Duration,
max_delay: Duration,
max_attempts: Option<NonZeroU32>,
}
impl Default for RetryPolicy {
fn default() -> Self {
Self {
initial_delay: DEFAULT_RETRY_INITIAL,
max_delay: DEFAULT_RETRY_MAX,
max_attempts: NonZeroU32::new(DEFAULT_RETRY_ATTEMPTS),
}
}
}
impl RetryPolicy {
#[must_use = "builders do nothing unless the result is used"]
pub const fn with_initial_delay(mut self, delay: Duration) -> Self {
self.initial_delay = delay;
self
}
#[must_use = "builders do nothing unless the result is used"]
pub const fn with_max_delay(mut self, delay: Duration) -> Self {
self.max_delay = delay;
self
}
#[must_use = "builders do nothing unless the result is used"]
pub const fn with_max_attempts(mut self, attempts: Option<NonZeroU32>) -> Self {
self.max_attempts = attempts;
self
}
#[must_use]
#[inline]
pub const fn initial_delay(&self) -> Duration {
self.initial_delay
}
#[must_use]
#[inline]
pub const fn max_delay(&self) -> Duration {
self.max_delay
}
#[must_use]
#[inline]
pub const fn max_attempts(&self) -> Option<NonZeroU32> {
self.max_attempts
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct ConnectionOptions {
open_timeout: Duration,
keepalive_slack: Duration,
keepalive_hint: Option<Duration>,
inactivity_commitment: Option<Duration>,
send_sync: bool,
content_length: Option<NonZeroU64>,
retry: RetryPolicy,
session_event_capacity: NonZeroUsize,
update_capacity: NonZeroUsize,
}
impl Default for ConnectionOptions {
fn default() -> Self {
Self {
open_timeout: DEFAULT_OPEN_TIMEOUT,
keepalive_slack: DEFAULT_KEEPALIVE_SLACK,
keepalive_hint: None,
inactivity_commitment: None,
send_sync: true,
content_length: None,
retry: RetryPolicy::default(),
session_event_capacity: NonZeroUsize::new(DEFAULT_SESSION_EVENT_CAPACITY)
.unwrap_or(NonZeroUsize::MIN),
update_capacity: NonZeroUsize::new(DEFAULT_UPDATE_CAPACITY)
.unwrap_or(NonZeroUsize::MIN),
}
}
}
impl ConnectionOptions {
#[must_use = "builders do nothing unless the result is used"]
pub const fn with_open_timeout(mut self, timeout: Duration) -> Self {
self.open_timeout = timeout;
self
}
#[must_use = "builders do nothing unless the result is used"]
pub const fn with_keepalive_slack(mut self, slack: Duration) -> Self {
self.keepalive_slack = slack;
self
}
#[must_use = "builders do nothing unless the result is used"]
pub const fn with_keepalive_hint(mut self, interval: Option<Duration>) -> Self {
self.keepalive_hint = interval;
self
}
#[must_use = "builders do nothing unless the result is used"]
pub const fn with_inactivity_commitment(mut self, interval: Option<Duration>) -> Self {
self.inactivity_commitment = interval;
self
}
#[must_use = "builders do nothing unless the result is used"]
pub const fn with_send_sync(mut self, enabled: bool) -> Self {
self.send_sync = enabled;
self
}
#[must_use = "builders do nothing unless the result is used"]
pub const fn with_content_length(mut self, bytes: Option<NonZeroU64>) -> Self {
self.content_length = bytes;
self
}
#[must_use = "builders do nothing unless the result is used"]
pub const fn with_retry(mut self, retry: RetryPolicy) -> Self {
self.retry = retry;
self
}
#[must_use = "builders do nothing unless the result is used"]
pub const fn with_session_event_capacity(mut self, capacity: NonZeroUsize) -> Self {
self.session_event_capacity = capacity;
self
}
#[must_use = "builders do nothing unless the result is used"]
pub const fn with_update_capacity(mut self, capacity: NonZeroUsize) -> Self {
self.update_capacity = capacity;
self
}
#[must_use]
#[inline]
pub const fn open_timeout(&self) -> Duration {
self.open_timeout
}
#[must_use]
#[inline]
pub const fn keepalive_slack(&self) -> Duration {
self.keepalive_slack
}
#[must_use]
#[inline]
pub const fn keepalive_hint(&self) -> Option<Duration> {
self.keepalive_hint
}
#[must_use]
#[inline]
pub const fn inactivity_commitment(&self) -> Option<Duration> {
self.inactivity_commitment
}
#[must_use]
#[inline]
pub const fn send_sync(&self) -> bool {
self.send_sync
}
#[must_use]
#[inline]
pub const fn content_length(&self) -> Option<NonZeroU64> {
self.content_length
}
#[must_use]
#[inline]
pub const fn retry(&self) -> RetryPolicy {
self.retry
}
#[must_use]
#[inline]
pub const fn session_event_capacity(&self) -> NonZeroUsize {
self.session_event_capacity
}
#[must_use]
#[inline]
pub const fn update_capacity(&self) -> NonZeroUsize {
self.update_capacity
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_options_defaults_are_the_documented_ones() {
let options = ConnectionOptions::default();
assert_eq!(options.open_timeout(), Duration::from_secs(10));
assert_eq!(options.keepalive_slack(), Duration::from_secs(3));
assert_eq!(options.keepalive_hint(), None);
assert_eq!(options.inactivity_commitment(), None);
assert!(options.send_sync());
assert_eq!(options.content_length(), None);
}
#[test]
fn test_retry_defaults_are_the_documented_ones() {
let retry = RetryPolicy::default();
assert_eq!(retry.initial_delay(), Duration::from_millis(500));
assert_eq!(retry.max_delay(), Duration::from_secs(30));
assert_eq!(retry.max_attempts().map(NonZeroU32::get), Some(8));
}
#[test]
fn test_retry_max_attempts_can_be_removed() {
let retry = RetryPolicy::default().with_max_attempts(None);
assert_eq!(retry.max_attempts(), None);
}
#[test]
fn test_options_setters_round_trip() {
let options = ConnectionOptions::default()
.with_open_timeout(Duration::from_secs(4))
.with_keepalive_slack(Duration::from_secs(1))
.with_keepalive_hint(Some(Duration::from_secs(5)))
.with_inactivity_commitment(Some(Duration::from_secs(20)))
.with_send_sync(false)
.with_content_length(NonZeroU64::new(1_000_000));
assert_eq!(options.open_timeout(), Duration::from_secs(4));
assert_eq!(options.keepalive_slack(), Duration::from_secs(1));
assert_eq!(options.keepalive_hint(), Some(Duration::from_secs(5)));
assert_eq!(
options.inactivity_commitment(),
Some(Duration::from_secs(20))
);
assert!(!options.send_sync());
assert_eq!(
options.content_length().map(NonZeroU64::get),
Some(1_000_000)
);
}
#[test]
fn test_transport_default_is_websocket() {
assert_eq!(Transport::default(), Transport::WebSocket);
assert_eq!(Transport::default().as_str(), "websocket");
}
#[test]
fn test_transport_variants_name_themselves() {
assert_eq!(Transport::HttpStreaming.as_str(), "http-streaming");
assert_eq!(Transport::HttpPolling.as_str(), "http-polling");
}
}