aimdb_core/session/mod.rs
1//! Connector-session substrate — the shared machinery for transport-based
2//! remote access.
3//!
4//! Three layers: transport ([`Connection`]/[`Listener`]/[`Dialer`]), codec
5//! ([`EnvelopeCodec`]), and dispatch ([`Dispatch`]/[`Session`]), over a
6//! role-neutral [`Inbound`]/[`Outbound`] message set shared by the reactive
7//! server engine (`serve`/`run_session`) and the proactive client engine
8//! (`run_client`/`pump_client`). Data-plane connectors use `pump_sink`/
9//! `pump_source` over the [`Source`] / [`Connector`](crate::transport::Connector)
10//! capabilities.
11//!
12//! All contracts are `dyn`-safe and compile on `std` and `no_std + alloc`.
13
14use alloc::{boxed::Box, string::String, sync::Arc, vec::Vec};
15use core::future::Future;
16use core::pin::Pin;
17
18use futures_core::Stream;
19
20// The engines are runtime-neutral (`futures` channels + the adapter's `RuntimeOps`
21// clock, no `tokio`/`embassy-*`), so they cross-compile to `no_std + alloc`.
22#[cfg(feature = "connector-session")]
23mod client;
24#[cfg(feature = "connector-session")]
25mod connector;
26#[cfg(feature = "connector-session")]
27mod endpoint;
28#[cfg(feature = "connector-session")]
29mod io;
30#[cfg(feature = "connector-session")]
31mod pump;
32#[cfg(feature = "connector-session")]
33mod server;
34
35// Concrete AimX protocol substrate. The transport lives in a separate connector
36// crate (`aimdb-uds-connector`) — core keeps the protocol plus the generic
37// [`SessionClientConnector`] / [`SessionServerConnector`] spine.
38#[cfg(all(feature = "connector-session", feature = "remote"))]
39pub mod aimx;
40
41mod topic_match;
42pub use topic_match::{is_wildcard, pattern_contains, topic_matches};
43
44#[cfg(feature = "connector-session")]
45pub use client::{pump_client, run_client, ClientConfig, ClientHandle};
46#[cfg(feature = "connector-session")]
47pub use connector::{SessionClientConnector, SessionServerConnector};
48#[cfg(feature = "connector-session")]
49pub use endpoint::{split_host_port, split_host_port_opt, EndpointError};
50#[cfg(feature = "connector-session")]
51pub use io::{
52 ByteRead, ByteStream, ByteWrite, Datagram, DatagramBinder, Delay, FrameFault, FramedConnection,
53 Framer, FramerFactory, FramingDialer, FramingListener, IoError, OneShot, OneShotDialer,
54 OneShotListener, StreamDialer, StreamListener,
55};
56#[cfg(feature = "connector-session")]
57pub use pump::{pump_sink, pump_source};
58#[cfg(feature = "connector-session")]
59pub use server::{run_session, serve, SessionConfig};
60
61// ===========================================================================
62// Shared aliases
63// ===========================================================================
64
65/// Boxed, `Send` future — the object-safe async return shape used throughout.
66pub type BoxFut<'a, T> = Pin<Box<dyn Future<Output = T> + Send + 'a>>;
67
68/// Boxed, `Send` stream — the reply shape of a subscription ([`Session::subscribe`]).
69pub type BoxStream<'a, T> = Pin<Box<dyn Stream<Item = T> + Send + 'a>>;
70
71/// A serialized record value, carried opaquely through the codec.
72///
73/// `Arc<[u8]>` so fan-out is a refcount bump. Bytes stay opaque until a handler
74/// inspects them.
75pub type Payload = Arc<[u8]>;
76
77/// One update delivered on a subscription stream (server [`Session::subscribe`]
78/// and client [`ClientHandle::subscribe`] alike).
79///
80/// `topic` names the concrete record that fired: `Some` on wildcard
81/// subscriptions, which fan in many records under one subscription id, and on
82/// any transport that tags every event; `None` on an exact-topic subscription.
83#[derive(Clone, Debug)]
84pub struct SubUpdate {
85 /// Concrete record topic that fired, when the producer side tags it.
86 pub topic: Option<Arc<str>>,
87 /// The serialized record value.
88 pub data: Payload,
89 /// Count of skipped records
90 pub skipped: u64,
91 /// Set on the last update of the late-join snapshot burst, carrying its total `skipped`.
92 ///
93 /// Only set when the burst produced a final update to carry it — an empty
94 /// burst, an exact-topic subscribe, or a tail that failed to encode end
95 /// without one — so its absence says nothing.
96 pub snapshot_end: bool,
97}
98
99impl SubUpdate {
100 /// An untagged update (exact-topic subscription).
101 pub fn new(data: Payload) -> Self {
102 Self {
103 topic: None,
104 data,
105 skipped: 0,
106 snapshot_end: false,
107 }
108 }
109
110 /// A topic-tagged update (wildcard / bus subscription).
111 pub fn tagged(topic: Arc<str>, data: Payload) -> Self {
112 Self {
113 topic: Some(topic),
114 data,
115 skipped: 0,
116 snapshot_end: false,
117 }
118 }
119
120 /// Mark that `n` updates were lost on this subscription immediately before
121 /// this one (`0` leaves the update lossless).
122 pub fn with_skipped(mut self, n: u64) -> Self {
123 self.skipped = n;
124 self
125 }
126
127 /// Mark this update as the last of the late-join snapshot burst (see
128 /// [`snapshot_end`](Self::snapshot_end)).
129 pub fn with_snapshot_end(mut self) -> Self {
130 self.snapshot_end = true;
131 self
132 }
133}
134
135/// Result of a transport-layer operation.
136pub type TransportResult<T> = Result<T, TransportError>;
137
138// ===========================================================================
139// Supporting types (stubs — sufficient for the signatures to compile)
140// ===========================================================================
141
142/// Remote-peer metadata carried by a [`Connection`].
143///
144/// A neutral [`peer_addr`](Self::peer_addr) plus a type-erased
145/// [`ext`](Self::ext) slot a connector fills with its own resolved identity
146/// (e.g. WS attaches `ClientInfo` at the HTTP upgrade), keeping core
147/// connector-agnostic. Downcast `ext` with [`ext_as`](Self::ext_as).
148#[derive(Clone, Default)]
149#[non_exhaustive]
150pub struct PeerInfo {
151 /// Remote address, if the transport exposes one.
152 pub peer_addr: Option<String>,
153 /// Connector-resolved identity, type-erased so core need not know the
154 /// connector's auth types. Downcast with [`ext_as`](Self::ext_as).
155 pub ext: Option<Arc<dyn core::any::Any + Send + Sync>>,
156}
157
158impl PeerInfo {
159 /// Attach a connector-resolved identity (consumed by [`Dispatch::authenticate`]).
160 pub fn with_ext(mut self, ext: Arc<dyn core::any::Any + Send + Sync>) -> Self {
161 self.ext = Some(ext);
162 self
163 }
164
165 /// Downcast the [`ext`](Self::ext) identity to a concrete connector type.
166 pub fn ext_as<T: core::any::Any + Send + Sync>(&self) -> Option<Arc<T>> {
167 self.ext.clone()?.downcast::<T>().ok()
168 }
169}
170
171impl core::fmt::Debug for PeerInfo {
172 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
173 f.debug_struct("PeerInfo")
174 .field("peer_addr", &self.peer_addr)
175 .field("ext", &self.ext.as_ref().map(|_| "<opaque>"))
176 .finish()
177 }
178}
179
180/// The authenticated session context produced by [`Dispatch::authenticate`] and
181/// threaded into [`Dispatch::open`].
182///
183/// Carries the resolved principal as a type-erased [`ext`](Self::ext) for
184/// per-operation authorization in the [`Session`]; downcast with
185/// [`ext_as`](Self::ext_as). Connectors that don't authenticate leave it `None`.
186#[derive(Clone, Default)]
187#[non_exhaustive]
188pub struct SessionCtx {
189 /// The resolved principal, type-erased. Downcast with [`ext_as`](Self::ext_as).
190 pub ext: Option<Arc<dyn core::any::Any + Send + Sync>>,
191}
192
193impl SessionCtx {
194 /// Build a context carrying a connector-resolved principal.
195 pub fn with_ext(ext: Arc<dyn core::any::Any + Send + Sync>) -> Self {
196 Self { ext: Some(ext) }
197 }
198
199 /// Downcast the [`ext`](Self::ext) principal to a concrete connector type.
200 pub fn ext_as<T: core::any::Any + Send + Sync>(&self) -> Option<Arc<T>> {
201 self.ext.clone()?.downcast::<T>().ok()
202 }
203}
204
205impl core::fmt::Debug for SessionCtx {
206 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
207 f.debug_struct("SessionCtx")
208 .field("ext", &self.ext.as_ref().map(|_| "<opaque>"))
209 .finish()
210 }
211}
212
213/// Per-session resource bounds consumed by the engines.
214#[derive(Debug, Clone)]
215pub struct SessionLimits {
216 /// Maximum concurrently served connections.
217 pub max_connections: usize,
218 /// Maximum live subscriptions per connection.
219 pub max_subs_per_connection: usize,
220}
221
222impl Default for SessionLimits {
223 fn default() -> Self {
224 Self {
225 max_connections: 16,
226 max_subs_per_connection: 32,
227 }
228 }
229}
230
231/// Transport-layer failure (Layer 1).
232#[derive(Debug, Clone, PartialEq, Eq)]
233#[non_exhaustive]
234pub enum TransportError {
235 /// The connection was closed or reset by the peer.
236 Closed,
237 /// An underlying I/O operation failed.
238 Io,
239 /// The byte stream could not be framed, and the framer cannot resynchronize.
240 Framing,
241 /// The transport's one endpoint resource is already in use — a second dial
242 /// on a single-socket transport while the first connection is live.
243 Busy,
244}
245
246/// Envelope-codec failure — a frame could not be decoded/encoded.
247#[derive(Debug, Clone, PartialEq, Eq)]
248#[non_exhaustive]
249pub enum CodecError {
250 /// The frame was not valid for this envelope format.
251 Malformed,
252}
253
254/// Dispatch-layer (application) failure for `call` / `subscribe` / `write`.
255#[derive(Debug, Clone, PartialEq, Eq)]
256#[non_exhaustive]
257pub enum RpcError {
258 /// No such method or topic.
259 NotFound,
260 /// The caller lacks permission for this operation.
261 Denied,
262 /// The handler failed.
263 Internal,
264 /// The peer's declared protocol version is incompatible with this server's
265 /// `PROTOCOL_VERSION`. Raised at the handshake, before `hello` completes.
266 VersionMismatch,
267}
268
269/// Authentication failure raised by [`Dispatch::authenticate`].
270#[derive(Debug, Clone, PartialEq, Eq)]
271#[non_exhaustive]
272pub enum AuthError {
273 /// Credentials were missing or rejected.
274 Unauthorized,
275}
276
277// ===========================================================================
278// Logical message set — role-neutral: the server's `Inbound` is the client's
279// outbound and vice versa.
280// ===========================================================================
281
282/// A logical request arriving over a [`Connection`] (what the server receives).
283pub enum Inbound {
284 /// An RPC call expecting a single [`Outbound::Reply`].
285 Request {
286 /// Correlation id, echoed in the reply.
287 id: u64,
288 /// Method name (e.g. `"record.set"`, `"query"`).
289 method: String,
290 /// Unparsed method parameters.
291 params: Payload,
292 },
293 /// Open a subscription producing many [`Outbound::Event`]s.
294 Subscribe {
295 /// Correlation id for the subscription handshake.
296 id: u64,
297 /// Topic to subscribe to.
298 topic: String,
299 },
300 /// Close a previously opened subscription.
301 Unsubscribe {
302 /// Subscription id to cancel.
303 sub: String,
304 },
305 /// A fire-and-forget write (no reply).
306 Write {
307 /// Destination topic.
308 topic: String,
309 /// Unparsed record value.
310 payload: Payload,
311 },
312 /// Keepalive.
313 Ping,
314}
315
316/// A logical message sent back over a [`Connection`] (what the server emits).
317pub enum Outbound<'a> {
318 /// Reply to an [`Inbound::Request`].
319 Reply {
320 /// Correlation id of the originating request.
321 id: u64,
322 /// The result, or an [`RpcError`].
323 result: Result<Payload, RpcError>,
324 },
325 /// A subscription update.
326 Event {
327 /// Subscription id this event belongs to.
328 sub: &'a str,
329 /// Monotonic sequence number.
330 seq: u64,
331 /// Concrete record topic that fired, when the subscription tags it
332 /// (wildcard subscriptions fan in many records — see [`SubUpdate`]).
333 topic: Option<&'a str>,
334 /// Unparsed record value.
335 data: Payload,
336 },
337 /// An initial snapshot emitted when a subscription opens (late-join).
338 Snapshot {
339 /// Subscription id the snapshot belongs to, so the client demux routes
340 /// it like an [`Event`](Outbound::Event) (a wildcard subscription's
341 /// snapshots carry topics that don't string-match the pattern key).
342 sub: &'a str,
343 /// Monotonic sequence number, in the **same space** as this
344 /// subscription's [`Event`](Outbound::Event)s: the burst is numbered
345 /// `1..=N` and the first event continues at `N + 1`, so a lost snapshot
346 /// surfaces in the next update's [`SubUpdate::skipped`].
347 seq: u64,
348 /// Set on the last snapshot of the burst, so the loss total lands on an
349 /// update the subscriber is guaranteed to see. The client engine
350 /// reserves a sink slot for this frame.
351 last: bool,
352 /// Topic the snapshot is for.
353 topic: &'a str,
354 /// Unparsed record value.
355 data: Payload,
356 },
357 /// An explicit acknowledgement that a subscription opened. Emitted by
358 /// [`run_session`] only when [`SessionConfig::acks_subscribe`] is set.
359 /// The `sub` is the subscription's routing id — the same value that tags its
360 /// [`Event`](Outbound::Event)s.
361 Subscribed {
362 /// Subscription id that was opened.
363 sub: &'a str,
364 },
365 /// Keepalive response.
366 Pong,
367}
368
369// ===========================================================================
370// Layer 1 — transport. Framing lives in the transport: `recv` returns one
371// logical frame. `Dialer` is the client-side dual of `Listener`.
372// ===========================================================================
373
374/// A framed, bidirectional pipe — role-neutral (yielded by either
375/// [`Listener::accept`] or [`Dialer::connect`]).
376pub trait Connection: Send {
377 /// Receive one logical frame. `Ok(None)` signals the peer closed.
378 fn recv(&mut self) -> BoxFut<'_, TransportResult<Option<Vec<u8>>>>;
379
380 /// Send one logical frame.
381 fn send<'a>(&'a mut self, frame: &'a [u8]) -> BoxFut<'a, TransportResult<()>>;
382
383 /// Peer metadata (remote addr, headers, pre-resolved auth).
384 fn peer(&self) -> &PeerInfo;
385}
386
387/// The accepting (server) side — produces [`Connection`]s we did not initiate.
388pub trait Listener: Send {
389 /// Accept the next inbound connection.
390 fn accept(&mut self) -> BoxFut<'_, TransportResult<Box<dyn Connection>>>;
391}
392
393/// The initiating (client) side — the dual of [`Listener`]; dials out and
394/// produces the same [`Connection`].
395pub trait Dialer: Send {
396 /// Open a connection to the configured remote.
397 fn connect(&self) -> BoxFut<'_, TransportResult<Box<dyn Connection>>>;
398}
399
400/// A boxed dialer is itself a [`Dialer`], so a runtime-selected
401/// `Box<dyn Dialer>` — from a `scheme://` URL resolver, say — can be passed
402/// where [`run_client`]`<D: Dialer>` is expected. The `Send` supertrait makes
403/// the box `Send + 'static`.
404impl Dialer for Box<dyn Dialer> {
405 fn connect(&self) -> BoxFut<'_, TransportResult<Box<dyn Connection>>> {
406 (**self).connect()
407 }
408}
409
410// ===========================================================================
411// Layer 3 — dispatch. RPC and streaming unify in one per-connection role with
412// three reply cardinalities: `call` (one) / `subscribe` (many) / `write` (none).
413// Split across two traits: the shared, immutable [`Dispatch`] (one
414// `Arc<dyn Dispatch>` per server) and the per-connection, `&mut`-threaded
415// [`Session`] that can hold mutable state without a lock.
416// ===========================================================================
417
418/// The shared application dispatch: authenticate a connection, then open a
419/// per-connection [`Session`]. One `Arc<dyn Dispatch>` is shared across every
420/// accepted connection, so it stays `Send + Sync` and behind `&self`.
421pub trait Dispatch: Send + Sync {
422 /// Resolve a [`SessionCtx`] from peer metadata and/or the first frame
423 /// (a pre-resolved identity in [`PeerInfo`], or an in-band Hello in `first`).
424 fn authenticate<'a>(
425 &'a self,
426 peer: &'a PeerInfo,
427 first: Option<&'a [u8]>,
428 ) -> BoxFut<'a, Result<SessionCtx, AuthError>>;
429
430 /// Open the per-connection [`Session`] once, after [`authenticate`](Self::authenticate). It owns
431 /// the connection's mutable dispatch state that the shared `Arc<Self>` cannot
432 /// hold behind `&self`; the engine threads `&mut` into it.
433 fn open(&self, ctx: &SessionCtx) -> Box<dyn Session>;
434}
435
436/// The per-connection session: serves calls, subscriptions, and writes for one
437/// accepted [`Connection`]. The engine owns the `Box<dyn Session>` and threads
438/// `&mut self` into each method, so it can hold per-connection state without a
439/// lock; the shared, immutable role stays on [`Dispatch`].
440pub trait Session: Send {
441 /// One-shot RPC: one request → one reply.
442 fn call<'a>(
443 &'a mut self,
444 method: &'a str,
445 params: Payload,
446 ) -> BoxFut<'a, Result<Payload, RpcError>>;
447
448 /// Open a subscription yielding many [`SubUpdate`]s. The stream is `'static`
449 /// (it captures cloned handles) so it outlives the `&mut` borrow and lives in
450 /// the engine. Async so a connector can await per-operation authorization.
451 /// Defaulted to [`RpcError::NotFound`] for dispatches with no streaming.
452 fn subscribe<'a>(
453 &'a mut self,
454 topic: &'a str,
455 ) -> BoxFut<'a, Result<BoxStream<'static, SubUpdate>, RpcError>> {
456 let _ = topic;
457 Box::pin(async { Err(RpcError::NotFound) })
458 }
459
460 /// Late-join snapshots: one `(topic, value)` per record covered by `topic`
461 /// (several for a wildcard subscription), each emitted as an
462 /// [`Outbound::Snapshot`] right after a successful
463 /// [`subscribe`](Session::subscribe) and before the first event. Defaulted
464 /// to empty (no snapshots).
465 ///
466 /// The engine numbers the burst `1..=N` and starts the event stream at
467 /// `N + 1`, so a snapshot dropped in transit shows up as
468 /// [`SubUpdate::skipped`] on the next delivered update.
469 ///
470 /// The burst's last snapshot is flagged and reaches the subscriber as the
471 /// one update with [`SubUpdate::snapshot_end`] set, carrying the burst's
472 /// whole loss; the client engine reserves a sink slot so it survives a slow
473 /// consumer.
474 ///
475 /// **Do not block on that flag.** It is absent when `topic` matches no
476 /// records, and when the final snapshot fails to encode or its frame is
477 /// rejected — the flag rides that frame. Loss accounting survives all of
478 /// these; only the end-of-burst signal is lost.
479 fn snapshots(&mut self, topic: &str) -> Vec<(String, Payload)> {
480 let _ = topic;
481 Vec::new()
482 }
483
484 /// Fire-and-forget write: no reply. Routes through the producer/arbiter path,
485 /// so single-writer-per-key stays intact.
486 fn write<'a>(
487 &'a mut self,
488 topic: &'a str,
489 payload: Payload,
490 ) -> BoxFut<'a, Result<(), RpcError>>;
491}
492
493/// The protocol-envelope codec: frame bytes ↔ the logical message set. Layered
494/// above (and nesting) the record-value `JsonCodec`, so the wire format stays
495/// pluggable; `decode`/`encode` keep the record value as an opaque [`Payload`]
496/// sliced from / spliced into the frame.
497///
498/// Symmetric, so one codec object serves both engines: `decode`/`encode` are the
499/// server direction (read requests / write replies) and
500/// [`encode_inbound`](EnvelopeCodec::encode_inbound) /
501/// [`decode_outbound`](EnvelopeCodec::decode_outbound) the client direction.
502pub trait EnvelopeCodec: Send + Sync {
503 /// Decode one frame into a logical [`Inbound`] message (server reads a request).
504 fn decode(&self, frame: &[u8]) -> Result<Inbound, CodecError>;
505
506 /// Encode a logical [`Outbound`] message, appending its bytes to `out`
507 /// (server writes a reply/event).
508 fn encode(&self, msg: Outbound<'_>, out: &mut Vec<u8>) -> Result<(), CodecError>;
509
510 /// Encode a logical [`Inbound`] message, appending its bytes to `out`
511 /// (client writes a request). The dual of [`decode`](EnvelopeCodec::decode).
512 fn encode_inbound(&self, msg: Inbound, out: &mut Vec<u8>) -> Result<(), CodecError>;
513
514 /// Decode one frame into a logical [`Outbound`] message (client reads a
515 /// reply/event). The dual of [`encode`](EnvelopeCodec::encode); the result
516 /// borrows the frame (`Outbound`'s `sub`/`topic` are `&str` slices into it).
517 fn decode_outbound<'a>(&self, frame: &'a [u8]) -> Result<Outbound<'a>, CodecError>;
518}
519
520// ===========================================================================
521// Data-plane capabilities — connectionless (an external library owns any
522// session). The outbound `Sink` is the canonical
523// [`Connector`](crate::transport::Connector); the inbound `Source` is below.
524// ===========================================================================
525
526/// External → AimDB data-plane: a stream of inbound frames, drained by
527/// `pump_source`.
528pub trait Source: Send {
529 /// Yield the next `(topic, payload)`, or `None` when the source is done.
530 fn next(&mut self) -> BoxFut<'_, Option<(String, Payload)>>;
531}
532
533// ===========================================================================
534// Taking each trait as `&dyn Trait` forces the dyn-compatibility check on all
535// targets, not just under `cargo test`.
536// ===========================================================================
537
538#[allow(dead_code, clippy::too_many_arguments)]
539fn _assert_object_safe(
540 _connection: &dyn Connection,
541 _listener: &dyn Listener,
542 _dialer: &dyn Dialer,
543 _dispatch: &dyn Dispatch,
544 _session: &dyn Session,
545 _codec: &dyn EnvelopeCodec,
546 _source: &dyn Source,
547) {
548}
549
550#[cfg(test)]
551mod tests {
552 use super::*;
553
554 struct MockConnection;
555 impl Connection for MockConnection {
556 fn recv(&mut self) -> BoxFut<'_, TransportResult<Option<Vec<u8>>>> {
557 unimplemented!()
558 }
559 fn send<'a>(&'a mut self, _frame: &'a [u8]) -> BoxFut<'a, TransportResult<()>> {
560 unimplemented!()
561 }
562 fn peer(&self) -> &PeerInfo {
563 unimplemented!()
564 }
565 }
566
567 struct MockListener;
568 impl Listener for MockListener {
569 fn accept(&mut self) -> BoxFut<'_, TransportResult<Box<dyn Connection>>> {
570 unimplemented!()
571 }
572 }
573
574 struct MockDialer;
575 impl Dialer for MockDialer {
576 fn connect(&self) -> BoxFut<'_, TransportResult<Box<dyn Connection>>> {
577 unimplemented!()
578 }
579 }
580
581 struct MockDispatch;
582 impl Dispatch for MockDispatch {
583 fn authenticate<'a>(
584 &'a self,
585 _peer: &'a PeerInfo,
586 _first: Option<&'a [u8]>,
587 ) -> BoxFut<'a, Result<SessionCtx, AuthError>> {
588 unimplemented!()
589 }
590 fn open(&self, _ctx: &SessionCtx) -> Box<dyn Session> {
591 unimplemented!()
592 }
593 }
594
595 struct MockSession;
596 impl Session for MockSession {
597 fn call<'a>(
598 &'a mut self,
599 _method: &'a str,
600 _params: Payload,
601 ) -> BoxFut<'a, Result<Payload, RpcError>> {
602 unimplemented!()
603 }
604 fn subscribe<'a>(
605 &'a mut self,
606 _topic: &'a str,
607 ) -> BoxFut<'a, Result<BoxStream<'static, SubUpdate>, RpcError>> {
608 unimplemented!()
609 }
610 fn write<'a>(
611 &'a mut self,
612 _topic: &'a str,
613 _payload: Payload,
614 ) -> BoxFut<'a, Result<(), RpcError>> {
615 unimplemented!()
616 }
617 }
618
619 struct MockCodec;
620 impl EnvelopeCodec for MockCodec {
621 fn decode(&self, _frame: &[u8]) -> Result<Inbound, CodecError> {
622 unimplemented!()
623 }
624 fn encode(&self, _msg: Outbound<'_>, _out: &mut Vec<u8>) -> Result<(), CodecError> {
625 unimplemented!()
626 }
627 fn encode_inbound(&self, _msg: Inbound, _out: &mut Vec<u8>) -> Result<(), CodecError> {
628 unimplemented!()
629 }
630 fn decode_outbound<'a>(&self, _frame: &'a [u8]) -> Result<Outbound<'a>, CodecError> {
631 unimplemented!()
632 }
633 }
634
635 struct MockSource;
636 impl Source for MockSource {
637 fn next(&mut self) -> BoxFut<'_, Option<(String, Payload)>> {
638 unimplemented!()
639 }
640 }
641
642 /// Every trait is `dyn`-usable.
643 #[test]
644 fn traits_are_object_safe() {
645 let _connection: Box<dyn Connection> = Box::new(MockConnection);
646 let _listener: Box<dyn Listener> = Box::new(MockListener);
647 let _dialer: Box<dyn Dialer> = Box::new(MockDialer);
648 let _dispatch: Box<dyn Dispatch> = Box::new(MockDispatch);
649 let _session: Box<dyn Session> = Box::new(MockSession);
650 let _codec: Box<dyn EnvelopeCodec> = Box::new(MockCodec);
651 let _source: Box<dyn Source> = Box::new(MockSource);
652 }
653
654 /// `Box<dyn Dialer>` satisfies the `Dialer` bound, so a runtime-selected
655 /// dialer can be passed where `D: Dialer` is expected.
656 #[test]
657 fn boxed_dialer_is_a_dialer() {
658 fn takes_dialer<D: Dialer + 'static>(_d: D) {}
659 let boxed: Box<dyn Dialer> = Box::new(MockDialer);
660 takes_dialer(boxed);
661 }
662}