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.query_names_iter().cloned().collect::<Vec<_>>();
91 names.sort_by(|left, right| left.as_str().cmp(right.as_str()));
92 names
93 }
94
95 pub fn query_names_iter(&self) -> impl ExactSizeIterator<Item = &QueryName> {
101 self.state.queries.keys()
102 }
103
104 pub fn dispatch_query(
106 &self,
107 name: &QueryName,
108 request: ApiRequest,
109 ) -> RuntimeResult<ApiResponse> {
110 let Some(endpoint) = self.state.queries.get(name) else {
111 return Err(RuntimeError::RegistryItemNotFound {
112 kind: "query",
113 name: name.as_str().to_string(),
114 });
115 };
116 endpoint.handle_query(request)
117 }
118
119 pub fn dispatch_command(&self, request: ApiRequest) -> RuntimeResult<ApiResponse> {
121 let Some(endpoint) = &self.state.command_endpoint else {
122 return Err(RuntimeError::MissingConfiguration {
123 name: "command_endpoint",
124 });
125 };
126 endpoint.handle_command(request)
127 }
128}
129
130#[cfg(test)]
131#[path = "router_tests.rs"]
132mod tests;