1use std::collections::HashMap;
14use std::sync::Arc;
15
16use appcore_core::{RuntimeError, RuntimeResult};
17
18use crate::api::{ApiRequest, ApiResponse};
19use crate::command_endpoint::CommandEndpoint;
20use crate::query_endpoint::{QueryEndpoint, QueryName};
21
22#[derive(Clone, Default)]
28pub struct ApiRouter {
29 state: Arc<ApiRouterState>,
30}
31
32#[derive(Clone, Default)]
33struct ApiRouterState {
34 command_endpoint: Option<Arc<dyn CommandEndpoint>>,
35 queries: HashMap<QueryName, Arc<dyn QueryEndpoint>>,
36 queries_frozen: bool,
37}
38
39impl ApiRouter {
40 pub fn new() -> Self {
42 Self::default()
43 }
44
45 pub fn set_command_endpoint<E: CommandEndpoint + 'static>(&mut self, endpoint: E) {
47 Arc::make_mut(&mut self.state).command_endpoint = Some(Arc::new(endpoint));
48 }
49
50 pub fn register_query<E: QueryEndpoint + 'static>(&mut self, endpoint: E) -> RuntimeResult<()> {
54 let name = endpoint.query_name().clone();
55 if self.state.queries_frozen {
56 return Err(RuntimeError::InvalidRequest {
57 kind: "query",
58 reason: "router_frozen",
59 });
60 }
61 if self.state.queries.contains_key(&name) {
62 return Err(RuntimeError::RegistryItemAlreadyRegistered {
63 kind: "query",
64 name: name.as_str().to_string(),
65 });
66 }
67 Arc::make_mut(&mut self.state)
68 .queries
69 .insert(name, Arc::new(endpoint));
70 Ok(())
71 }
72
73 pub fn freeze_queries(&mut self) {
75 Arc::make_mut(&mut self.state).queries_frozen = true;
76 }
77
78 pub fn queries_are_frozen(&self) -> bool {
80 self.state.queries_frozen
81 }
82
83 pub fn has_query(&self, name: &QueryName) -> bool {
85 self.state.queries.contains_key(name)
86 }
87
88 pub fn query_names(&self) -> Vec<QueryName> {
90 let mut names = self.state.queries.keys().cloned().collect::<Vec<_>>();
91 names.sort_by(|left, right| left.as_str().cmp(right.as_str()));
92 names
93 }
94
95 pub fn dispatch_query(
97 &self,
98 name: &QueryName,
99 request: ApiRequest,
100 ) -> RuntimeResult<ApiResponse> {
101 let Some(endpoint) = self.state.queries.get(name) else {
102 return Err(RuntimeError::RegistryItemNotFound {
103 kind: "query",
104 name: name.as_str().to_string(),
105 });
106 };
107 endpoint.handle_query(request)
108 }
109
110 pub fn dispatch_command(&self, request: ApiRequest) -> RuntimeResult<ApiResponse> {
112 let Some(endpoint) = &self.state.command_endpoint else {
113 return Err(RuntimeError::MissingConfiguration {
114 name: "command_endpoint",
115 });
116 };
117 endpoint.handle_command(request)
118 }
119}
120
121#[cfg(test)]
122#[path = "router_tests.rs"]
123mod tests;