Skip to main content

appcore_api/
router.rs

1// =============================================================================
2//        #######
3//     ###       ###     F: router.rs
4//    ##   ## ##   ##    P: AppCore-Runtime
5//         ## ##
6//                       C: 2026/05/31 13:38:42 by dnettoRaw
7//    ##   ## ##   ##    U: 2026/07/23 13:45:20 by dnettoRaw
8//      ###########      S: 1.0.1-rc.8
9// =============================================================================
10
11//! In-memory API router contracts.
12
13use std::collections::HashMap;
14
15use appcore_core::{RuntimeError, RuntimeResult};
16
17use crate::api::{ApiRequest, ApiResponse};
18use crate::command_endpoint::CommandEndpoint;
19use crate::query_endpoint::{QueryEndpoint, QueryName};
20
21/// Minimal router for one command endpoint and multiple query endpoints.
22#[derive(Default)]
23pub struct ApiRouter {
24    command_endpoint: Option<Box<dyn CommandEndpoint>>,
25    queries: HashMap<QueryName, Box<dyn QueryEndpoint>>,
26}
27
28impl ApiRouter {
29    /// Creates an empty transport-neutral router.
30    pub fn new() -> Self {
31        Self::default()
32    }
33
34    /// Replaces the command endpoint used by this router.
35    pub fn set_command_endpoint<E: CommandEndpoint + 'static>(&mut self, endpoint: E) {
36        self.command_endpoint = Some(Box::new(endpoint));
37    }
38
39    /// Registers one uniquely named application query endpoint.
40    pub fn register_query<E: QueryEndpoint + 'static>(&mut self, endpoint: E) -> RuntimeResult<()> {
41        let name = endpoint.query_name().clone();
42        if self.queries.contains_key(&name) {
43            return Err(RuntimeError::RegistryItemAlreadyRegistered {
44                kind: "query",
45                name: name.as_str().to_string(),
46            });
47        }
48        self.queries.insert(name, Box::new(endpoint));
49        Ok(())
50    }
51
52    /// Reports whether a query endpoint is registered.
53    pub fn has_query(&self, name: &QueryName) -> bool {
54        self.queries.contains_key(name)
55    }
56
57    /// Returns registered query names in deterministic lexical order.
58    pub fn query_names(&self) -> Vec<QueryName> {
59        let mut names = self.queries.keys().cloned().collect::<Vec<_>>();
60        names.sort_by(|left, right| left.as_str().cmp(right.as_str()));
61        names
62    }
63
64    /// Dispatches a request to a named query endpoint.
65    pub fn dispatch_query(
66        &self,
67        name: &QueryName,
68        request: ApiRequest,
69    ) -> RuntimeResult<ApiResponse> {
70        let Some(endpoint) = self.queries.get(name) else {
71            return Err(RuntimeError::RegistryItemNotFound {
72                kind: "query",
73                name: name.as_str().to_string(),
74            });
75        };
76        endpoint.handle_query(request)
77    }
78
79    /// Dispatches a request to the configured command endpoint.
80    pub fn dispatch_command(&self, request: ApiRequest) -> RuntimeResult<ApiResponse> {
81        let Some(endpoint) = &self.command_endpoint else {
82            return Err(RuntimeError::MissingConfiguration {
83                name: "command_endpoint",
84            });
85        };
86        endpoint.handle_command(request)
87    }
88}
89
90#[cfg(test)]
91#[path = "router_tests.rs"]
92mod tests;