redirectionio 2.5.2

Redirection IO Library to handle matching rule, redirect and filtering headers and body.
use crate::http::Request;
use crate::regex_radix_tree::{NodeItem, RegexRadixTree};
use crate::router::request_matcher::matcher_tree_storage::{ItemRoute, MatcherTreeStorage};
use crate::router::trace::TraceInfo;
use crate::router::{IpMatcher, RequestMatcher, Route, RouteData, RouterConfig, StaticOrDynamic, Trace};
use std::collections::HashMap;

#[derive(Debug, Clone)]
struct HostRegexNodeItem<T: RouteData> {
    route: Route<T>,
    host_regex: String,
    ignore_case: bool,
}

impl<T: RouteData> NodeItem for HostRegexNodeItem<T> {
    fn regex(&self) -> &str {
        self.host_regex.as_str()
    }

    fn case_insensitive(&self) -> bool {
        self.ignore_case
    }
}

impl<T: RouteData> ItemRoute<T> for HostRegexNodeItem<T> {
    fn route(self) -> Route<T> {
        self.route
    }
}

type HostRegexTreeMatcher<T> = MatcherTreeStorage<T, HostRegexNodeItem<T>, IpMatcher<T>>;

#[derive(Debug, Clone)]
pub struct HostMatcher<T: RouteData> {
    static_hosts: HashMap<String, Box<dyn RequestMatcher<T>>>,
    regex_tree_rule: RegexRadixTree<HostRegexNodeItem<T>, HostRegexTreeMatcher<T>>,
    any_host: Box<dyn RequestMatcher<T>>,
    always_match_any_host: bool,
    count: usize,
}

impl<T: RouteData> RequestMatcher<T> for HostMatcher<T> {
    fn insert(&mut self, route: Route<T>) {
        self.count += 1;

        match route.host() {
            None => self.any_host.insert(route),
            Some(host) => match host {
                StaticOrDynamic::Static(static_host) => {
                    if static_host.is_empty() {
                        self.any_host.insert(route);

                        return;
                    }

                    if !self.static_hosts.contains_key(static_host) {
                        self.static_hosts.insert(static_host.clone(), HostMatcher::create_sub_matcher());
                    }

                    self.static_hosts.get_mut(static_host).unwrap().insert(route);
                }
                StaticOrDynamic::Dynamic(dynamic_host) => {
                    self.regex_tree_rule.insert(HostRegexNodeItem {
                        host_regex: dynamic_host.regex.clone(),
                        ignore_case: dynamic_host.ignore_case,
                        route,
                    });
                }
            },
        }
    }

    fn remove(&mut self, id: &str) -> bool {
        let mut removed = false;

        if self.any_host.remove(id) {
            self.count -= 1;

            return true;
        }

        if self.regex_tree_rule.remove(id) {
            self.count -= 1;

            return true;
        }

        self.static_hosts.retain(|_, matcher| {
            removed = removed || matcher.remove(id);

            matcher.len() > 0
        });

        if removed {
            self.count -= 1;
        }

        removed
    }

    fn match_request(&self, request: &Request) -> Vec<&Route<T>> {
        let mut routes = Vec::new();

        if let Some(host) = request.host() {
            let storages = self.regex_tree_rule.find(host);

            for storage in storages {
                routes.extend(storage.matcher.match_request(request));
            }

            if let Some(matcher) = self.static_hosts.get(host) {
                routes.extend(matcher.match_request(request));
            }
        }

        if self.always_match_any_host || routes.is_empty() {
            routes.extend(self.any_host.match_request(request));
        }

        routes
    }

    fn trace(&self, request: &Request) -> Vec<Trace<T>> {
        let mut traces = Vec::new();
        let request_host = request.host().unwrap_or("");

        for (host, matcher) in &self.static_hosts {
            if host == request_host && request.host().is_some() {
                let host_traces = matcher.trace(request);

                traces.push(Trace::new(
                    true,
                    true,
                    matcher.len() as u64,
                    host_traces,
                    TraceInfo::HostStatic {
                        request: request_host.to_string(),
                        against: Some(host.clone()),
                    },
                ));
            } else {
                traces.push(Trace::new(
                    false,
                    false,
                    matcher.len() as u64,
                    Vec::new(),
                    TraceInfo::HostStatic {
                        request: request_host.to_string(),
                        against: Some(host.clone()),
                    },
                ));
            }
        }

        if let Some(host) = request.host() {
            let node_trace = self.regex_tree_rule.trace(host);
            traces.push(HostRegexTreeMatcher::<T>::node_trace_to_router_trace(
                host,
                node_trace,
                request,
                Some(TraceInfo::HostRegex),
            ));

            if !self.static_hosts.contains_key(host) {
                traces.push(Trace::new(
                    true,
                    false,
                    0,
                    Vec::new(),
                    TraceInfo::HostStatic {
                        request: request_host.to_string(),
                        against: None,
                    },
                ));
            }
        }

        if self.always_match_any_host || Trace::<T>::get_routes_from_traces(&traces).is_empty() {
            traces.extend(self.any_host.trace(request));
        }

        traces
    }

    fn cache(&mut self, limit: u64, level: u64) -> u64 {
        let mut new_limit = self.regex_tree_rule.cache(limit, level);

        for matcher in self.static_hosts.values_mut() {
            new_limit = matcher.cache(new_limit, level);
        }

        self.any_host.cache(new_limit, level)
    }

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

    fn is_empty(&self) -> bool {
        self.count == 0
    }

    fn box_clone(&self) -> Box<dyn RequestMatcher<T>> {
        Box::new((*self).clone())
    }
}

impl<T: RouteData> Default for HostMatcher<T> {
    fn default() -> Self {
        HostMatcher {
            static_hosts: HashMap::new(),
            any_host: HostMatcher::create_sub_matcher(),
            count: 0,
            regex_tree_rule: RegexRadixTree::default(),
            always_match_any_host: false,
        }
    }
}

impl<T: RouteData> HostMatcher<T> {
    pub fn create_sub_matcher() -> Box<dyn RequestMatcher<T>> {
        Box::new(IpMatcher::default())
    }

    pub fn new(config: RouterConfig) -> Self {
        HostMatcher {
            static_hosts: HashMap::new(),
            any_host: HostMatcher::create_sub_matcher(),
            count: 0,
            regex_tree_rule: RegexRadixTree::default(),
            always_match_any_host: config.always_match_any_host,
        }
    }
}