//! Subscription ID
//!
//! This mod abstract the subscription and makes sure it is valid
use hex::ToHex;
use rand::RngCore;
use std::{
    convert::TryFrom,
    fmt::{self, Display},
    ops::Deref,
};
use thiserror::Error;

/// Error type
#[derive(Error, Debug)]
pub enum Error {
    /// The subscription ID is too long
    #[error("Subscription ID is limited to 64 characters")]
    TooLong,
}

/// Subscription ID
///
/// The rules are simple, any UTF-8 valid string with fewer than 32 characters
///
/// By default a random ID will be created if needed.
#[derive(Debug, Ord, PartialOrd, Clone, Hash, PartialEq, Eq)]
pub struct SubscriptionId(String);

impl SubscriptionId {
    /// Create a new subscription ID
    pub fn empty() -> Self {
        Self("".to_owned())
    }
}

impl Deref for SubscriptionId {
    type Target = str;

    fn deref(&self) -> &Self::Target {
        &self.0
    }
}

impl TryFrom<String> for SubscriptionId {
    type Error = Error;
    fn try_from(s: String) -> Result<Self, Self::Error> {
        if s.as_bytes().len() > 64 {
            return Err(Error::TooLong);
        }
        Ok(SubscriptionId(s))
    }
}

impl TryFrom<&str> for SubscriptionId {
    type Error = Error;
    fn try_from(s: &str) -> Result<Self, Self::Error> {
        s.to_owned().try_into()
    }
}

impl Default for SubscriptionId {
    fn default() -> Self {
        let mut data = [0u8; 32];
        rand::thread_rng().fill_bytes(&mut data);
        Self(data.encode_hex::<String>())
    }
}

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

#[cfg(test)]
mod test {
    use super::*;

    #[test]
    fn default() {
        assert_eq!(64, SubscriptionId::default().to_string().len());
    }

    #[test]
    fn too_long() {
        let f = format!("too_long:{}", SubscriptionId::default());
        let r: Result<SubscriptionId, _> = f.as_str().try_into();
        assert!(r.is_err());
    }
}
