phoenix_channel_runtime/
transport.rs1use futures::future::LocalBoxFuture;
2use thiserror::Error;
3
4#[derive(Clone, Debug, PartialEq, Eq)]
6pub enum WireMessage {
7 Text(String),
9 Binary(Vec<u8>),
11}
12
13#[derive(Clone, Debug, Eq, PartialEq)]
15pub struct TransportClose {
16 pub code: Option<u16>,
18 pub reason: String,
20 pub was_clean: bool,
22}
23
24#[derive(Clone, Debug, Eq, PartialEq)]
26pub struct TransportCloseRequest {
27 pub code: u16,
29 pub reason: String,
31}
32
33impl TransportCloseRequest {
34 pub fn new(code: u16, reason: impl Into<String>) -> Self {
36 Self {
37 code,
38 reason: reason.into(),
39 }
40 }
41}
42
43impl TransportClose {
44 pub fn new(code: Option<u16>, reason: impl Into<String>, was_clean: bool) -> Self {
46 Self {
47 code,
48 reason: reason.into(),
49 was_clean,
50 }
51 }
52
53 pub fn connection_ended() -> Self {
55 Self::new(
56 None,
57 "WebSocket connection ended without a close frame",
58 false,
59 )
60 }
61
62 pub fn should_reconnect(&self) -> bool {
64 !self.was_clean && !matches!(self.code, Some(1000 | 1008))
65 }
66}
67
68#[derive(Clone, Debug, PartialEq)]
70pub enum TransportEvent {
71 Message(WireMessage),
73 Closed(TransportClose),
75}
76
77pub trait Transport {
82 fn supports_binary(&self) -> bool {
84 true
85 }
86
87 fn send<'a>(
89 &'a mut self,
90 message: WireMessage,
91 ) -> LocalBoxFuture<'a, Result<(), TransportError>>;
92
93 fn receive<'a>(&'a mut self) -> LocalBoxFuture<'a, Result<TransportEvent, TransportError>>;
95
96 fn close<'a>(&'a mut self) -> LocalBoxFuture<'a, Result<(), TransportError>>;
98
99 fn close_with<'a>(
101 &'a mut self,
102 request: TransportCloseRequest,
103 ) -> LocalBoxFuture<'a, Result<(), TransportError>> {
104 let _ = request;
105 self.close()
106 }
107}
108
109#[derive(Clone, Copy, Debug, Eq, PartialEq)]
111pub enum TransportErrorKind {
112 Connect,
114 Send,
116 Receive,
118 Close,
120 Other,
122}
123
124impl std::fmt::Display for TransportErrorKind {
125 fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
126 formatter.write_str(match self {
127 Self::Connect => "connect",
128 Self::Send => "send",
129 Self::Receive => "receive",
130 Self::Close => "close",
131 Self::Other => "transport",
132 })
133 }
134}
135
136#[derive(Clone, Debug, Error, Eq, PartialEq)]
138#[error("{kind} error: {message}")]
139pub struct TransportError {
140 kind: TransportErrorKind,
141 message: String,
142}
143
144impl TransportError {
145 pub fn new(message: impl Into<String>) -> Self {
147 Self::with_kind(TransportErrorKind::Other, message)
148 }
149
150 pub fn with_kind(kind: TransportErrorKind, message: impl Into<String>) -> Self {
152 Self {
153 kind,
154 message: message.into(),
155 }
156 }
157
158 pub fn kind(&self) -> TransportErrorKind {
160 self.kind
161 }
162
163 pub fn message(&self) -> &str {
165 &self.message
166 }
167}
168
169#[cfg(test)]
170mod tests {
171 use super::*;
172
173 #[test]
174 fn reconnects_only_after_abnormal_reconnectable_closes() {
175 assert!(!TransportClose::new(Some(1000), "normal", true).should_reconnect());
176 assert!(!TransportClose::new(Some(1008), "policy", false).should_reconnect());
177 assert!(TransportClose::new(Some(1006), "abnormal", false).should_reconnect());
178 assert!(TransportClose::connection_ended().should_reconnect());
179 }
180}