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
use crate::{Code, HcaptchaError};
use std::collections::HashSet;
use std::fmt;
use std::str::FromStr;
use uuid::Uuid;

#[derive(Debug, Default, Clone, serde::Deserialize, serde::Serialize)]
pub struct HcaptchaSitekey(String);

impl fmt::Display for HcaptchaSitekey {
    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
        write!(f, "{}", self.0)
    }
}

impl HcaptchaSitekey {
    #[cfg_attr(
        feature = "trace",
        tracing::instrument(name = "Validate Site Key.", skip(s), level = "debug")
    )]
    pub fn parse(s: String) -> Result<Self, HcaptchaError> {
        empty_sitekey(&s)?;
        invalid_sitekey(&s)?;

        Ok(HcaptchaSitekey(s))
    }
}

#[cfg_attr(
    feature = "trace",
    tracing::instrument(name = "Return error on empty string.", skip(s), level = "debug")
)]
fn empty_sitekey(s: &str) -> Result<(), HcaptchaError> {
    if s.trim().is_empty() {
        let mut codes = HashSet::new();
        codes.insert(Code::MissingSiteKey);

        #[cfg(feature = "trace")]
        tracing::debug!("{}", Code::MissingSiteKey);
        Err(HcaptchaError::Codes(codes))
    } else {
        Ok(())
    }
}

#[cfg_attr(
    feature = "trace",
    tracing::instrument(name = "Return error if not an ip string.", skip(s), level = "debug")
)]
fn invalid_sitekey(s: &str) -> Result<(), HcaptchaError> {
    if Uuid::from_str(s).is_err() {
        let mut codes = HashSet::new();
        codes.insert(Code::InvalidSiteKey);

        #[cfg(feature = "trace")]
        tracing::debug!("{}", Code::InvalidSiteKey);
        Err(HcaptchaError::Codes(codes))
    } else {
        Ok(())
    }
}

#[cfg(test)]
mod tests {
    use super::HcaptchaSitekey;
    use crate::Code;
    use crate::HcaptchaError;
    use claim::{assert_err, assert_ok};

    // const CYAN: &str = "\u{001b}[36m";
    // const RESET: &str = "\u{001b}[0m";

    #[test]
    fn whitespace_only_sitekeys_are_rejected() {
        let sitekey = " ".to_string();
        assert_err!(HcaptchaSitekey::parse(sitekey));
    }

    #[test]
    fn empty_string_is_rejected() {
        let sitekey = "".to_string();
        assert_err!(HcaptchaSitekey::parse(sitekey));
    }

    #[test]
    fn error_set_contains_missing_sitekey_error() {
        let sitekey = "".to_string();
        if let Err(HcaptchaError::Codes(hs)) = HcaptchaSitekey::parse(sitekey) {
            assert!(hs.contains(&Code::MissingSiteKey));
        }
    }

    #[test]
    fn error_set_contains_invalid_sitekey_error() {
        let sitekey = "1922.20".to_string();
        let res = HcaptchaSitekey::parse(sitekey);
        assert_err!(&res);

        if let Err(HcaptchaError::Codes(hs)) = res {
            assert!(hs.contains(&Code::InvalidSiteKey));
        }
    }

    #[test]
    fn valid_sitekey_key_is_valid() {
        let sitekey = fakeit::unique::uuid_v4();

        assert_ok!(HcaptchaSitekey::parse(sitekey));
    }
}