1use std::any::{Any, TypeId, type_name};
33use std::collections::HashMap;
34
35use serde::{Deserialize, Serialize};
36
37use crate::protocol::{Command, ProtocolMeta, ProtocolQuery};
38
39pub trait CommandHandler<C: Command, R: Serialize>: Send + Sync {
48 fn handle(&self, cmd: C) -> Result<R, String>;
50}
51
52pub trait QueryHandler<Q: ProtocolQuery, R: Serialize>: Send + Sync {
57 fn handle(&self, query: Q) -> Result<R, String>;
59}
60
61trait ErasedCommandHandler: Send + Sync {
69 fn handle_json(&self, request: &str) -> Result<String, String>;
71
72 fn handle_any(&self, cmd: Box<dyn Any>) -> Result<Box<dyn Any>, String>;
74}
75
76trait ErasedQueryHandler: Send + Sync {
78 fn handle_json(&self, request: &str) -> Result<String, String>;
79 fn handle_any(&self, query: Box<dyn Any>) -> Result<Box<dyn Any>, String>;
80}
81
82struct CommandHandlerWrapper<C, R, H> {
84 handler: H,
85 _phantom: std::marker::PhantomData<(C, R)>,
86}
87
88impl<C, R, H> ErasedCommandHandler for CommandHandlerWrapper<C, R, H>
89where
90 C: Command,
91 R: Serialize + for<'de> Deserialize<'de> + Send + Sync + 'static,
92 H: CommandHandler<C, R> + Send + Sync,
93{
94 fn handle_json(&self, request: &str) -> Result<String, String> {
95 let cmd: C = serde_json::from_str(request).map_err(|e| e.to_string())?;
96 let response = self.handler.handle(cmd)?;
97 serde_json::to_string(&response).map_err(|e| e.to_string())
98 }
99
100 fn handle_any(&self, cmd: Box<dyn Any>) -> Result<Box<dyn Any>, String> {
101 let cmd = *cmd.downcast::<C>().map_err(|_| "type mismatch")?;
102 let response = self.handler.handle(cmd)?;
103 Ok(Box::new(response))
104 }
105}
106
107struct QueryHandlerWrapper<Q, R, H> {
109 handler: H,
110 _phantom: std::marker::PhantomData<(Q, R)>,
111}
112
113impl<Q, R, H> ErasedQueryHandler for QueryHandlerWrapper<Q, R, H>
114where
115 Q: ProtocolQuery,
116 R: Serialize + for<'de> Deserialize<'de> + Send + Sync + 'static,
117 H: QueryHandler<Q, R> + Send + Sync,
118{
119 fn handle_json(&self, request: &str) -> Result<String, String> {
120 let query: Q = serde_json::from_str(request).map_err(|e| e.to_string())?;
121 let response = self.handler.handle(query)?;
122 serde_json::to_string(&response).map_err(|e| e.to_string())
123 }
124
125 fn handle_any(&self, query: Box<dyn Any>) -> Result<Box<dyn Any>, String> {
126 let query = *query.downcast::<Q>().map_err(|_| "type mismatch")?;
127 let response = self.handler.handle(query)?;
128 Ok(Box::new(response))
129 }
130}
131
132struct CommandEntry(std::sync::Arc<dyn ErasedCommandHandler>);
138struct QueryEntry(std::sync::Arc<dyn ErasedQueryHandler>);
139
140pub struct HandlerRegistry {
155 commands_by_type: HashMap<TypeId, CommandEntry>,
157 commands_by_name: HashMap<String, CommandEntry>,
159 queries_by_type: HashMap<TypeId, QueryEntry>,
161 queries_by_name: HashMap<String, QueryEntry>,
163}
164
165impl HandlerRegistry {
166 pub fn new() -> Self {
168 Self {
169 commands_by_type: HashMap::new(),
170 commands_by_name: HashMap::new(),
171 queries_by_type: HashMap::new(),
172 queries_by_name: HashMap::new(),
173 }
174 }
175
176 pub fn register_command<C, R, H>(&mut self, handler: H)
185 where
186 C: Command + ProtocolMeta,
187 R: Serialize + for<'de> Deserialize<'de> + Send + Sync + 'static,
188 H: CommandHandler<C, R> + 'static,
189 {
190 let type_id = TypeId::of::<C>();
191 let protocol_name = C::name().to_string();
192 assert!(
193 !self.commands_by_type.contains_key(&type_id),
194 "duplicate command handler for type {}",
195 protocol_name
196 );
197 assert!(
198 !self.commands_by_name.contains_key(&protocol_name),
199 "duplicate command handler for protocol name {:?} (different type, same name)",
200 protocol_name
201 );
202 let shared: std::sync::Arc<dyn ErasedCommandHandler> =
203 std::sync::Arc::new(CommandHandlerWrapper {
204 handler,
205 _phantom: std::marker::PhantomData::<(C, R)>,
206 });
207 self.commands_by_type
208 .insert(type_id, CommandEntry(shared.clone()));
209 self.commands_by_name
210 .insert(protocol_name, CommandEntry(shared));
211 }
212
213 pub fn register_query<Q, R, H>(&mut self, handler: H)
222 where
223 Q: ProtocolQuery + ProtocolMeta,
224 R: Serialize + for<'de> Deserialize<'de> + Send + Sync + 'static,
225 H: QueryHandler<Q, R> + 'static,
226 {
227 let type_id = TypeId::of::<Q>();
228 let protocol_name = Q::name().to_string();
229 assert!(
230 !self.queries_by_type.contains_key(&type_id),
231 "duplicate query handler for type {}",
232 protocol_name
233 );
234 assert!(
235 !self.queries_by_name.contains_key(&protocol_name),
236 "duplicate query handler for protocol name {:?} (different type, same name)",
237 protocol_name
238 );
239 let shared: std::sync::Arc<dyn ErasedQueryHandler> =
240 std::sync::Arc::new(QueryHandlerWrapper {
241 handler,
242 _phantom: std::marker::PhantomData::<(Q, R)>,
243 });
244 self.queries_by_type
245 .insert(type_id, QueryEntry(shared.clone()));
246 self.queries_by_name
247 .insert(protocol_name, QueryEntry(shared));
248 }
249
250 pub fn dispatch_command<C: Command + 'static, R: 'static>(&self, cmd: C) -> Result<R, String> {
256 let entry = self
257 .commands_by_type
258 .get(&TypeId::of::<C>())
259 .ok_or_else(|| format!("no handler for command {}", type_name::<C>()))?;
260
261 let result = entry.0.handle_any(Box::new(cmd))?;
262 let boxed = result
263 .downcast::<R>()
264 .map_err(|_| "response type mismatch".to_string())?;
265 Ok(*boxed)
266 }
267
268 pub fn dispatch_query<Q: ProtocolQuery + 'static, R: 'static>(
270 &self,
271 query: Q,
272 ) -> Result<R, String> {
273 let entry = self
274 .queries_by_type
275 .get(&TypeId::of::<Q>())
276 .ok_or_else(|| format!("no handler for query {}", type_name::<Q>()))?;
277
278 let result = entry.0.handle_any(Box::new(query))?;
279 let boxed = result
280 .downcast::<R>()
281 .map_err(|_| "response type mismatch".to_string())?;
282 Ok(*boxed)
283 }
284
285 pub fn dispatch_command_json(
295 &self,
296 protocol_name: &str,
297 request_json: &str,
298 ) -> Result<String, String> {
299 let entry = self
300 .commands_by_name
301 .get(protocol_name)
302 .ok_or_else(|| format!("unknown command: {}", protocol_name))?;
303 entry.0.handle_json(request_json)
304 }
305
306 pub fn dispatch_query_json(
308 &self,
309 protocol_name: &str,
310 request_json: &str,
311 ) -> Result<String, String> {
312 let entry = self
313 .queries_by_name
314 .get(protocol_name)
315 .ok_or_else(|| format!("unknown query: {}", protocol_name))?;
316 entry.0.handle_json(request_json)
317 }
318
319 pub fn command_count(&self) -> usize {
321 self.commands_by_type.len()
322 }
323
324 pub fn query_count(&self) -> usize {
326 self.queries_by_type.len()
327 }
328}
329
330impl Default for HandlerRegistry {
331 fn default() -> Self {
332 Self::new()
333 }
334}
335
336#[cfg(test)]
341mod tests {
342 use super::*;
343
344 use crate::protocol::ProtocolKind;
347
348 #[derive(Debug, Serialize, Deserialize)]
349 struct SpawnUnit {
350 unit_id: u64,
351 location_id: u64,
352 }
353 impl Command for SpawnUnit {}
354 impl ProtocolMeta for SpawnUnit {
355 fn name() -> &'static str {
356 "SpawnUnit"
357 }
358 fn kind() -> ProtocolKind {
359 ProtocolKind::Command
360 }
361 }
362
363 #[derive(Debug, Serialize, Deserialize)]
364 struct GetWorldSnapshot;
365 impl ProtocolQuery for GetWorldSnapshot {}
366 impl ProtocolMeta for GetWorldSnapshot {
367 fn name() -> &'static str {
368 "GetWorldSnapshot"
369 }
370 fn kind() -> ProtocolKind {
371 ProtocolKind::Query
372 }
373 }
374
375 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
376 struct WorldSnapshot {
377 units_active: u32,
378 units_idle: u32,
379 }
380
381 #[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
382 struct DispatchResult {
383 ok: bool,
384 }
385
386 struct UnitSpawner;
389
390 impl CommandHandler<SpawnUnit, DispatchResult> for UnitSpawner {
391 fn handle(&self, _cmd: SpawnUnit) -> Result<DispatchResult, String> {
392 Ok(DispatchResult { ok: true })
393 }
394 }
395
396 struct WorldQuerier;
397
398 impl QueryHandler<GetWorldSnapshot, WorldSnapshot> for WorldQuerier {
399 fn handle(&self, _query: GetWorldSnapshot) -> Result<WorldSnapshot, String> {
400 Ok(WorldSnapshot {
401 units_active: 2,
402 units_idle: 5,
403 })
404 }
405 }
406
407 #[test]
410 fn register_and_dispatch_command_local() {
411 let mut registry = HandlerRegistry::new();
412 registry.register_command::<SpawnUnit, DispatchResult, _>(UnitSpawner);
413
414 let result: DispatchResult = registry
415 .dispatch_command(SpawnUnit {
416 unit_id: 1,
417 location_id: 42,
418 })
419 .unwrap();
420
421 assert!(result.ok);
422 }
423
424 #[test]
425 fn register_and_dispatch_query_local() {
426 let mut registry = HandlerRegistry::new();
427 registry.register_query::<GetWorldSnapshot, WorldSnapshot, _>(WorldQuerier);
428
429 let snapshot: WorldSnapshot = registry.dispatch_query(GetWorldSnapshot).unwrap();
430
431 assert_eq!(snapshot.units_active, 2);
432 assert_eq!(snapshot.units_idle, 5);
433 }
434
435 #[test]
436 fn dispatch_command_json_by_protocol_name() {
437 let mut registry = HandlerRegistry::new();
438 registry.register_command::<SpawnUnit, DispatchResult, _>(UnitSpawner);
439
440 let response = registry
442 .dispatch_command_json("SpawnUnit", r#"{"unit_id":1,"location_id":42}"#)
443 .unwrap();
444
445 assert!(response.contains("true"));
446 }
447
448 #[test]
449 fn dispatch_query_json_by_protocol_name() {
450 let mut registry = HandlerRegistry::new();
451 registry.register_query::<GetWorldSnapshot, WorldSnapshot, _>(WorldQuerier);
452
453 let response = registry
454 .dispatch_query_json("GetWorldSnapshot", "null")
455 .unwrap();
456
457 let snapshot: WorldSnapshot = serde_json::from_str(&response).unwrap();
458 assert_eq!(snapshot.units_idle, 5);
459 }
460
461 #[test]
462 fn same_registry_serves_both_adapters() {
463 let mut registry = HandlerRegistry::new();
464 registry.register_command::<SpawnUnit, DispatchResult, _>(UnitSpawner);
465 registry.register_query::<GetWorldSnapshot, WorldSnapshot, _>(WorldQuerier);
466
467 let local_result: DispatchResult = registry
469 .dispatch_command(SpawnUnit {
470 unit_id: 1,
471 location_id: 42,
472 })
473 .unwrap();
474 assert!(local_result.ok);
475
476 let local_snapshot: WorldSnapshot = registry.dispatch_query(GetWorldSnapshot).unwrap();
477 assert_eq!(local_snapshot.units_idle, 5);
478
479 let json_result = registry
481 .dispatch_command_json("SpawnUnit", r#"{"unit_id":2,"location_id":99}"#)
482 .unwrap();
483 assert!(json_result.contains("true"));
484
485 let json_snapshot = registry
486 .dispatch_query_json("GetWorldSnapshot", "null")
487 .unwrap();
488 let remote_snapshot: WorldSnapshot = serde_json::from_str(&json_snapshot).unwrap();
489 assert_eq!(remote_snapshot.units_idle, 5);
490 }
491
492 #[test]
495 fn descriptor_driven_remote_dispatch() {
496 use crate::codegen::generate_descriptors;
497 use crate::manifest::{ManifestEntry, ManifestField, ProtocolManifest};
498
499 let manifest = ProtocolManifest {
501 manifest_version: "2".into(),
502 protocol_version: "test@0.1".into(),
503 default_surface: "default".into(),
504 surfaces: vec!["default".into()],
505 commands: vec![ManifestEntry {
506 name: "SpawnUnit".into(),
507 kind: ProtocolKind::Command,
508 fields: vec![
509 ManifestField {
510 name: "unit_id".into(),
511 ty: "u64".into(),
512 },
513 ManifestField {
514 name: "location_id".into(),
515 ty: "u64".into(),
516 },
517 ],
518 doc: "".into(),
519 surfaces: vec![],
520 }],
521 queries: vec![ManifestEntry {
522 name: "GetWorldSnapshot".into(),
523 kind: ProtocolKind::Query,
524 fields: vec![],
525 doc: "".into(),
526 surfaces: vec![],
527 }],
528 events: vec![],
529 dtos: vec![],
530 };
531
532 let desc_set = generate_descriptors(&manifest);
534
535 let mut registry = HandlerRegistry::new();
537 registry.register_command::<SpawnUnit, DispatchResult, _>(UnitSpawner);
538 registry.register_query::<GetWorldSnapshot, WorldSnapshot, _>(WorldQuerier);
539
540 for surface in &desc_set.surfaces {
542 for desc in &surface.descriptors {
543 match desc.kind {
544 ProtocolKind::Command => {
545 let response = registry
546 .dispatch_command_json(&desc.name, r#"{"unit_id":1,"location_id":42}"#)
547 .unwrap();
548 assert!(response.contains("true"));
549 }
550 ProtocolKind::Query => {
551 let response = registry.dispatch_query_json(&desc.name, "null").unwrap();
552 let snapshot: WorldSnapshot = serde_json::from_str(&response).unwrap();
553 assert_eq!(snapshot.units_idle, 5);
554 }
555 _ => {}
556 }
557 }
558 }
559 }
560
561 #[test]
562 fn missing_handler_returns_error() {
563 let registry = HandlerRegistry::new();
564 let result = registry.dispatch_command::<SpawnUnit, DispatchResult>(SpawnUnit {
565 unit_id: 1,
566 location_id: 1,
567 });
568 assert!(result.is_err());
569 assert!(result.unwrap_err().contains("no handler"));
570 }
571
572 #[test]
573 #[should_panic(expected = "duplicate command handler")]
574 fn duplicate_handler_panics() {
575 let mut registry = HandlerRegistry::new();
576 registry.register_command::<SpawnUnit, DispatchResult, _>(UnitSpawner);
577 registry.register_command::<SpawnUnit, DispatchResult, _>(UnitSpawner);
578 }
579
580 #[test]
581 fn registry_counts() {
582 let mut registry = HandlerRegistry::new();
583 assert_eq!(registry.command_count(), 0);
584 assert_eq!(registry.query_count(), 0);
585
586 registry.register_command::<SpawnUnit, DispatchResult, _>(UnitSpawner);
587 registry.register_query::<GetWorldSnapshot, WorldSnapshot, _>(WorldQuerier);
588
589 assert_eq!(registry.command_count(), 1);
590 assert_eq!(registry.query_count(), 1);
591 }
592
593 #[test]
596 #[should_panic(expected = "duplicate command handler for protocol name")]
597 fn name_collision_panics() {
598 #[derive(Debug, Serialize, Deserialize)]
600 struct SpawnUnitV2 {
601 unit_id: u64,
602 }
603 impl Command for SpawnUnitV2 {}
604 impl ProtocolMeta for SpawnUnitV2 {
605 fn name() -> &'static str {
606 "SpawnUnit" }
608 fn kind() -> ProtocolKind {
609 ProtocolKind::Command
610 }
611 }
612
613 struct V2Spawner;
614 impl CommandHandler<SpawnUnitV2, DispatchResult> for V2Spawner {
615 fn handle(&self, _cmd: SpawnUnitV2) -> Result<DispatchResult, String> {
616 Ok(DispatchResult { ok: false })
617 }
618 }
619
620 let mut registry = HandlerRegistry::new();
621 registry.register_command::<SpawnUnit, DispatchResult, _>(UnitSpawner);
622 registry.register_command::<SpawnUnitV2, DispatchResult, _>(V2Spawner);
624 }
625
626 #[test]
629 fn single_registry_serves_multiple_surfaces() {
630 use crate::codegen::generate_descriptors;
631 use crate::manifest::{ManifestEntry, ManifestField, ProtocolManifest};
632
633 #[derive(Debug, Serialize, Deserialize)]
637 struct AdminReset {
638 zone_id: u64,
639 }
640 impl Command for AdminReset {}
641 impl ProtocolMeta for AdminReset {
642 fn name() -> &'static str {
643 "AdminReset"
644 }
645 fn kind() -> ProtocolKind {
646 ProtocolKind::Command
647 }
648 }
649
650 struct ZoneResetter;
651 impl CommandHandler<AdminReset, DispatchResult> for ZoneResetter {
652 fn handle(&self, _cmd: AdminReset) -> Result<DispatchResult, String> {
653 Ok(DispatchResult { ok: true })
654 }
655 }
656
657 let manifest = ProtocolManifest {
658 manifest_version: "2".into(),
659 protocol_version: "test@0.1".into(),
660 default_surface: "gameplay".into(),
661 surfaces: vec!["authority".into(), "gameplay".into()],
662 commands: vec![
663 ManifestEntry {
664 name: "SpawnUnit".into(),
665 kind: ProtocolKind::Command,
666 fields: vec![ManifestField {
667 name: "unit_id".into(),
668 ty: "u64".into(),
669 }],
670 doc: "".into(),
671 surfaces: vec![],
672 },
673 ManifestEntry {
674 name: "AdminReset".into(),
675 kind: ProtocolKind::Command,
676 fields: vec![ManifestField {
677 name: "zone_id".into(),
678 ty: "u64".into(),
679 }],
680 doc: "".into(),
681 surfaces: vec!["authority".into()],
682 },
683 ],
684 queries: vec![],
685 events: vec![],
686 dtos: vec![],
687 };
688
689 let mut registry = HandlerRegistry::new();
691 registry.register_command::<SpawnUnit, DispatchResult, _>(UnitSpawner);
692 registry.register_command::<AdminReset, DispatchResult, _>(ZoneResetter);
693
694 let descs = generate_descriptors(&manifest);
696
697 for surface in &descs.surfaces {
700 for desc in &surface.descriptors {
701 if desc.kind == ProtocolKind::Command {
702 let payload = match desc.name.as_str() {
703 "SpawnUnit" => r#"{"unit_id":1,"location_id":42}"#,
704 "AdminReset" => r#"{"zone_id":7}"#,
705 other => panic!("unexpected descriptor: {other}"),
706 };
707 let response = registry.dispatch_command_json(&desc.name, payload).unwrap();
708 assert!(response.contains("true"));
709 }
710 }
711 }
712
713 let gameplay = descs
715 .surfaces
716 .iter()
717 .find(|s| s.name == "gameplay")
718 .unwrap();
719 assert_eq!(gameplay.descriptors.len(), 1);
720 assert_eq!(gameplay.descriptors[0].name, "SpawnUnit");
721
722 let authority = descs
724 .surfaces
725 .iter()
726 .find(|s| s.name == "authority")
727 .unwrap();
728 assert_eq!(authority.descriptors.len(), 1);
729 assert_eq!(authority.descriptors[0].name, "AdminReset");
730 }
731}