battler_wamprat_uri/lib.rs
1//! # battler-wamprat-uri
2//!
3//! **battler-wamprat-uri** is a utility crate for [`battler-wamprat`](https://crates.io/crates/battler-wamprat). It provides a procedural macro for dynamically matching URIs for WAMP subscriptions and procedure registrations.
4//!
5//! Pattern-based subscriptions and procedure registrations can be complex. They provide an
6//! additional avenue of input for WAMP messages, outside the `arguments` and `arguments_keyword`
7//! fields expressed by [`battler_wamprat_message::WampApplicationMessage`](https://docs.rs/battler-wamprat-message/latest/battler_wamprat_message/) types.
8//!
9//! For example, a callee can register a procedure on the wildcard URI `com.test.add..v2`. A caller
10//! can then call this procedure using any URI that matches this pattern. One caller may call
11//! `com.test.add.integers.v2` while another may call `com.test.add.strings.v2`. The wildcard URI
12//! component (in this case, `integers` or `strings`) is likely very important to the callee!
13//!
14//! This crate provides runtime type checking and pattern matching to incoming and outgoing WAMP URI
15//! patterns through the [`WampUriMatcher`] derive macro. A URI matcher can be thought of as a
16//! *glorified regular expression*: each wildcard component is a *named capture group* that can
17//! be referenced after matching. The end result is that URIs going out from a peer can be formatted
18//! automatically, and URIs coming into a peer can be pattern matched, allowing the extracted URI
19//! components to be easily read by application logic.
20//!
21//! ## Basic Usage
22//!
23//! A struct using the [`WampUriMatcher`] derive macro must have a `uri` attribute. The URI
24//! describes the wildcard pattern, which is used for formatting outgoing URIs and matching incoming
25//! URIs.
26//!
27//! In the simplest case, a struct with no fields can match a static URI:
28//!
29//! ```
30//! use battler_wamprat_uri::WampUriMatcher;
31//!
32//! #[derive(WampUriMatcher)]
33//! #[uri("com.test.add")]
34//! struct AddUri {}
35//! ```
36//!
37//! For each field you add, that field *must* be represented in the URI pattern. Otherwise, the
38//! struct would be impossible to construct for incoming URIs.
39//!
40//! ```
41//! use battler_wamprat_uri::WampUriMatcher;
42//!
43//! #[derive(WampUriMatcher)]
44//! #[uri("com.test.add.{name}.v2")]
45//! struct AddUri {
46//! name: String,
47//! }
48//! ```
49//!
50//! Each struct field must be convertible to and from a string, using
51//! [`Display`][`core::fmt::Display`] and [`FromStr`][`core::str::FromStr`] respectively.
52//! ```
53//! use battler_wamprat_uri::WampUriMatcher;
54//!
55//! #[derive(WampUriMatcher)]
56//! #[uri("com.test.add.{name}.{count}")]
57//! struct AddUri {
58//! name: String,
59//! count: u64,
60//! }
61//! ```
62//! ## Advanced Features
63//!
64//! ### Prefix Matching
65//!
66//! In some cases, a URI pattern may need to match in prefix form. Prefix matching is only possible
67//! if the last field in the struct is marked with the `rest` attribute. This attribute requires
68//! that an iterator of [`String`]s can be collected into its type.
69//!
70//! ```
71//! use battler_wamprat_uri::WampUriMatcher;
72//!
73//! #[derive(WampUriMatcher)]
74//! #[uri("com.test.fn.{a}.{rest}")]
75//! struct PrefixUri {
76//! a: String,
77//! #[rest]
78//! rest: Vec<String>,
79//! }
80//! ```
81//!
82//! ### Field Repetition
83//!
84//! Fields can be repeated in the URI pattern. The first use of the field will be treated as the
85//! source of truth, and all later uses of the field must match this first value.
86//!
87//! ```
88//! use battler_wamprat_uri::WampUriMatcher;
89//!
90//! #[derive(WampUriMatcher)]
91//! #[uri("com.test.fn.{a}.{b}.{a}")]
92//! struct RepeatedUri {
93//! a: u64,
94//! b: u64,
95//! }
96//! ```
97//!
98//! ### Regular Expression Component Matching
99//!
100//! In most cases, struct fields should be isolated to their own URI components for simplicity.
101//! However, this is not a strict requirement of the URI matcher macro.
102//!
103//! Strings and struct fields can be combined into the same URI component. If this is done, the URI
104//! pattern matching is less enforceable on the router, but the peer will still validate and match
105//! the URI as expected.
106//!
107//! ```
108//! use battler_wamprat_uri::WampUriMatcher;
109//!
110//! #[derive(WampUriMatcher)]
111//! #[uri("com.test.math.{a}log{b}")]
112//! struct RegExUri {
113//! a: u64,
114//! b: u64,
115//! }
116//! ```
117//!
118//! Note that in the above case, the URI registered on the router will be the wildcard
119//! `com.test.math.`, so the last component can be matched by *any* string. However, the `RegExUri`
120//! type will enforce the additional restrictions. For instance, the URI `com.test.math.abc` will
121//! be rejected by the peer, while `com.test.math.2log12` will be accepted.
122//!
123//! *Note: URI such as the one above will generate a dependency on the `regex` crate.*
124//!
125//! ### Generators
126//!
127//! It can sometimes be useful to generate partial-wildcard URIs based on a URI pattern. For
128//! example, one peer may be publishing messages to `com.chat.{version}.{channel}.{author}`, and
129//! another peer may want to subscribe to messages for a specific channel using the
130//! `com.chat.1.main.{author}`. Rather than requiring the subscriber to generate this
131//! wildcard URI manually, it can use a **derived generator struct** from base URI pattern.
132//!
133//! A generator struct implements the [`WampWildcardUriGenerator`] trait and can be generated
134//! automatically with the `generator` macro attribute. You can generate multiple generators for a
135//! single matcher.
136//!
137//! ```
138//! use std::marker::PhantomData;
139//!
140//! use battler_wamprat_uri::{
141//! WampUriMatcher,
142//! Wildcard,
143//! WampWildcardUriGenerator,
144//! };
145//!
146//! #[derive(Clone, WampUriMatcher)]
147//! #[uri("com.chat.{version}.{channel}.{author}")]
148//! #[generator(ChannelMessageUri, fixed(version = 1u64), require(channel), derive(Clone))]
149//! struct MessageUri {
150//! version: u64,
151//! channel: String,
152//! author: String,
153//! }
154//!
155//! fn main() {
156//! assert_matches::assert_matches!(ChannelMessageUri {
157//! version: PhantomData,
158//! channel: "main".to_owned(),
159//! author: Wildcard::Wildcard,
160//! }.wamp_generate_wildcard_uri(), Ok(uri) => {
161//! assert_eq!(uri.as_ref(), "com.chat.1.main.");
162//! });
163//! }
164//! ```
165//!
166//! You can also create generators for patterns with unnamed fields, but indices must be prefixed
167//! with an underscore (e.g., `_0`, `_1`) in the `generator` attribute.
168//!
169//! **Note:** Generators are limited to wildcard URI patterns only. URI patterns with prefix
170//! matching **cannot** use the `generator` attribute. You may, however, create your own generator
171//! structs manually.
172
173#![feature(slice_concat_trait)]
174use battler_wamp::core::{
175 error::WampError,
176 match_style::MatchStyle,
177};
178use battler_wamp_uri::{
179 InvalidUri,
180 Uri,
181 WildcardUri,
182};
183pub use battler_wamprat_uri_proc_macro::WampUriMatcher;
184use thiserror::Error;
185
186/// An error resulting from attempting to match a [`Uri`] using a [`WampUriMatcher`].
187#[derive(Debug, Error)]
188#[error("{msg}")]
189pub struct WampUriMatchError {
190 msg: String,
191}
192
193impl WampUriMatchError {
194 pub fn new<S>(msg: S) -> Self
195 where
196 S: Into<String>,
197 {
198 Self { msg: msg.into() }
199 }
200}
201
202impl Into<WampError> for WampUriMatchError {
203 fn into(self) -> WampError {
204 WampError::new(
205 Uri::try_from("com.battler_wamprat.uri_match_error").unwrap(),
206 self.to_string(),
207 )
208 }
209}
210
211impl TryFrom<WampError> for WampUriMatchError {
212 type Error = WampError;
213 fn try_from(value: WampError) -> Result<Self, Self::Error> {
214 if value.reason().as_ref() == "com.battler_wamprat.uri_match_error" {
215 Ok(Self {
216 msg: value.message().to_owned(),
217 })
218 } else {
219 Err(value)
220 }
221 }
222}
223
224/// A dynamic WAMP URI matcher, which configures a URI pattern for incoming and outgoing URIs.
225///
226/// This type can receive a WAMP URI as input and parse it to this type (e.g., for callee-side
227/// invocations), or it can generate a URI based on itself (e.g., for caller-side invocations).
228pub trait WampUriMatcher: Sized {
229 /// The wildcard URI for the router.
230 fn uri_for_router() -> WildcardUri;
231
232 /// The match style of the URI matcher.
233 fn match_style() -> Option<MatchStyle>;
234
235 /// Matches an incoming URI to the configured pattern.
236 fn wamp_match_uri(uri: &str) -> Result<Self, WampUriMatchError>;
237
238 /// Generates an outgoing URI for the configured values.
239 fn wamp_generate_uri(&self) -> Result<Uri, InvalidUri>;
240}
241
242/// A field of a custom generator of a [`WampUriMatcher`] implementation (a.k.a., an implementation
243/// of [`WampWildcardUriGenerator`]) where wildcard is allowed.
244#[derive(Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
245pub enum Wildcard<T> {
246 #[default]
247 Wildcard,
248 Value(T),
249}
250
251impl<T> std::fmt::Display for Wildcard<T>
252where
253 T: std::fmt::Display,
254{
255 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
256 match self {
257 Self::Wildcard => write!(f, ""),
258 Self::Value(val) => val.fmt(f),
259 }
260 }
261}
262
263impl<T> std::str::FromStr for Wildcard<T>
264where
265 T: std::str::FromStr,
266{
267 type Err = <T as std::str::FromStr>::Err;
268 fn from_str(s: &str) -> Result<Self, Self::Err> {
269 if s.is_empty() {
270 Ok(Self::Wildcard)
271 } else {
272 Ok(Self::Value(T::from_str(s)?))
273 }
274 }
275}
276
277impl<T> From<T> for Wildcard<T> {
278 fn from(value: T) -> Self {
279 Wildcard::Value(value)
280 }
281}
282
283/// A custom generator of a [`WampUriMatcher`] implementation where wildcards may be used to
284/// generate [`WildcardUri`]s.
285pub trait WampWildcardUriGenerator<T> {
286 /// Generates an outgoing URI for the configured values.
287 fn wamp_generate_wildcard_uri(&self) -> Result<WildcardUri, InvalidUri>;
288}
289
290impl<T> WampWildcardUriGenerator<T> for T
291where
292 T: WampUriMatcher,
293{
294 fn wamp_generate_wildcard_uri(&self) -> Result<WildcardUri, InvalidUri> {
295 Ok(WildcardUri::from(self.wamp_generate_uri()?))
296 }
297}