1use std::any::Any;
5use std::collections::HashMap;
6use std::sync::{Arc, Mutex};
7
8use serde_json::Value;
9
10use crate::{CdpError, DomainHandler, EventSender};
11
12pub struct DomainRegistry<H: DomainHandler> {
16 handlers: Mutex<HashMap<&'static str, H>>,
17}
18
19pub struct EmptyHandler;
24
25impl DomainHandler for EmptyHandler {
26 fn domain_name(&self) -> &'static str {
27 ""
28 }
29 fn handle_command(&self, _: &str, _: Value, _: &dyn EventSender) -> Result<Value, CdpError> {
30 Err(CdpError {
31 code: -32601,
32 message: "empty handler".into(),
33 })
34 }
35}
36
37pub trait RegistryDispatch: Send + Sync + 'static {
47 fn dispatch_command(
49 &self,
50 method: &str,
51 params: Value,
52 event_sender: &dyn EventSender,
53 ) -> Option<Result<Value, CdpError>>;
54
55 fn dispatch_message(
63 &self,
64 msg: &crate::protocol::CdpMessage,
65 ws_target_id: &str,
66 event_sender: &dyn EventSender,
67 ) -> Option<Result<Value, CdpError>> {
68 let _ = ws_target_id;
69 self.dispatch_command(
70 &msg.method,
71 msg.params.clone().unwrap_or_default(),
72 event_sender,
73 )
74 }
75
76 fn notify_session_created(&self, domain: &str, session_id: &str);
78
79 fn notify_session_destroyed(&self, domains: &[String], session_id: &str);
81
82 fn has_domain(&self, domain: &str) -> bool;
84
85 fn as_any(&self) -> &dyn Any;
87}
88
89impl<H: DomainHandler + 'static> RegistryDispatch for DomainRegistry<H> {
91 fn dispatch_command(
92 &self,
93 method: &str,
94 params: Value,
95 event_sender: &dyn EventSender,
96 ) -> Option<Result<Value, CdpError>> {
97 DomainRegistry::dispatch_command(self, method, params, event_sender)
98 }
99
100 fn notify_session_created(&self, domain: &str, session_id: &str) {
101 DomainRegistry::notify_session_created(self, domain, session_id)
102 }
103
104 fn notify_session_destroyed(&self, domains: &[String], session_id: &str) {
105 DomainRegistry::notify_session_destroyed(self, domains, session_id)
106 }
107
108 fn has_domain(&self, domain: &str) -> bool {
109 DomainRegistry::has_domain(self, domain)
110 }
111
112 fn as_any(&self) -> &dyn Any {
113 self
114 }
115}
116
117impl dyn RegistryDispatch {
119 pub fn downcast_ref<T: 'static>(&self) -> Option<&T> {
120 self.as_any().downcast_ref::<T>()
121 }
122}
123
124impl<H: DomainHandler> Default for DomainRegistry<H> {
129 fn default() -> Self {
130 Self::new()
131 }
132}
133
134impl<H: DomainHandler> DomainRegistry<H> {
135 pub fn new() -> Self {
136 DomainRegistry {
137 handlers: Mutex::new(HashMap::new()),
138 }
139 }
140
141 pub fn register(&self, handler: H) -> Result<(), String> {
144 let mut map = self
145 .handlers
146 .lock()
147 .map_err(|_| -> String { "lock poisoned".into() })?;
148 let name = handler.domain_name();
149 if map.contains_key(name) {
150 return Err(format!("domain '{}' already registered", name));
151 }
152 map.insert(name, handler);
153 Ok(())
154 }
155
156 pub fn dispatch_command(
159 &self,
160 method: &str,
161 params: Value,
162 event_sender: &dyn EventSender,
163 ) -> Option<Result<Value, CdpError>> {
164 let domain = method.split('.').next().unwrap_or("");
165 let map = self.handlers.lock().ok()?;
166 let handler = map.get(domain)?;
167 Some(handler.handle_command(method, params, event_sender))
168 }
169
170 pub fn notify_session_created(&self, domain: &str, session_id: &str) {
172 if let Ok(map) = self.handlers.lock() {
173 if let Some(handler) = map.get(domain) {
174 handler.on_session_created(session_id);
175 }
176 }
177 }
178
179 pub fn notify_session_destroyed(&self, domains: &[String], session_id: &str) {
181 if let Ok(map) = self.handlers.lock() {
182 for domain in domains {
183 if let Some(handler) = map.get(domain.as_str()) {
184 handler.on_session_destroyed(session_id);
185 }
186 }
187 }
188 }
189
190 pub fn has_domain(&self, domain: &str) -> bool {
192 self.handlers
193 .lock()
194 .map(|m| m.contains_key(domain))
195 .unwrap_or(false)
196 }
197}
198
199pub type SharedRegistry = Arc<dyn RegistryDispatch>;
201
202#[cfg(test)]
203mod tests {
204 use serde_json::json;
205
206 use super::*;
207 use crate::{CdpError, DomainHandler, EventSender};
208
209 struct NoopSender;
210 impl EventSender for NoopSender {
211 fn send_event(&self, _method: &str, _params: Value) {}
212 }
213
214 struct MockHandler {
215 name: &'static str,
216 }
217
218 impl DomainHandler for MockHandler {
219 fn domain_name(&self) -> &'static str {
220 self.name
221 }
222
223 fn handle_command(
224 &self,
225 _command: &str,
226 _params: Value,
227 _event_sender: &dyn EventSender,
228 ) -> Result<Value, CdpError> {
229 Ok(json!({}))
230 }
231
232 fn on_session_created(&self, _session_id: &str) {}
233
234 fn on_session_destroyed(&self, _session_id: &str) {}
235 }
236
237 #[test]
239 fn new_registry_is_empty() {
240 let reg = DomainRegistry::<MockHandler>::new();
241 assert!(!reg.has_domain("Page"));
242 }
243
244 #[test]
246 fn default_same_as_new() {
247 let via_new = DomainRegistry::<MockHandler>::new();
248 let via_default = DomainRegistry::<MockHandler>::default();
249 assert!(!via_new.has_domain("Page"));
250 assert!(!via_default.has_domain("Page"));
251 }
252
253 #[test]
255 fn register_handler_then_has_domain() {
256 let reg = DomainRegistry::<MockHandler>::new();
257 reg.register(MockHandler { name: "Page" }).unwrap();
258 assert!(reg.has_domain("Page"));
259 }
260
261 #[test]
263 fn register_duplicate_returns_err() {
264 let reg = DomainRegistry::<MockHandler>::new();
265 reg.register(MockHandler { name: "Page" }).unwrap();
266 let err = reg.register(MockHandler { name: "Page" }).unwrap_err();
267 assert!(err.contains("'Page'"));
268 }
269
270 #[test]
272 fn register_different_domains_both_present() {
273 let reg = DomainRegistry::<MockHandler>::new();
274 reg.register(MockHandler { name: "Page" }).unwrap();
275 reg.register(MockHandler { name: "Runtime" }).unwrap();
276 assert!(reg.has_domain("Page"));
277 assert!(reg.has_domain("Runtime"));
278 }
279
280 #[test]
282 fn has_domain_unregistered_returns_false() {
283 let reg = DomainRegistry::<MockHandler>::new();
284 reg.register(MockHandler { name: "Page" }).unwrap();
285 assert!(!reg.has_domain("DOM"));
286 }
287
288 #[test]
290 fn dispatch_command_registered_returns_some_ok() {
291 let reg = DomainRegistry::<MockHandler>::new();
292 reg.register(MockHandler { name: "Page" }).unwrap();
293 let result = reg.dispatch_command("Page.navigate", json!(null), &NoopSender);
294 assert!(result.is_some());
295 assert!(result.unwrap().is_ok());
296 }
297
298 #[test]
300 fn dispatch_command_unregistered_returns_none() {
301 let reg = DomainRegistry::<MockHandler>::new();
302 let result = reg.dispatch_command("DOM.getDocument", json!(null), &NoopSender);
303 assert!(result.is_none());
304 }
305
306 #[test]
308 fn dispatch_command_extracts_domain_from_method() {
309 let reg = DomainRegistry::<MockHandler>::new();
310 reg.register(MockHandler { name: "Runtime" }).unwrap();
311 let result = reg.dispatch_command("Runtime.evaluate", json!(null), &NoopSender);
312 assert!(result.is_some());
313 }
314
315 #[test]
317 fn notify_session_created_unregistered_no_panic() {
318 let reg = DomainRegistry::<MockHandler>::new();
319 reg.notify_session_created("Page", "sess-1");
320 }
321
322 #[test]
324 fn notify_session_destroyed_unregistered_no_panic() {
325 let reg = DomainRegistry::<MockHandler>::new();
326 reg.notify_session_destroyed(&["Page".to_string()], "sess-1");
327 }
328
329 #[test]
331 fn dispatch_command_with_valid_json_params() {
332 let reg = DomainRegistry::<MockHandler>::new();
333 reg.register(MockHandler { name: "Page" }).unwrap();
334 let result = reg.dispatch_command(
335 "Page.navigate",
336 json!({ "url": "https://example.com" }),
337 &NoopSender,
338 );
339 assert!(result.is_some());
340 assert!(result.unwrap().is_ok());
341 }
342
343 #[test]
346 fn enum_dispatch_with_concrete_type() {
347 enum TestDispatch {
348 Page(MockHandler),
349 Runtime(MockHandler),
350 }
351 impl DomainHandler for TestDispatch {
352 fn domain_name(&self) -> &'static str {
353 match self {
354 Self::Page(h) => h.domain_name(),
355 Self::Runtime(h) => h.domain_name(),
356 }
357 }
358 fn handle_command(
359 &self,
360 cmd: &str,
361 p: Value,
362 s: &dyn EventSender,
363 ) -> Result<Value, CdpError> {
364 match self {
365 Self::Page(h) => h.handle_command(cmd, p, s),
366 Self::Runtime(h) => h.handle_command(cmd, p, s),
367 }
368 }
369 }
370 let reg = DomainRegistry::<TestDispatch>::new();
371 reg.register(TestDispatch::Page(MockHandler { name: "Page" }))
372 .unwrap();
373 reg.register(TestDispatch::Runtime(MockHandler { name: "Runtime" }))
374 .unwrap();
375 assert!(reg.has_domain("Page"));
376 assert!(reg.has_domain("Runtime"));
377 let result = reg.dispatch_command("Page.navigate", json!(null), &NoopSender);
378 assert!(result.is_some());
379 assert!(result.unwrap().is_ok());
380 }
381}