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
use super::Result;
use regex::Regex;

/// DestinationRule
///
/// An abstraction that captures the information needed to make routing decisions.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub struct DestinationRule {
    /// The identifier the incoming request must possess to be considered for forwarding
    pub identifier: String,

    /// The host to forward this request to
    pub host: String,
}

impl DestinationRule {
    const DNS_LABEL_MAX_LENGTH: u8 = 63;
    const DNS_LABEL_PATTERN: &'static str = "^(?:[A-Za-z0-9][-A-Za-z0-9_\\.]*)?[A-Za-z0-9]$";

    fn is_dns_label(value: &str) -> bool {
        value.len() <= (DestinationRule::DNS_LABEL_MAX_LENGTH as usize)
            && Regex::new(DestinationRule::DNS_LABEL_PATTERN)
                .unwrap()
                .is_match(value)
    }

    pub fn verify(&self, identifierPattern: &Regex) -> Result<bool> {
        let mut erroneous = false;

        if !identifierPattern.is_match(&self.identifier) {
            erroneous = true;
            error!("Identifier \"{}\" is invalid.", &self.identifier);
        }

        if !DestinationRule::is_dns_label(&self.host[..]) {
            erroneous = true;
            error!("Host \"{}\" is not a valid DNS label.", &self.host);
        }

        if erroneous {
            bail!("Fatal errors detected.")
        } else {
            Ok(true)
        }
    }
}

#[cfg(test)]
mod tests {
    use super::DestinationRule;
    use regex::Regex;

    const MATCH_ALL_PATTERN: &'static str = ".*";
    const MATCH_NONE_PATTERN: &'static str = ".^";
    const HOST_VALID: &'static str = "hostname.local";
    const HOST_INVALID: &'static str = ">:(";
    const IDENTIFIER: &'static str = "IDENTIFIER";

    fn withIdentifierAndHost(host: &str) -> DestinationRule {
        let identifier = IDENTIFIER.into();
        let host = host.into();
        DestinationRule { identifier, host }
    }

    #[test]
    fn verifies_if_all_fields_valid() {
        assert!(withIdentifierAndHost(HOST_VALID)
            .verify(&Regex::new(MATCH_ALL_PATTERN).unwrap())
            .unwrap());
    }

    #[test]
    fn verification_fails_if_identifier_invalid() {
        withIdentifierAndHost(HOST_VALID)
            .verify(&Regex::new(MATCH_NONE_PATTERN).unwrap())
            .unwrap_err();
    }

    #[test]
    fn verification_fails_if_host_invalid() {
        withIdentifierAndHost(HOST_INVALID)
            .verify(&Regex::new(MATCH_ALL_PATTERN).unwrap())
            .unwrap_err();
    }

    #[test]
    fn verification_fails_if_all_fields_invalid() {
        withIdentifierAndHost(HOST_INVALID)
            .verify(&Regex::new(MATCH_NONE_PATTERN).unwrap())
            .unwrap_err();
    }
}