terrazzo-terminal 0.2.7

A simple web-based terminal emulator built on Terrazzo.
use std::fmt::Debug;
use std::marker::PhantomData;
use std::path::Path;
use std::sync::Arc;
use std::time::Duration;

use serde::Deserialize;
use serde::Serialize;
use trz_gateway_common::retry_strategy::RetryStrategy;

pub trait ConfigTypes: Clone {
    type String: Serialize + for<'t> Deserialize<'t> + Debug + Default;
    type Path: Serialize + for<'t> Deserialize<'t> + Debug;
    type MaybePath: Serialize + for<'t> Deserialize<'t> + Debug + Default;
    type MaybeBool: Serialize + for<'t> Deserialize<'t> + Debug + Default;
    type Port: Serialize + for<'t> Deserialize<'t> + Debug + Default;
    type Ports: Serialize + for<'t> Deserialize<'t> + Debug + Default;
    type Duration: Serialize + for<'t> Deserialize<'t> + Debug + Default;
    type RetryStrategy: Serialize + for<'t> Deserialize<'t> + Debug + Default;
    type MaybeRetryStrategy: Serialize + for<'t> Deserialize<'t> + Debug + Default;
}

#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct ConfigFileTypes<T = RuntimeTypes>(PhantomData<T>);

impl<T: ConfigTypes> ConfigTypes for ConfigFileTypes<T> {
    type String = Option<T::String>;
    type Path = Option<T::Path>;
    type MaybePath = T::MaybePath;
    type MaybeBool = Option<bool>;
    type Port = Option<T::Port>;
    type Ports = T::Ports;
    type Duration = Option<String>;
    type RetryStrategy = Option<RetryStrategy>;
    type MaybeRetryStrategy = Option<RetryStrategy>;
}

#[derive(Clone, Debug, Default, Serialize, Deserialize)]
pub struct RuntimeTypes(PhantomData<()>);

impl ConfigTypes for RuntimeTypes {
    type String = String;
    type Path = Arc<Path>;
    type MaybePath = Option<Arc<Path>>;
    type MaybeBool = bool;
    type Port = u16;
    type Ports = Vec<u16>;
    type Duration = Duration;
    type RetryStrategy = RetryStrategy;
    type MaybeRetryStrategy = Option<RetryStrategy>;
}

#[must_use]
#[derive(Clone, Default, Serialize, Deserialize, PartialEq, Eq)]
pub struct Password {
    #[serde(with = "password_serde")]
    pub hash: Vec<u8>,

    pub iterations: u32,

    #[serde(with = "password_serde")]
    pub salt: Vec<u8>,
}

mod password_serde {
    use base64::Engine as _;
    use base64::engine::general_purpose;
    use serde::Deserialize;
    use serde::Deserializer;
    use serde::Serializer;

    pub fn serialize<S>(bytes: &Vec<u8>, serializer: S) -> Result<S::Ok, S::Error>
    where
        S: Serializer,
    {
        let encoded = general_purpose::STANDARD_NO_PAD.encode(bytes);
        serializer.serialize_str(&encoded)
    }

    pub fn deserialize<'de, D>(deserializer: D) -> Result<Vec<u8>, D::Error>
    where
        D: Deserializer<'de>,
    {
        let s = String::deserialize(deserializer)?;
        general_purpose::STANDARD_NO_PAD
            .decode(&s)
            .map_err(serde::de::Error::custom)
    }
}

impl std::fmt::Debug for Password {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        use base64::Engine as _;
        use base64::engine::general_purpose;
        f.debug_struct("Password")
            .field("hash", &general_purpose::STANDARD_NO_PAD.encode(&self.hash))
            .field("iterations", &self.iterations)
            .field("salt", &general_purpose::STANDARD_NO_PAD.encode(&self.salt))
            .finish()
    }
}

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

    #[test]
    fn serialize_deserialize() {
        let password = Password {
            hash: vec![1, 2, 3, 4],
            iterations: 42,
            salt: vec![11, 12, 13, 14],
        };
        let password = toml::ser::to_string(&password).unwrap();
        assert_eq!(
            "hash = \"AQIDBA\"\niterations = 42\nsalt = \"CwwNDg\"\n",
            password
        );

        let password: Password = toml::de::from_str(&password).unwrap();
        assert_eq!(password.hash, vec![1, 2, 3, 4]);
        assert_eq!(password.iterations, 42);
        assert_eq!(password.salt, vec![11, 12, 13, 14]);
    }
}