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;