use super::route::{compile_uri_template, Route, RouteResource};
use crate::cesr::indexing::siger::Siger;
use crate::cesr::prefixer::Prefixer;
use crate::cesr::saider::Saider;
use crate::cesr::seqner::Seqner;
use crate::cesr::Matter;
use crate::keri::core::serdering::Serder;
use crate::keri::core::serdering::SerderKERI;
use crate::keri::KERIError;
use std::collections::HashMap;
use tracing::debug;
pub struct Router {
routes: Vec<Route>,
}
impl Router {
pub const DEFAULT_RESOURCE_FUNC: &'static str = "processReply";
pub fn new(routes: Option<Vec<Route>>) -> Self {
Self {
routes: routes.unwrap_or_default(),
}
}
pub fn add_route(
&mut self,
route_template: &str,
resource: Box<dyn RouteResource>,
suffix: Option<String>,
) -> Result<(), KERIError> {
let (fields, regex) = compile_uri_template(route_template)?;
self.routes
.push(Route::new(regex, fields, resource, suffix));
Ok(())
}
pub fn dispatch(
&self,
serder: &SerderKERI,
saider: &Saider,
cigars: Option<&[Siger]>,
tsgs: Option<&[(Prefixer, Seqner, Saider, Vec<Siger>)]>,
) -> Result<(), KERIError> {
let ked = serder.ked();
let route = ked
.get("r")
.and_then(|v| v.as_str())
.ok_or_else(|| KERIError::ValueError("Missing route 'r' in message".to_string()))?;
let (matched_route, captured_params) = self.find_route(route)?;
let params = captured_params.unwrap_or_default();
matched_route
.resource
.process_reply(serder, saider, route, cigars, tsgs, params)
}
fn find_route(
&self,
route: &str,
) -> Result<(&Route, Option<HashMap<String, String>>), KERIError> {
for registered_route in &self.routes {
if let Some(captures) = registered_route.regex.captures(route) {
let mut captured_params = HashMap::new();
for field_name in ®istered_route.fields {
if let Some(matched_value) = captures.name(field_name) {
captured_params
.insert(field_name.clone(), matched_value.as_str().to_string());
}
}
return Ok((registered_route, Some(captured_params)));
}
}
Err(KERIError::ValidationError(format!(
"No resource is registered to handle route {}",
route
)))
}
pub fn route_count(&self) -> usize {
self.routes.len()
}
pub fn has_route(&self, route: &str) -> bool {
self.find_route(route).is_ok()
}
}
pub struct DefaultRouteResource;
impl RouteResource for DefaultRouteResource {
fn process_reply(
&self,
serder: &SerderKERI,
saider: &Saider,
route: &str,
_cigars: Option<&[Siger]>,
_tsgs: Option<&[(Prefixer, Seqner, Saider, Vec<Siger>)]>,
params: HashMap<String, String>,
) -> Result<(), KERIError> {
debug!(
"DefaultRouteResource processing reply: route={}, said={}, params={:?}",
route,
saider.qb64(),
params
);
debug!("Reply message: {}", serder.pretty(None));
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::cesr::saider::Saider;
use crate::keri::core::serdering::SerderKERI;
use std::collections::HashMap;
struct TestResource {
pub called: std::sync::Arc<std::sync::Mutex<bool>>,
}
impl RouteResource for TestResource {
fn process_reply(
&self,
_serder: &SerderKERI,
_saider: &Saider,
_route: &str,
_cigars: Option<&[Siger]>,
_tsgs: Option<&[(Prefixer, Seqner, Saider, Vec<Siger>)]>,
_params: HashMap<String, String>,
) -> Result<(), KERIError> {
let mut called = self.called.lock().unwrap();
*called = true;
Ok(())
}
}
#[test]
fn test_router_new() {
let router = Router::new(None);
assert_eq!(router.route_count(), 0);
}
#[test]
fn test_router_add_route() {
let mut router = Router::new(None);
let resource = Box::new(DefaultRouteResource);
router.add_route("/test", resource, None).unwrap();
assert_eq!(router.route_count(), 1);
assert!(router.has_route("/test"));
}
#[test]
fn test_router_add_route_with_params() {
let mut router = Router::new(None);
let resource = Box::new(DefaultRouteResource);
router.add_route("/books/{isbn}", resource, None).unwrap();
assert_eq!(router.route_count(), 1);
assert!(router.has_route("/books/123"));
assert!(!router.has_route("/books"));
}
}