asknothingx2-util 0.5.0

asknothing
Documentation
use std::{
    fmt::{Debug, Display, Formatter, Result as FmtResult},
    hash::{Hash, Hasher},
    ops::Deref,
    str::FromStr,
};

use serde::{Deserialize, Serialize};
use sha2::{Digest, Sha256};
use subtle::ConstantTimeEq;
use url::Url;
use zeroize::{Zeroize, ZeroizeOnDrop};

macro_rules! regular_type {
    (
    $(#[$attr:meta])*
    $name:ident
    ) => (
        #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
        #[serde(transparent)]
        $(#[$attr])*
        pub struct  $name(String);

        impl $name {
            pub fn as_str(&self) -> &str {
                &self.0
            }
        }

        impl Display for $name {
            fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
                f.write_str(self.as_str())
            }
        }

        impl From<String> for $name {
            fn from(value: String) -> Self {
                Self(value.into())
            }
        }

        impl From<&str> for $name {
            fn from(value: &str) -> Self {
                Self(value.into())
            }
        }

        impl AsRef<str> for $name {
            fn as_ref(&self) -> &str {
                self.as_str()
            }
        }

        impl Deref for $name {
            type Target = str;

            fn deref(&self) -> &str {
                self.as_str()
            }
        }


    )
}

macro_rules! secret_type {
    (
    $(#[$attr:meta])*
    $name:ident
    ) => (
        #[derive(Clone, Serialize, Deserialize)]
        #[derive(Zeroize, ZeroizeOnDrop)]
        #[serde(transparent)]
        $(#[$attr])*
        pub struct  $name(String);

        impl $name {
            pub fn secret(&self) -> &str {
                &self.0
            }

            pub fn into_secret(mut self) -> String {
                std::mem::take(&mut self.0)
            }

            pub fn clear(&mut self) {
                self.zeroize();
            }
        }

        impl Display for $name {
            fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
                f.write_str("[REDACTED]")

            }
        }

        impl Debug for $name {
            fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
                f.debug_tuple(stringify!($name))
                    .field(&"[REDACTED]")
                    .finish()
            }
        }

        impl From<String> for $name {
            fn from(value: String) -> Self {
                Self(value)
            }
        }

        impl From<&str> for $name {
            fn from(value: &str) -> Self {
                Self(value.into())
            }
        }

        impl AsRef<str> for $name {
            fn as_ref(&self) -> &str {
                &self.0
            }
        }

        impl Deref for $name {
            type Target = str;

            fn deref(&self) -> &str {
                &self.0
            }
        }

        impl PartialEq for $name {
            fn eq(&self, other: &Self) -> bool {
                self.0.as_bytes().ct_eq(other.0.as_bytes()).into()
            }
        }

        impl Eq for $name {}

        impl Hash for $name {
            fn hash<H: Hasher>(&self, state: &mut H) {
                Sha256::digest(self.secret()).hash(state);
            }
        }

    )
}

macro_rules! url_type {
    (
        $(#[$attr:meta])*
        $name:ident
    ) => (
        #[derive(Debug, Clone, PartialEq, Eq, Hash, Serialize, Deserialize)]
        #[serde(transparent)]
        $(#[$attr])*
        pub struct  $name(String);

        impl $name {
            pub fn as_str(&self) -> &str {
                &self.0
            }

            pub fn to_url(&self) -> Url {
                Url::parse(&self.0).unwrap()
            }
        }

        impl Display for $name {
            fn fmt(&self, f: &mut Formatter<'_>) -> FmtResult {
                f.write_str(self.as_str())
            }
        }

        impl FromStr for $name {
            type Err = url::ParseError;
            fn from_str(s: &str) -> Result<Self, Self::Err> {
                url::Url::parse(s)?;
                Ok(Self(s.to_string()))
            }
        }

        impl AsRef<str> for $name {
            fn as_ref(&self) -> &str {
                self.as_str()
            }
        }

        impl Deref for $name {
            type Target = str;

            fn deref(&self) -> &str {
                self.as_str()
            }
        }
    )
}

regular_type!(ClientId);
regular_type!(Scope);
regular_type!(CsrfToken);

secret_type!(ClientSecret);
secret_type!(AuthorizationCode);
secret_type!(RefreshToken);
secret_type!(AccessToken);
secret_type!(DeviceCode);

url_type!(AuthUrl);
url_type!(TokenUrl);
url_type!(RedirectUrl);
url_type!(RevocationUrl);
url_type!(ValidateUrl);
url_type!(DeviceUrl);

#[cfg(test)]
mod tests {
    use std::str::FromStr;

    use crate::oauth::{AccessToken, AuthUrl, ClientId, ClientSecret, RedirectUrl};

    #[test]
    fn regular_type() {
        let client_id = ClientId::from("client_id");

        let json = serde_json::to_string(&client_id).unwrap();
        assert_eq!(json, "\"client_id\"");

        let deserialized: ClientId = serde_json::from_str(&json).unwrap();
        assert_eq!(client_id, deserialized);

        let client_id2 = ClientId::from("client_id");
        let client_id3 = ClientId::from("client_id3");
        assert_eq!(client_id, client_id2);
        assert_ne!(client_id, client_id3);

        let as_str = client_id.as_str();
        assert_eq!(as_str, "client_id");

        let display = client_id.to_string();
        assert_eq!(display, "client_id");

        assert_eq!(&*client_id, "client_id");
        assert_eq!(client_id.len(), 9);
    }

    #[test]
    fn secret_type() {
        let client_secret = ClientSecret::from("client_secret");

        let json = serde_json::to_string(&client_secret).unwrap();
        assert_eq!(json, "\"client_secret\"");

        let deserialized: ClientSecret = serde_json::from_str(&json).unwrap();
        assert_eq!(client_secret, deserialized);

        let client_secret2 = ClientSecret::from("client_secret");
        let client_secret3 = ClientSecret::from("client_secret3");
        assert_eq!(client_secret, client_secret2);
        assert_ne!(client_secret, client_secret3);

        let secret = client_secret.secret();
        assert_eq!(secret, "client_secret");

        let display = client_secret.to_string();
        assert_eq!(display, "[REDACTED]");

        assert_eq!(&*client_secret, "client_secret");
        assert_eq!(client_secret.len(), 13);

        let mut token = AccessToken::from("access_token");

        token.clear();

        assert_eq!(token.secret(), "");
    }

    #[test]
    fn url_type() {
        let auth_url = AuthUrl::from_str("https://id.twitch.tv/oauth2/authorize").unwrap();

        let json = serde_json::to_string(&auth_url).unwrap();
        assert_eq!(json, "\"https://id.twitch.tv/oauth2/authorize\"");

        let deserialized: AuthUrl = serde_json::from_str(&json).unwrap();
        assert_eq!(auth_url, deserialized);

        let auth_url2 = AuthUrl::from_str("https://id.twitch.tv/oauth2/authorize").unwrap();
        assert_eq!(auth_url, auth_url2);

        let as_str = auth_url.as_str();
        assert_eq!(as_str, "https://id.twitch.tv/oauth2/authorize");

        let display = auth_url.to_string();
        assert_eq!(display, "https://id.twitch.tv/oauth2/authorize");

        assert_eq!(&*auth_url, "https://id.twitch.tv/oauth2/authorize");

        let valid = RedirectUrl::from_str("not_a_ual");
        assert!(valid.is_err());
    }
}