rtc_interceptor/registry.rs
1//! Interceptor Registry - Type-safe builder for constructing interceptor chains.
2//!
3//! The [`Registry`] provides a fluent API for composing interceptor chains. Each call
4//! to [`with()`](Registry::with) wraps the current chain with a new interceptor layer.
5//!
6//! # Chain Construction
7//!
8//! Interceptors are added from innermost to outermost. The first interceptor added
9//! becomes the innermost (closest to [`NoopInterceptor`](crate::NoopInterceptor)),
10//! and the last becomes the outermost (processes packets first).
11//!
12//! ```text
13//! Registry::new()
14//! .with(InterceptorA) // Innermost
15//! .with(InterceptorB) // Middle
16//! .with(InterceptorC) // Outermost
17//! .build()
18//!
19//! Results in: C wraps B wraps A wraps NoopInterceptor
20//! ```
21
22use crate::noop::NoopInterceptor;
23use crate::{BoxedInterceptor, Interceptor};
24
25/// Registry for constructing interceptor chains.
26///
27/// `Registry` wraps an interceptor chain and allows adding more interceptors
28/// via the [`with`](Registry::with) method. The chain can be extracted with [`build`](Registry::build).
29///
30/// # Example
31///
32/// ```
33/// use rtc_interceptor::{ReceiverReportBuilder, Registry, SenderReportBuilder};
34///
35/// // Each `with` changes the registry's type, so rebind rather than reassign.
36/// let registry = Registry::new()
37/// .with(SenderReportBuilder::new().build())
38/// .with(ReceiverReportBuilder::new().build());
39///
40/// // Build the final chain
41/// let chain = registry.build();
42/// ```
43///
44/// # Helper Function Pattern
45///
46/// ```
47/// use rtc_interceptor::{Interceptor, ReceiverReportBuilder, Registry, SenderReportBuilder};
48///
49/// fn register_default_interceptors<P: Interceptor>(
50/// registry: Registry<P>,
51/// ) -> Registry<impl Interceptor + use<P>> {
52/// registry
53/// .with(SenderReportBuilder::new().build())
54/// .with(ReceiverReportBuilder::new().build())
55/// }
56///
57/// let registry = Registry::new();
58/// let registry = register_default_interceptors(registry);
59/// let chain = registry.build();
60/// ```
61#[derive(Clone)]
62pub struct Registry<P> {
63 inner: P,
64}
65
66impl Registry<NoopInterceptor> {
67 /// Create a new empty registry.
68 ///
69 /// This creates a `NoopInterceptor` as the innermost layer.
70 ///
71 /// # Example
72 ///
73 /// ```
74 /// use rtc_interceptor::Registry;
75 ///
76 /// let registry = Registry::new();
77 /// ```
78 pub fn new() -> Self {
79 Registry {
80 inner: NoopInterceptor::new(),
81 }
82 }
83}
84
85impl Default for Registry<NoopInterceptor> {
86 fn default() -> Self {
87 Self::new()
88 }
89}
90
91impl<P: Interceptor> Registry<P> {
92 /// Create a registry from an existing interceptor.
93 ///
94 /// # Example
95 ///
96 /// ```
97 /// use rtc_interceptor::{NoopInterceptor, Registry};
98 ///
99 /// let custom = NoopInterceptor::new();
100 /// let registry = Registry::from(custom);
101 /// ```
102 pub fn from(inner: P) -> Self {
103 Registry { inner }
104 }
105
106 /// Wrap the current chain with another interceptor.
107 ///
108 /// Returns a new `Registry` with the updated chain type.
109 ///
110 /// # Example
111 ///
112 /// ```
113 /// use rtc_interceptor::{ReceiverReportBuilder, Registry, SenderReportBuilder};
114 ///
115 /// let registry = Registry::new()
116 /// .with(SenderReportBuilder::new().build())
117 /// .with(ReceiverReportBuilder::new().build());
118 /// ```
119 pub fn with<O, F>(self, f: F) -> Registry<O>
120 where
121 F: FnOnce(P) -> O,
122 O: Interceptor,
123 {
124 Registry {
125 inner: f(self.inner),
126 }
127 }
128
129 /// Build and return the interceptor chain.
130 ///
131 /// Consumes the registry and returns the inner interceptor chain.
132 ///
133 /// # Example
134 ///
135 /// ```
136 /// use rtc_interceptor::{Registry, SenderReportBuilder};
137 ///
138 /// let registry = Registry::new().with(SenderReportBuilder::new().build());
139 /// let chain = registry.build();
140 /// ```
141 pub fn build(self) -> P {
142 self.inner
143 }
144
145 /// Erase the chain's type, turning this into a `Registry<BoxedInterceptor>`.
146 ///
147 /// The chain an application assembles at runtime is a deep nest of generic types
148 /// (`TwccSender<NackResponder<...<NoopInterceptor>>>`), which otherwise leaks into every
149 /// type that holds the peer connection. Boxing it collapses that to one concrete type, so
150 /// a struct can store an `RTCPeerConnection<BoxedInterceptor>` field directly.
151 ///
152 /// # Example
153 ///
154 /// ```
155 /// use rtc_interceptor::{BoxedInterceptor, Registry, SenderReportBuilder};
156 ///
157 /// // Whatever the chain was composed of, the result has one concrete type.
158 /// let chain: BoxedInterceptor = Registry::new()
159 /// .with(SenderReportBuilder::new().build())
160 /// .boxed()
161 /// .build();
162 /// ```
163 ///
164 /// The `rtc` crate accepts the erased registry directly, so a peer connection can be stored
165 /// as `RTCPeerConnection<BoxedInterceptor>`:
166 ///
167 /// ```ignore
168 /// let registry = register_default_interceptors(Registry::new(), &mut media_engine)?;
169 /// let pc = RTCPeerConnectionBuilder::new()
170 /// .with_interceptor_registry(registry.boxed())
171 /// .build()?;
172 /// ```
173 ///
174 /// `P: 'static` is required because [`BoxedInterceptor`] is `Box<dyn Interceptor + 'static>`,
175 /// so the chain must not borrow anything shorter-lived. This is the only operation that needs
176 /// the bound, which is why [`Interceptor`] itself does not require `'static`.
177 pub fn boxed(self) -> Registry<BoxedInterceptor>
178 where
179 P: 'static,
180 {
181 Registry {
182 inner: Box::new(self.inner),
183 }
184 }
185}
186
187#[cfg(test)]
188mod tests {
189 use super::*;
190 use crate::TaggedPacket;
191 use sansio::Protocol;
192 use shared::error::Error;
193 use std::time::Instant;
194
195 fn dummy_rtp_packet() -> TaggedPacket {
196 TaggedPacket {
197 now: Instant::now(),
198 transport: Default::default(),
199 message: crate::Packet::Rtp(rtp::Packet::default()),
200 }
201 }
202
203 // A simple test interceptor that wraps an inner protocol
204 struct TestInterceptor<P> {
205 inner: P,
206 name: &'static str,
207 }
208
209 impl<P> TestInterceptor<P> {
210 fn new(inner: P) -> Self {
211 Self {
212 inner,
213 name: "test",
214 }
215 }
216
217 fn with_name(name: &'static str) -> impl FnOnce(P) -> Self {
218 move |inner| Self { inner, name }
219 }
220 }
221
222 impl<P: Interceptor> Protocol<TaggedPacket, TaggedPacket, ()> for TestInterceptor<P> {
223 type Rout = TaggedPacket;
224 type Wout = TaggedPacket;
225 type Eout = ();
226 type Error = Error;
227 type Time = Instant;
228
229 fn handle_read(&mut self, msg: TaggedPacket) -> Result<(), Self::Error> {
230 self.inner.handle_read(msg)
231 }
232
233 fn poll_read(&mut self) -> Option<Self::Rout> {
234 self.inner.poll_read()
235 }
236
237 fn handle_write(&mut self, msg: TaggedPacket) -> Result<(), Self::Error> {
238 self.inner.handle_write(msg)
239 }
240
241 fn poll_write(&mut self) -> Option<Self::Wout> {
242 self.inner.poll_write()
243 }
244 }
245
246 impl<P: Interceptor> Interceptor for TestInterceptor<P> {
247 fn bind_local_stream(&mut self, info: &crate::StreamInfo) {
248 self.inner.bind_local_stream(info);
249 }
250 fn unbind_local_stream(&mut self, info: &crate::StreamInfo) {
251 self.inner.unbind_local_stream(info);
252 }
253 fn bind_remote_stream(&mut self, info: &crate::StreamInfo) {
254 self.inner.bind_remote_stream(info);
255 }
256 fn unbind_remote_stream(&mut self, info: &crate::StreamInfo) {
257 self.inner.unbind_remote_stream(info);
258 }
259 }
260
261 #[test]
262 fn test_registry_new() {
263 let registry = Registry::new();
264 let mut chain = registry.build();
265 let pkt = dummy_rtp_packet();
266 chain.handle_read(pkt).unwrap();
267 assert!(chain.poll_read().is_some());
268 }
269
270 #[test]
271 fn test_registry_with_single_interceptor() {
272 let registry = Registry::new().with(TestInterceptor::new);
273 let mut chain = registry.build();
274
275 let pkt = dummy_rtp_packet();
276 chain.handle_read(pkt).unwrap();
277 assert!(chain.poll_read().is_some());
278 assert_eq!(chain.name, "test");
279 }
280
281 #[test]
282 fn test_registry_with_multiple_interceptors() {
283 let registry = Registry::new()
284 .with(TestInterceptor::with_name("inner"))
285 .with(TestInterceptor::with_name("outer"));
286 let mut chain = registry.build();
287
288 let pkt = dummy_rtp_packet();
289 chain.handle_read(pkt).unwrap();
290 assert!(chain.poll_read().is_some());
291 assert_eq!(chain.name, "outer");
292 assert_eq!(chain.inner.name, "inner");
293 }
294
295 #[test]
296 fn test_registry_from_inner() {
297 let custom = NoopInterceptor::new();
298 let registry = Registry::from(custom).with(TestInterceptor::new);
299 let mut chain = registry.build();
300
301 let pkt = dummy_rtp_packet();
302 let pkt_message = pkt.message.clone();
303 chain.handle_write(pkt).unwrap();
304 assert_eq!(chain.poll_write().unwrap().message, pkt_message);
305 }
306
307 // Test the helper function pattern
308 fn register_test_interceptors<P: Interceptor>(
309 registry: Registry<P>,
310 ) -> Registry<TestInterceptor<TestInterceptor<P>>> {
311 registry
312 .with(TestInterceptor::with_name("first"))
313 .with(TestInterceptor::with_name("second"))
314 }
315
316 #[test]
317 fn test_helper_function_pattern() {
318 let registry = Registry::new();
319 let registry = register_test_interceptors(registry);
320 let mut chain = registry.build();
321
322 let pkt = dummy_rtp_packet();
323 chain.handle_read(pkt).unwrap();
324 assert!(chain.poll_read().is_some());
325 assert_eq!(chain.name, "second");
326 assert_eq!(chain.inner.name, "first");
327 }
328}