use liminal_protocol::client::{
ClientParticipantAggregate, ClientResponseCorrelation, DetachReplayRefusalReason,
DetachTransportFate, DetachTransportFateDecision, EstablishedConnectionTransportFate,
ExplicitReconnectAction, ReconnectAttemptDecision, ReconnectAttemptFate,
ReconnectAttemptFateDecision, ReconnectAttemptFateRefusalReason, ReconnectAttemptPermit,
ReconnectAttemptRefusalReason, ReconnectFreshEvent, ReconnectInProgressAttempt,
ReconnectPermitDecision, ReconnectPermitRefusalReason, record_attempt_fate,
record_explicit_reconnect, record_transport_fate, redeem_attempt, transport_fate,
};
use liminal_protocol::outcome::ReconnectState;
use super::core::TransportTerminal;
#[derive(Debug, PartialEq, Eq)]
pub enum OpenRequestDecision {
Authorized {
event: ReconnectFreshEvent,
},
Refused(OpenRequestRefusal),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum OpenRequestRefusal {
OpenAlreadyInProgress,
Permit(ReconnectPermitRefusalReason),
Redemption(ReconnectAttemptRefusalReason),
}
#[derive(Debug, PartialEq, Eq)]
pub enum AttemptFateOutcome {
Recorded {
state: ReconnectState,
},
Refused(AttemptFateRefusal),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum AttemptFateRefusal {
NoOpenInProgress,
Fate(ReconnectAttemptFateRefusalReason),
}
#[derive(Debug, PartialEq, Eq)]
pub enum LossRecordOutcome {
PermitRetained,
Refused(LossRecordRefusal),
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum LossRecordRefusal {
NotEstablished,
Permit(ReconnectPermitRefusalReason),
}
#[derive(Debug, PartialEq, Eq)]
pub enum DetachLossOutcome {
Parked,
Refused(DetachReplayRefusalReason),
}
#[derive(Debug)]
pub struct WebSocketAuthorityBinding {
aggregate: ClientParticipantAggregate,
permit: Option<ReconnectAttemptPermit>,
attempt: Option<ReconnectInProgressAttempt>,
last_loss_diagnostic: Option<TransportTerminal>,
}
impl WebSocketAuthorityBinding {
#[must_use]
pub const fn new() -> Self {
Self::with_aggregate(ClientParticipantAggregate::new())
}
#[must_use]
pub const fn with_aggregate(aggregate: ClientParticipantAggregate) -> Self {
Self {
aggregate,
permit: None,
attempt: None,
last_loss_diagnostic: None,
}
}
#[must_use]
pub const fn aggregate(&self) -> &ClientParticipantAggregate {
&self.aggregate
}
#[must_use]
pub const fn reconnect_state(&self) -> ReconnectState {
self.aggregate.reconnect().state()
}
#[must_use]
pub const fn last_loss_diagnostic(&self) -> Option<&TransportTerminal> {
self.last_loss_diagnostic.as_ref()
}
pub fn request_open(&mut self) -> OpenRequestDecision {
if self.attempt.is_some() {
return OpenRequestDecision::Refused(OpenRequestRefusal::OpenAlreadyInProgress);
}
let aggregate = self.take_aggregate();
let (aggregate, permit) = match self.permit.take() {
Some(retained) => (aggregate, retained),
None => {
match record_explicit_reconnect(aggregate, ExplicitReconnectAction::ReconnectNow) {
ReconnectPermitDecision::Permitted {
aggregate, permit, ..
} => (aggregate, permit),
ReconnectPermitDecision::Refused(refusal) => {
let reason = refusal.reason();
let (aggregate, _event) = refusal.into_parts();
self.aggregate = aggregate;
return OpenRequestDecision::Refused(OpenRequestRefusal::Permit(reason));
}
}
}
};
match redeem_attempt(aggregate, permit) {
ReconnectAttemptDecision::Started { aggregate, attempt } => {
let event = attempt.event();
self.aggregate = aggregate;
self.attempt = Some(attempt);
OpenRequestDecision::Authorized { event }
}
ReconnectAttemptDecision::Refused {
aggregate,
permit,
reason,
} => {
self.aggregate = aggregate;
self.permit = Some(permit);
OpenRequestDecision::Refused(OpenRequestRefusal::Redemption(reason))
}
}
}
pub fn connection_established(&mut self) -> AttemptFateOutcome {
self.record_open_fate(ReconnectAttemptFate::Connected)
}
pub fn open_failed(&mut self) -> AttemptFateOutcome {
self.record_open_fate(ReconnectAttemptFate::Failed)
}
fn record_open_fate(&mut self, fate: ReconnectAttemptFate) -> AttemptFateOutcome {
let Some(attempt) = self.attempt.take() else {
return AttemptFateOutcome::Refused(AttemptFateRefusal::NoOpenInProgress);
};
let aggregate = self.take_aggregate();
match record_attempt_fate(aggregate, attempt, fate) {
ReconnectAttemptFateDecision::Recorded(aggregate) => {
let state = aggregate.reconnect().state();
self.aggregate = aggregate;
AttemptFateOutcome::Recorded { state }
}
ReconnectAttemptFateDecision::Refused {
aggregate,
attempt,
reason,
..
} => {
self.aggregate = aggregate;
self.attempt = Some(attempt);
AttemptFateOutcome::Refused(AttemptFateRefusal::Fate(reason))
}
}
}
pub fn established_terminal(&mut self, terminal: &TransportTerminal) -> LossRecordOutcome {
if self.reconnect_state() != ReconnectState::Online {
return LossRecordOutcome::Refused(LossRecordRefusal::NotEstablished);
}
let aggregate = self.take_aggregate();
match record_transport_fate(aggregate, EstablishedConnectionTransportFate::Lost) {
ReconnectPermitDecision::Permitted {
aggregate, permit, ..
} => {
self.aggregate = aggregate;
self.permit = Some(permit);
self.last_loss_diagnostic = Some(*terminal);
LossRecordOutcome::PermitRetained
}
ReconnectPermitDecision::Refused(refusal) => {
let reason = refusal.reason();
let (aggregate, _event) = refusal.into_parts();
self.aggregate = aggregate;
LossRecordOutcome::Refused(LossRecordRefusal::Permit(reason))
}
}
}
pub fn detach_send_lost(
&mut self,
correlation: ClientResponseCorrelation,
) -> DetachLossOutcome {
let aggregate = self.take_aggregate();
match transport_fate(
aggregate,
correlation,
DetachTransportFate::ResponseUnavailable,
) {
DetachTransportFateDecision::Parked(applied) => {
self.aggregate = applied.into_aggregate();
DetachLossOutcome::Parked
}
DetachTransportFateDecision::Refused(refusal) => {
let reason = refusal.reason();
let (aggregate, _input) = refusal.into_parts();
self.aggregate = aggregate;
DetachLossOutcome::Refused(reason)
}
}
}
const fn take_aggregate(&mut self) -> ClientParticipantAggregate {
core::mem::replace(&mut self.aggregate, ClientParticipantAggregate::new())
}
}
impl Default for WebSocketAuthorityBinding {
fn default() -> Self {
Self::new()
}
}