redirectionio 3.1.0

Redirection IO Library to handle matching rule, redirect and filtering headers and body.
Documentation
pub mod request_matcher;
mod route;
mod route_datetime;
mod route_header;
mod route_ip;
mod route_time;
mod route_weekday;
mod trace;

use core::cmp::Reverse;
use std::{
    collections::{HashMap, HashSet},
    sync::Arc,
};

#[cfg(feature = "dot")]
use dot_graph::{Edge, Graph, Kind, Node};
pub use request_matcher::{DateTimeMatcher, HostMatcher, IpMatcher, MethodMatcher, PathAndQueryMatcher, SchemeMatcher};
pub use route::{IntoRoute, Route};
pub use route_datetime::RouteDateTime;
pub use route_header::{RouteHeader, RouteHeaderKind};
pub use route_ip::RouteIp;
pub use route_time::RouteTime;
pub use route_weekday::RouteWeekday;
pub use trace::{RouteTrace, Trace};

#[cfg(feature = "dot")]
use crate::dot::DotBuilder;
use crate::{http::Request, router_config::RouterConfig};

#[derive(Debug, Clone)]
pub struct Router<T> {
    matcher: SchemeMatcher<T>,
    pub config: Arc<RouterConfig>,
    pub routes: HashMap<String, Arc<Route<T>>>,
}

impl<T> Default for Router<T> {
    fn default() -> Self {
        let config = Arc::new(RouterConfig::default());

        Router {
            matcher: SchemeMatcher::new(config.clone()),
            config,
            routes: HashMap::new(),
        }
    }
}

impl<T> Router<T> {
    pub fn from_config(config: RouterConfig) -> Self {
        Self::from_arc_config(Arc::new(config))
    }

    pub fn from_arc_config(config: Arc<RouterConfig>) -> Self {
        Self {
            matcher: SchemeMatcher::new(config.clone()),
            config,
            routes: HashMap::new(),
        }
    }

    pub fn insert_route(&mut self, route: Route<T>) {
        let arc_route = Arc::new(route);

        self.matcher.insert(arc_route.clone());
        self.routes.insert(arc_route.id().to_string(), arc_route);
    }

    pub fn get_route_by_id(&self, id: &str) -> Option<Arc<Route<T>>> {
        self.routes.get(id).cloned()
    }

    pub fn remove(&mut self, id: &str) -> Option<Arc<Route<T>>> {
        if self.routes.contains_key(id) {
            self.routes.remove(id);

            self.matcher.remove(id)
        } else {
            None
        }
    }

    pub fn batch_remove(&mut self, ids: &HashSet<String>) {
        self.routes.retain(|id, _| !ids.contains(id));
        self.matcher.batch_remove(ids);
    }

    pub fn rebuild_request(&self, request: &Request) -> Request {
        Request::rebuild_with_config(self.config.as_ref(), request)
    }

    pub fn match_request(&self, request: &Request) -> Vec<Arc<Route<T>>> {
        self.matcher.match_request(request)
    }

    pub fn len(&self) -> usize {
        self.routes.len()
    }

    pub fn is_empty(&self) -> bool {
        self.routes.is_empty()
    }

    pub fn routes(&self) -> &HashMap<String, Arc<Route<T>>> {
        &self.routes
    }

    pub fn trace_request(&self, request: &Request) -> Vec<Trace<T>> {
        let request_rebuild = Request::rebuild_with_config(self.config.as_ref(), request);

        self.matcher.trace(&request_rebuild)
    }

    pub fn get_route(&self, request: &Request) -> Option<Arc<Route<T>>> {
        let mut routes = self.match_request(request);

        if routes.is_empty() {
            return None;
        }

        routes.sort_by_key(|b| Reverse(b.priority()));
        routes.first().cloned()
    }

    pub fn get_trace(&self, request: &Request) -> RouteTrace<T> {
        let traces = self.trace_request(request);
        let mut routes_traces = Trace::get_routes_from_traces(&traces);
        let mut routes = Vec::new();

        for route in &routes_traces {
            routes.push(route.clone());
        }

        routes_traces.sort_by_key(|b| Reverse(b.priority()));

        let final_route = routes_traces.first().cloned();

        RouteTrace::new(traces, routes, final_route)
    }

    pub fn cache(&mut self, limit: Option<u64>) {
        let mut prev_cache_limit = match limit {
            Some(limit) => limit as i64,
            None => (self.routes.len() / 10).clamp(100, 10_000) as i64,
        };

        let mut level = 0;
        let mut retry = 0;

        while prev_cache_limit > 0 {
            let next_cache_limit = self.matcher.cache(prev_cache_limit as u64, level) as i64;

            if next_cache_limit == prev_cache_limit {
                retry += 1;

                if retry > 5 {
                    break;
                }
            }

            level += 1;
            prev_cache_limit = next_cache_limit;
        }

        if prev_cache_limit > 0 {
            for route in self.routes.values() {
                prev_cache_limit -= route.compile() as i64;

                if prev_cache_limit <= 0 {
                    break;
                }
            }
        }
    }

    #[cfg(feature = "dot")]
    pub fn graph(&self) -> Graph {
        let mut graph = Graph::new("router", Kind::Digraph);
        graph.add_node(Node::new("router"));

        let mut id = 0;

        if let Some(key) = self.matcher.graph(&mut id, &mut graph) {
            graph.add_edge(Edge::new("router", &key, ""));
        }

        graph
    }
}

impl<T> Router<T>
where
    T: IntoRoute<T>,
{
    pub fn insert(&mut self, item: T) {
        self.insert_route(item.into_route(self.config.as_ref()));
    }

    pub fn apply_change_set(&mut self, added: Vec<T>, updated: Vec<T>, mut removed: HashSet<String>) {
        let updated_route = updated
            .into_iter()
            .map(|item| item.into_route(self.config.as_ref()))
            .collect::<Vec<Route<T>>>();

        removed.extend(updated_route.iter().map(|item| item.id().to_string()));
        self.batch_remove(&removed);

        for item in updated_route {
            self.insert_route(item);
        }

        for item in added {
            self.insert(item);
        }
    }
}