battler_wamp/lib.rs
1//! # battler-wamp
2//!
3//! **battler-wamp** is an implementation of the **Web Application Message Protocol** (WAMP) for
4//! Rust.
5//!
6//! The library implements the WAMP protocol for both routers and peers (a.k.a., servers and
7//! clients).
8//!
9//! The library uses [`tokio`](https://tokio.rs) as its asynchronous runtime, and is ready for
10//! use on top of WebSocket streams.
11//!
12//! For writing peers that desire strongly-typed messaging (including procedure calls and pub/sub
13//! events), use [`battler-wamprat`](https://crates.io/crates/battler-wamprat).
14//!
15//! ## What is WAMP?
16//!
17//! **WAMP** is an open standard, routed protocol that provides two messaging patterns: Publish &
18//! Subscribe and routed Remote Procedure Calls. It is intended to connect application components in
19//! distributed applications. WAMP uses WebSocket as its default transport, but it can be
20//! transmitted via any other protocol that allows for ordered, reliable, bi-directional, and
21//! message-oriented communications.
22//!
23//! The WAMP protocol specification is described [here](https://wamp-proto.org/spec.html).
24//!
25//! ## Routers
26//!
27//! WAMP peers talk to one another by establishing a session on a shared realm through a shared
28//! router.
29//!
30//! Spinning up a router with `battler-wamp` is incredibly easy. Configure the router through a
31//! [`RouterConfig`][`crate::router::RouterConfig`] and construct a
32//! [`Router`][`crate::router::Router`] object directly.
33//!
34//! If you are working with WebSocket connections, the
35//! [`new_web_socket_router`][`crate::router::new_web_socket_router`] utility function sets up
36//! the proper modules for convenience
37//!
38//! A router is a full-fledged server that manages resources and interactions between peers. Thus,
39//! the router can function mostly autonomously after it is set up. The router runs in a background
40//! task transparent to the caller. It can be interacted with through the returned
41//! [`RouterHandle`][`crate::router::RouterHandle`]. The caller also receives a
42//! [`tokio::task::JoinHandle`] that can be used to wait for the router to be fully destroyed.
43//!
44//! ### Router Example
45//!
46//! ```
47//! use battler_wamp::router::{
48//! EmptyPubSubPolicies,
49//! EmptyRpcPolicies,
50//! RealmAuthenticationConfig,
51//! RealmConfig,
52//! RouterConfig,
53//! new_web_socket_router,
54//! };
55//! use battler_wamp_uri::Uri;
56//!
57//! #[tokio::main]
58//! async fn main() {
59//! let mut config = RouterConfig::default();
60//! config.port = 8080;
61//! config.realms.push(RealmConfig {
62//! name: "Test Realm".to_owned(),
63//! uri: Uri::try_from("com.battler_wamp.realm.test").unwrap(),
64//! authentication: RealmAuthenticationConfig::default(),
65//! });
66//!
67//! // Create the router.
68//! //
69//! // Policy modules can be used to inject custom policies for resources created on the router.
70//! let router = new_web_socket_router(
71//! config,
72//! Box::new(EmptyPubSubPolicies::default()),
73//! Box::new(EmptyRpcPolicies::default()),
74//! )
75//! .unwrap();
76//!
77//! // Start the router in a background task.
78//! let (router_handle, router_join_handle) = router.start().await.unwrap();
79//!
80//! // Let the router run for as long as desired...
81//!
82//! // Cancel and wait for the router to terminate.
83//! router_handle.cancel().unwrap();
84//! router_join_handle.await;
85//! }
86//! ```
87//!
88//! ## Peers
89//!
90//! WAMP peers are simply clients that interact with a WAMP router. Unlike routers, they are
91//! directly controlled by callers, so peers are constructed and intended to be owned by
92//! higher-level application code.
93//!
94//! Configure a peer using a [`PeerConfig`][`crate::peer::PeerConfig`] and construct a
95//! [`Peer`][`crate::peer::Peer`] directly.
96//!
97//! If you are working with WebSocket connections, the
98//! [`new_web_socket_peer`][`crate::peer::new_web_socket_peer`] utility function sets up the
99//! proper modules for convenience.
100//!
101//! ### Connecting to a Realm
102//!
103//! ```
104//! use battler_wamp::{
105//! core::hash::HashMap,
106//! peer::{
107//! Peer,
108//! PeerConfig,
109//! WebSocketConfig,
110//! new_web_socket_peer,
111//! },
112//! router::{
113//! EmptyPubSubPolicies,
114//! EmptyRpcPolicies,
115//! RealmAuthenticationConfig,
116//! RealmConfig,
117//! RouterConfig,
118//! RouterHandle,
119//! new_web_socket_router,
120//! },
121//! };
122//! use battler_wamp_uri::Uri;
123//! use tokio::task::JoinHandle;
124//!
125//! async fn start_router() -> anyhow::Result<(RouterHandle, JoinHandle<()>)> {
126//! let mut config = RouterConfig::default();
127//! config.realms.push(RealmConfig {
128//! name: "Realm A".to_owned(),
129//! uri: Uri::try_from("com.battler_wamp.realm.a").unwrap(),
130//! authentication: RealmAuthenticationConfig::default(),
131//! });
132//! config.realms.push(RealmConfig {
133//! name: "Realm B".to_owned(),
134//! uri: Uri::try_from("com.battler_wamp.realm.b").unwrap(),
135//! authentication: RealmAuthenticationConfig::default(),
136//! });
137//! let router = new_web_socket_router(
138//! config,
139//! Box::new(EmptyPubSubPolicies::default()),
140//! Box::new(EmptyRpcPolicies::default()),
141//! )?;
142//! router.start().await
143//! }
144//!
145//! #[tokio::main]
146//! async fn main() {
147//! let (router_handle, _) = start_router().await.unwrap();
148//!
149//! let mut config = PeerConfig::default();
150//! config.web_socket = Some(WebSocketConfig {
151//! headers: HashMap::from_iter([(
152//! "X-WAMP-Framework".to_owned(),
153//! "battler-wamp".to_owned(),
154//! )]),
155//! });
156//!
157//! // Create peer, connect to a router, and join a realm.
158//! let peer = new_web_socket_peer(config).unwrap();
159//! peer.connect(&format!("ws://{}", router_handle.local_addr()))
160//! .await
161//! .unwrap();
162//! peer.join_realm("com.battler_wamp.realm.a").await.unwrap();
163//!
164//! // Leave the realm, and join a different one.
165//! peer.leave_realm().await.unwrap();
166//! peer.join_realm("com.battler_wamp.realm.b").await.unwrap();
167//!
168//! // Disconnect from the router altogether.
169//! peer.disconnect().await.unwrap();
170//! }
171//! ```
172//!
173//! ### Pub/Sub
174//!
175//! Peers can subscribe to topics that other peers can publish events to.
176//!
177//! #### Simple Example
178//!
179//! ```
180//! use battler_wamp::{
181//! core::hash::HashMap,
182//! peer::{
183//! Peer,
184//! PeerConfig,
185//! PublishedEvent,
186//! ReceivedEvent,
187//! new_web_socket_peer,
188//! },
189//! router::{
190//! EmptyPubSubPolicies,
191//! EmptyRpcPolicies,
192//! RealmAuthenticationConfig,
193//! RealmConfig,
194//! RouterConfig,
195//! RouterHandle,
196//! new_web_socket_router,
197//! },
198//! };
199//! use battler_wamp_uri::Uri;
200//! use battler_wamp_values::{
201//! Dictionary,
202//! List,
203//! Value,
204//! };
205//! use tokio::task::JoinHandle;
206//!
207//! async fn start_router() -> anyhow::Result<(RouterHandle, JoinHandle<()>)> {
208//! let mut config = RouterConfig::default();
209//! config.realms.push(RealmConfig {
210//! name: "Realm".to_owned(),
211//! uri: Uri::try_from("com.battler_wamp.realm").unwrap(),
212//! authentication: RealmAuthenticationConfig::default(),
213//! });
214//! let router = new_web_socket_router(
215//! config,
216//! Box::new(EmptyPubSubPolicies::default()),
217//! Box::new(EmptyRpcPolicies::default()),
218//! )?;
219//! router.start().await
220//! }
221//!
222//! async fn publisher(router_handle: RouterHandle) {
223//! let publisher = new_web_socket_peer(PeerConfig::default()).unwrap();
224//! publisher
225//! .connect(&format!("ws://{}", router_handle.local_addr()))
226//! .await
227//! .unwrap();
228//! publisher
229//! .join_realm("com.battler_wamp.realm")
230//! .await
231//! .unwrap();
232//!
233//! // Publish one event to a topic.
234//! publisher
235//! .publish(
236//! Uri::try_from("com.battler_wamp.topic1").unwrap(),
237//! PublishedEvent {
238//! arguments: List::from_iter([Value::Integer(123)]),
239//! arguments_keyword: Dictionary::from_iter([(
240//! "foo".to_owned(),
241//! Value::String("bar".to_owned()),
242//! )]),
243//! ..Default::default()
244//! },
245//! )
246//! .await
247//! .unwrap();
248//! }
249//!
250//! #[tokio::main]
251//! async fn main() {
252//! let (router_handle, _) = start_router().await.unwrap();
253//!
254//! let subscriber = new_web_socket_peer(PeerConfig::default()).unwrap();
255//! subscriber
256//! .connect(&format!("ws://{}", router_handle.local_addr()))
257//! .await
258//! .unwrap();
259//! subscriber
260//! .join_realm("com.battler_wamp.realm")
261//! .await
262//! .unwrap();
263//!
264//! // Subscribe to a topic.
265//! let mut subscription = subscriber
266//! .subscribe(Uri::try_from("com.battler_wamp.topic1").unwrap())
267//! .await
268//! .unwrap();
269//!
270//! tokio::spawn(publisher(router_handle.clone()));
271//!
272//! // The subscription contains a channel for receiving events.
273//! while let Ok(event) = subscription.event_rx.recv().await {
274//! assert_eq!(
275//! event,
276//! ReceivedEvent {
277//! arguments: List::from_iter([Value::Integer(123)]),
278//! arguments_keyword: Dictionary::from_iter([(
279//! "foo".to_owned(),
280//! Value::String("bar".to_owned())
281//! )]),
282//! topic: Some(Uri::try_from("com.battler_wamp.topic1").unwrap()),
283//! }
284//! );
285//!
286//! // Unsubscribe to close the event loop.
287//! subscriber.unsubscribe(subscription.id).await.unwrap();
288//! }
289//! }
290//! ```
291//!
292//! #### Pattern-Based Subscription
293//!
294//! ```
295//! use battler_wamp::{
296//! core::{
297//! hash::HashMap,
298//! match_style::MatchStyle,
299//! },
300//! peer::{
301//! Peer,
302//! PeerConfig,
303//! PublishedEvent,
304//! ReceivedEvent,
305//! SubscriptionOptions,
306//! new_web_socket_peer,
307//! },
308//! router::{
309//! EmptyPubSubPolicies,
310//! EmptyRpcPolicies,
311//! RealmAuthenticationConfig,
312//! RealmConfig,
313//! RouterConfig,
314//! RouterHandle,
315//! new_web_socket_router,
316//! },
317//! };
318//! use battler_wamp_uri::{
319//! Uri,
320//! WildcardUri,
321//! };
322//! use battler_wamp_values::{
323//! Dictionary,
324//! List,
325//! Value,
326//! };
327//! use tokio::task::JoinHandle;
328//!
329//! async fn start_router() -> anyhow::Result<(RouterHandle, JoinHandle<()>)> {
330//! let mut config = RouterConfig::default();
331//! config.realms.push(RealmConfig {
332//! name: "Realm".to_owned(),
333//! uri: Uri::try_from("com.battler_wamp.realm").unwrap(),
334//! authentication: RealmAuthenticationConfig::default(),
335//! });
336//! let router = new_web_socket_router(
337//! config,
338//! Box::new(EmptyPubSubPolicies::default()),
339//! Box::new(EmptyRpcPolicies::default()),
340//! )?;
341//! router.start().await
342//! }
343//!
344//! async fn publisher(router_handle: RouterHandle) {
345//! let publisher = new_web_socket_peer(PeerConfig::default()).unwrap();
346//! publisher
347//! .connect(&format!("ws://{}", router_handle.local_addr()))
348//! .await
349//! .unwrap();
350//! publisher
351//! .join_realm("com.battler_wamp.realm")
352//! .await
353//! .unwrap();
354//!
355//! publisher
356//! .publish(
357//! Uri::try_from("com.battler_wamp.topics.1").unwrap(),
358//! PublishedEvent {
359//! arguments: List::from_iter([Value::Integer(123)]),
360//! ..Default::default()
361//! },
362//! )
363//! .await
364//! .unwrap();
365//! publisher
366//! .publish(
367//! Uri::try_from("com.battler_wamp.topics.2").unwrap(),
368//! PublishedEvent {
369//! arguments: List::from_iter([Value::Integer(456)]),
370//! ..Default::default()
371//! },
372//! )
373//! .await
374//! .unwrap();
375//! }
376//!
377//! #[tokio::main]
378//! async fn main() {
379//! let (router_handle, _) = start_router().await.unwrap();
380//!
381//! let subscriber = new_web_socket_peer(PeerConfig::default()).unwrap();
382//! subscriber
383//! .connect(&format!("ws://{}", router_handle.local_addr()))
384//! .await
385//! .unwrap();
386//! subscriber
387//! .join_realm("com.battler_wamp.realm")
388//! .await
389//! .unwrap();
390//!
391//! // Subscribe to a topic.
392//! let mut subscription = subscriber
393//! .subscribe_with_options(
394//! WildcardUri::try_from("com.battler_wamp.topics").unwrap(),
395//! SubscriptionOptions {
396//! match_style: Some(MatchStyle::Prefix),
397//! },
398//! )
399//! .await
400//! .unwrap();
401//!
402//! tokio::spawn(publisher(router_handle.clone()));
403//!
404//! assert_eq!(
405//! subscription.event_rx.recv().await.unwrap(),
406//! ReceivedEvent {
407//! arguments: List::from_iter([Value::Integer(123)]),
408//! topic: Some(Uri::try_from("com.battler_wamp.topics.1").unwrap()),
409//! ..Default::default()
410//! }
411//! );
412//! assert_eq!(
413//! subscription.event_rx.recv().await.unwrap(),
414//! ReceivedEvent {
415//! arguments: List::from_iter([Value::Integer(456)]),
416//! topic: Some(Uri::try_from("com.battler_wamp.topics.2").unwrap()),
417//! ..Default::default()
418//! }
419//! );
420//! }
421//! ```
422//!
423//! ### RPC
424//!
425//! Peers can register procedures that other peers can call.
426//!
427//! #### Simple Example
428//!
429//! ```
430//! use battler_wamp::{
431//! core::hash::HashMap,
432//! peer::{
433//! Peer,
434//! PeerConfig,
435//! Procedure,
436//! ProcedureMessage,
437//! RpcCall,
438//! RpcResult,
439//! RpcYield,
440//! WebSocketPeer,
441//! new_web_socket_peer,
442//! },
443//! router::{
444//! EmptyPubSubPolicies,
445//! EmptyRpcPolicies,
446//! RealmAuthenticationConfig,
447//! RealmConfig,
448//! RouterConfig,
449//! RouterHandle,
450//! new_web_socket_router,
451//! },
452//! };
453//! use battler_wamp_uri::Uri;
454//! use battler_wamp_values::{
455//! Dictionary,
456//! List,
457//! Value,
458//! };
459//! use tokio::task::JoinHandle;
460//!
461//! async fn start_router() -> anyhow::Result<(RouterHandle, JoinHandle<()>)> {
462//! let mut config = RouterConfig::default();
463//! config.realms.push(RealmConfig {
464//! name: "Realm".to_owned(),
465//! uri: Uri::try_from("com.battler_wamp.realm").unwrap(),
466//! authentication: RealmAuthenticationConfig::default(),
467//! });
468//! let router = new_web_socket_router(
469//! config,
470//! Box::new(EmptyPubSubPolicies::default()),
471//! Box::new(EmptyRpcPolicies::default()),
472//! )?;
473//! router.start().await
474//! }
475//!
476//! async fn start_callee(router_handle: RouterHandle) -> WebSocketPeer {
477//! let callee = new_web_socket_peer(PeerConfig::default()).unwrap();
478//! callee
479//! .connect(&format!("ws://{}", router_handle.local_addr()))
480//! .await
481//! .unwrap();
482//! callee.join_realm("com.battler_wamp.realm").await.unwrap();
483//!
484//! // Register a procedure that echoes the caller's input.
485//! let mut procedure = callee
486//! .register(Uri::try_from("com.battler_wamp.echo").unwrap())
487//! .await
488//! .unwrap();
489//!
490//! // Handle the procedure in a separate task.
491//! async fn handler(mut procedure: Procedure) {
492//! while let Ok(message) = procedure.procedure_message_rx.recv().await {
493//! match message {
494//! ProcedureMessage::Invocation(invocation) => {
495//! let result = RpcYield {
496//! arguments: invocation.arguments.clone(),
497//! arguments_keyword: invocation.arguments_keyword.clone(),
498//! };
499//! invocation.respond_ok(result).await.unwrap();
500//! }
501//! _ => (),
502//! }
503//! }
504//! }
505//!
506//! tokio::spawn(handler(procedure));
507//! callee
508//! }
509//!
510//! #[tokio::main]
511//! async fn main() {
512//! let (router_handle, _) = start_router().await.unwrap();
513//!
514//! let callee = start_callee(router_handle.clone()).await;
515//!
516//! let caller = new_web_socket_peer(PeerConfig::default()).unwrap();
517//! caller
518//! .connect(&format!("ws://{}", router_handle.local_addr()))
519//! .await
520//! .unwrap();
521//! caller.join_realm("com.battler_wamp.realm").await.unwrap();
522//!
523//! let rpc = caller
524//! .call(
525//! Uri::try_from("com.battler_wamp.echo").unwrap(),
526//! RpcCall {
527//! arguments: List::from_iter([Value::Integer(1), Value::Integer(2)]),
528//! arguments_keyword: Dictionary::from_iter([(
529//! "foo".to_owned(),
530//! Value::String("bar".to_owned()),
531//! )]),
532//! ..Default::default()
533//! },
534//! )
535//! .await
536//! .unwrap();
537//! assert_eq!(
538//! rpc.result().await.unwrap(),
539//! RpcResult {
540//! arguments: List::from_iter([Value::Integer(1), Value::Integer(2)]),
541//! arguments_keyword: Dictionary::from_iter([(
542//! "foo".to_owned(),
543//! Value::String("bar".to_owned()),
544//! )]),
545//! ..Default::default()
546//! }
547//! );
548//! }
549//! ```
550//!
551//! #### Custom Error Reporting
552//!
553//! ```
554//! use battler_wamp::{
555//! core::{
556//! error::WampError,
557//! hash::HashMap,
558//! },
559//! peer::{
560//! Peer,
561//! PeerConfig,
562//! Procedure,
563//! ProcedureMessage,
564//! RpcCall,
565//! RpcResult,
566//! RpcYield,
567//! WebSocketPeer,
568//! new_web_socket_peer,
569//! },
570//! router::{
571//! EmptyPubSubPolicies,
572//! EmptyRpcPolicies,
573//! RealmAuthenticationConfig,
574//! RealmConfig,
575//! RouterConfig,
576//! RouterHandle,
577//! new_web_socket_router,
578//! },
579//! };
580//! use battler_wamp_uri::Uri;
581//! use battler_wamp_values::{
582//! Dictionary,
583//! List,
584//! Value,
585//! };
586//! use tokio::task::JoinHandle;
587//!
588//! async fn start_router() -> anyhow::Result<(RouterHandle, JoinHandle<()>)> {
589//! let mut config = RouterConfig::default();
590//! config.realms.push(RealmConfig {
591//! name: "Realm".to_owned(),
592//! uri: Uri::try_from("com.battler_wamp.realm").unwrap(),
593//! authentication: RealmAuthenticationConfig::default(),
594//! });
595//! let router = new_web_socket_router(
596//! config,
597//! Box::new(EmptyPubSubPolicies::default()),
598//! Box::new(EmptyRpcPolicies::default()),
599//! )?;
600//! router.start().await
601//! }
602//!
603//! async fn start_callee(router_handle: RouterHandle) -> WebSocketPeer {
604//! let callee = new_web_socket_peer(PeerConfig::default()).unwrap();
605//! callee
606//! .connect(&format!("ws://{}", router_handle.local_addr()))
607//! .await
608//! .unwrap();
609//! callee.join_realm("com.battler_wamp.realm").await.unwrap();
610//!
611//! let mut procedure = callee
612//! .register(Uri::try_from("com.battler_wamp.add2").unwrap())
613//! .await
614//! .unwrap();
615//!
616//! // Handle the procedure in a separate task.
617//! async fn handler(mut procedure: Procedure) {
618//! while let Ok(message) = procedure.procedure_message_rx.recv().await {
619//! match message {
620//! ProcedureMessage::Invocation(invocation) => {
621//! let result = if invocation.arguments.len() != 2 {
622//! Err(WampError::new(
623//! Uri::try_from("com.battler_wamp.error.add_error").unwrap(),
624//! "2 arguments required".to_owned(),
625//! ))
626//! } else {
627//! match (&invocation.arguments[0], &invocation.arguments[1]) {
628//! (Value::Integer(a), Value::Integer(b)) => Ok(RpcYield {
629//! arguments: List::from_iter([Value::Integer(a + b)]),
630//! ..Default::default()
631//! }),
632//! _ => Err(WampError::new(
633//! Uri::try_from("com.battler_wamp.error.add_error").unwrap(),
634//! "integers required",
635//! )),
636//! }
637//! };
638//! invocation.respond(result).await.unwrap();
639//! }
640//! _ => (),
641//! }
642//! }
643//! }
644//!
645//! tokio::spawn(handler(procedure));
646//! callee
647//! }
648//!
649//! #[tokio::main]
650//! async fn main() {
651//! let (router_handle, _) = start_router().await.unwrap();
652//!
653//! let callee = start_callee(router_handle.clone()).await;
654//!
655//! let caller = new_web_socket_peer(PeerConfig::default()).unwrap();
656//! caller
657//! .connect(&format!("ws://{}", router_handle.local_addr()))
658//! .await
659//! .unwrap();
660//! caller.join_realm("com.battler_wamp.realm").await.unwrap();
661//!
662//! assert_eq!(
663//! caller
664//! .call_and_wait(
665//! Uri::try_from("com.battler_wamp.add2").unwrap(),
666//! RpcCall::default()
667//! )
668//! .await
669//! .unwrap_err()
670//! .downcast::<WampError>()
671//! .unwrap(),
672//! WampError::new(
673//! Uri::try_from("com.battler_wamp.error.add_error").unwrap(),
674//! "2 arguments required"
675//! ),
676//! );
677//!
678//! assert_eq!(
679//! caller
680//! .call_and_wait(
681//! Uri::try_from("com.battler_wamp.add2").unwrap(),
682//! RpcCall {
683//! arguments: List::from_iter([Value::Integer(1), Value::Integer(2)]),
684//! ..Default::default()
685//! }
686//! )
687//! .await
688//! .unwrap(),
689//! RpcResult {
690//! arguments: List::from_iter([Value::Integer(3)]),
691//! ..Default::default()
692//! }
693//! );
694//! }
695//! ```
696//!
697//! #### Pattern-Based Registration
698//!
699//! ```
700//! use battler_wamp::{
701//! core::{
702//! hash::HashMap,
703//! match_style::MatchStyle,
704//! },
705//! peer::{
706//! Peer,
707//! PeerConfig,
708//! Procedure,
709//! ProcedureMessage,
710//! ProcedureOptions,
711//! RpcCall,
712//! RpcResult,
713//! RpcYield,
714//! WebSocketPeer,
715//! new_web_socket_peer,
716//! },
717//! router::{
718//! EmptyPubSubPolicies,
719//! EmptyRpcPolicies,
720//! RealmAuthenticationConfig,
721//! RealmConfig,
722//! RouterConfig,
723//! RouterHandle,
724//! new_web_socket_router,
725//! },
726//! };
727//! use battler_wamp_uri::{
728//! Uri,
729//! WildcardUri,
730//! };
731//! use battler_wamp_values::{
732//! Dictionary,
733//! List,
734//! Value,
735//! };
736//! use tokio::task::JoinHandle;
737//!
738//! async fn start_router() -> anyhow::Result<(RouterHandle, JoinHandle<()>)> {
739//! let mut config = RouterConfig::default();
740//! config.realms.push(RealmConfig {
741//! name: "Realm".to_owned(),
742//! uri: Uri::try_from("com.battler_wamp.realm").unwrap(),
743//! authentication: RealmAuthenticationConfig::default(),
744//! });
745//! let router = new_web_socket_router(
746//! config,
747//! Box::new(EmptyPubSubPolicies::default()),
748//! Box::new(EmptyRpcPolicies::default()),
749//! )?;
750//! router.start().await
751//! }
752//!
753//! async fn start_callee(router_handle: RouterHandle) -> WebSocketPeer {
754//! let callee = new_web_socket_peer(PeerConfig::default()).unwrap();
755//! callee
756//! .connect(&format!("ws://{}", router_handle.local_addr()))
757//! .await
758//! .unwrap();
759//! callee.join_realm("com.battler_wamp.realm").await.unwrap();
760//!
761//! let mut procedure = callee
762//! .register_with_options(
763//! WildcardUri::try_from("com.battler_wamp..echo").unwrap(),
764//! ProcedureOptions {
765//! match_style: Some(MatchStyle::Wildcard),
766//! ..Default::default()
767//! },
768//! )
769//! .await
770//! .unwrap();
771//!
772//! // Handle the procedure in a separate task.
773//! async fn handler(mut procedure: Procedure) {
774//! while let Ok(message) = procedure.procedure_message_rx.recv().await {
775//! match message {
776//! ProcedureMessage::Invocation(invocation) => {
777//! let result = RpcYield {
778//! arguments: invocation.arguments.clone(),
779//! arguments_keyword: invocation.arguments_keyword.clone(),
780//! };
781//! invocation.respond_ok(result).await.unwrap();
782//! }
783//! _ => (),
784//! }
785//! }
786//! }
787//!
788//! tokio::spawn(handler(procedure));
789//! callee
790//! }
791//!
792//! #[tokio::main]
793//! async fn main() {
794//! let (router_handle, _) = start_router().await.unwrap();
795//!
796//! let callee = start_callee(router_handle.clone()).await;
797//!
798//! let caller = new_web_socket_peer(PeerConfig::default()).unwrap();
799//! caller
800//! .connect(&format!("ws://{}", router_handle.local_addr()))
801//! .await
802//! .unwrap();
803//! caller.join_realm("com.battler_wamp.realm").await.unwrap();
804//!
805//! assert_eq!(
806//! caller
807//! .call_and_wait(
808//! Uri::try_from("com.battler_wamp.v1.echo").unwrap(),
809//! RpcCall {
810//! arguments: List::from_iter([Value::Integer(1), Value::Integer(2)]),
811//! arguments_keyword: Dictionary::from_iter([(
812//! "foo".to_owned(),
813//! Value::String("bar".to_owned()),
814//! )]),
815//! ..Default::default()
816//! }
817//! )
818//! .await
819//! .unwrap(),
820//! RpcResult {
821//! arguments: List::from_iter([Value::Integer(1), Value::Integer(2)]),
822//! arguments_keyword: Dictionary::from_iter([(
823//! "foo".to_owned(),
824//! Value::String("bar".to_owned()),
825//! )]),
826//! ..Default::default()
827//! }
828//! );
829//!
830//! assert_eq!(
831//! caller
832//! .call_and_wait(
833//! Uri::try_from("com.battler_wamp.v2.echo").unwrap(),
834//! RpcCall {
835//! arguments: List::from_iter([Value::String("abc".to_owned())]),
836//! ..Default::default()
837//! }
838//! )
839//! .await
840//! .unwrap(),
841//! RpcResult {
842//! arguments: List::from_iter([Value::String("abc".to_owned())]),
843//! ..Default::default()
844//! }
845//! );
846//! }
847//! ```
848pub mod auth;
849pub mod core;
850pub mod message;
851pub mod peer;
852pub mod router;
853pub mod serializer;
854pub mod transport;