use std::collections::HashMap;
use std::sync::Arc;
use appcore_core::{RuntimeError, RuntimeResult};
use crate::api::{ApiRequest, ApiResponse};
use crate::command_endpoint::CommandEndpoint;
use crate::query_endpoint::{QueryEndpoint, QueryName};
#[derive(Clone, Default)]
pub struct ApiRouter {
state: Arc<ApiRouterState>,
}
#[derive(Clone, Default)]
struct ApiRouterState {
command_endpoint: Option<Arc<dyn CommandEndpoint>>,
queries: HashMap<QueryName, Arc<dyn QueryEndpoint>>,
queries_frozen: bool,
}
impl ApiRouter {
pub fn new() -> Self {
Self::default()
}
pub fn set_command_endpoint<E: CommandEndpoint + 'static>(&mut self, endpoint: E) {
Arc::make_mut(&mut self.state).command_endpoint = Some(Arc::new(endpoint));
}
pub fn register_query<E: QueryEndpoint + 'static>(&mut self, endpoint: E) -> RuntimeResult<()> {
let name = endpoint.query_name().clone();
if self.state.queries_frozen {
return Err(RuntimeError::InvalidRequest {
kind: "query",
reason: "router_frozen",
});
}
if self.state.queries.contains_key(&name) {
return Err(RuntimeError::RegistryItemAlreadyRegistered {
kind: "query",
name: name.as_str().to_string(),
});
}
Arc::make_mut(&mut self.state)
.queries
.insert(name, Arc::new(endpoint));
Ok(())
}
pub fn freeze_queries(&mut self) {
Arc::make_mut(&mut self.state).queries_frozen = true;
}
pub fn queries_are_frozen(&self) -> bool {
self.state.queries_frozen
}
pub fn has_query(&self, name: &QueryName) -> bool {
self.state.queries.contains_key(name)
}
pub fn query_names(&self) -> Vec<QueryName> {
let mut names = self.state.queries.keys().cloned().collect::<Vec<_>>();
names.sort_by(|left, right| left.as_str().cmp(right.as_str()));
names
}
pub fn dispatch_query(
&self,
name: &QueryName,
request: ApiRequest,
) -> RuntimeResult<ApiResponse> {
let Some(endpoint) = self.state.queries.get(name) else {
return Err(RuntimeError::RegistryItemNotFound {
kind: "query",
name: name.as_str().to_string(),
});
};
endpoint.handle_query(request)
}
pub fn dispatch_command(&self, request: ApiRequest) -> RuntimeResult<ApiResponse> {
let Some(endpoint) = &self.state.command_endpoint else {
return Err(RuntimeError::MissingConfiguration {
name: "command_endpoint",
});
};
endpoint.handle_command(request)
}
}
#[cfg(test)]
#[path = "router_tests.rs"]
mod tests;