1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
use crate::errors::*;
use crate::autonoscope::{Autonoscope, IntoRule, AutoRule, RulePrecision};
use crate::models::*;
use std::convert::TryFrom;

#[derive(Debug, PartialEq)]
pub struct DomainRule {
    value: String,
    fragments: Vec<String>,
}

impl ToString for DomainRule {
    fn to_string(&self) -> String {
        self.value.clone()
    }
}

impl TryFrom<&str> for DomainRule {
    type Error = Error;

    fn try_from(rule: &str) -> Result<DomainRule> {
        let mut fragments = rule.split('.')
            .filter(|x| !x.is_empty())
            .map(String::from)
            .collect::<Vec<_>>();
        fragments.reverse();

        Ok(DomainRule {
            value: rule.to_string(),
            fragments,
        })
    }
}

impl TryFrom<Autonoscope> for DomainRule {
    type Error = Error;

    #[inline]
    fn try_from(rule: Autonoscope) -> Result<DomainRule> {
        DomainRule::try_from(rule.value.as_str())
    }
}

impl AutoRule<NewDomain> for DomainRule {
    #[inline]
    fn matches(&self, domain: &NewDomain) -> Result<bool> {
        self.matches(domain.value.as_str())
    }
}

impl AutoRule<NewSubdomain> for DomainRule {
    #[inline]
    fn matches(&self, domain: &NewSubdomain) -> Result<bool> {
        self.matches(domain.value.as_str())
    }
}

impl AutoRule<NewUrl> for DomainRule {
    #[inline]
    fn matches(&self, url: &NewUrl) -> Result<bool> {
        let url = url.value.parse::<url::Url>()?;
        if let Some(domain) = url.domain() {
            self.matches(domain)
        } else {
            Ok(false)
        }
    }
}

impl AutoRule<str> for DomainRule {
    fn matches(&self, domain: &str) -> Result<bool> {
        let frags = domain.split('.')
            .filter(|x| !x.is_empty())
            .collect::<Vec<_>>();

        if self.fragments.len() > frags.len() {
            return Ok(false);
        }

        for (rule, domain) in self.fragments.iter().zip(frags.iter().rev()) {
            if rule != domain {
                return Ok(false);
            }
        }

        Ok(true)
    }
}

impl RulePrecision for DomainRule {
    #[inline]
    fn precision(&self) -> usize {
        self.fragments.len()
    }
}

impl IntoRule for DomainRule {
    fn into_rule(&self) -> (&'static str, String) {
        ("domain", self.to_string())
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use std::convert::TryFrom;

    #[test]
    fn test_domain_rule_root() {
        let rule = DomainRule::try_from(".").unwrap();
        assert!(rule.matches("example.com").unwrap());
        assert_eq!(rule.precision(), 0);
    }

    #[test]
    fn test_domain_rule_com() {
        let rule = DomainRule::try_from("com").unwrap();
        assert!(rule.matches("example.com").unwrap());
        assert_eq!(rule.precision(), 1);
    }

    #[test]
    fn test_domain_rule_equals() {
        let rule = DomainRule::try_from("example.com").unwrap();
        assert!(rule.matches("example.com").unwrap());
        assert_eq!(rule.precision(), 2);
    }

    #[test]
    fn test_domain_rule_mismatch() {
        let rule = DomainRule::try_from("foo.example.com").unwrap();
        assert!(!rule.matches("example.com").unwrap());
        assert_eq!(rule.precision(), 3);
    }
}