matroskin 0.1.0

whatsminer api client lib
Documentation
//! Declare password module
//!
//! - Item: [Password]
//! - ApiDoc: <https://apidoc.whatsminer.com/#api-Getting_Started-getting_start>
use std::fmt::Display;

use serde::Serialize;
use zeroize::{Zeroize, ZeroizeOnDrop};

use crate::account::Account;
use crate::redacted::Redacted;

#[derive(Debug, Serialize, Clone, PartialEq, Zeroize, ZeroizeOnDrop)]
#[serde(rename_all = "lowercase")]
/// Account passwords. Defaults match the account name. Use `Custom` variant for changed passwords.
///
/// - ApiDoc: <https://apidoc.whatsminer.com/#api-Getting_Started-getting_start>
pub enum Password {
    Super,
    User1,
    User2,
    User3,
    // TODO: it can be useless
    Custom(Redacted<String>),
}

impl AsRef<str> for Password {
    fn as_ref(&self) -> &str {
        match self {
            Password::Super => "super",
            Password::User1 => "user1",
            Password::User2 => "user2",
            Password::User3 => "user3",
            Password::Custom(e) => e,
        }
    }
}

impl Display for Password {
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
        match self {
            Password::Super => write!(f, "super"),
            Password::User1 => write!(f, "user1"),
            Password::User2 => write!(f, "user2"),
            Password::User3 => write!(f, "user3"),
            Password::Custom(e) => write!(f, "{e}"),
        }
    }
}

impl From<Account> for Password {
    fn from(value: Account) -> Self {
        match value {
            Account::Super => Self::Super,
            Account::User1 => Self::User1,
            Account::User2 => Self::User2,
            Account::User3 => Self::User3,
        }
    }
}

impl From<&str> for Password {
    fn from(s: &str) -> Self {
        match s {
            "super" => Self::Super,
            "user1" => Self::User1,
            "user2" => Self::User2,
            "user3" => Self::User3,
            other => Self::Custom(Redacted(other.to_string())),
        }
    }
}

impl From<String> for Password {
    fn from(mut s: String) -> Self {
        match s.as_str() {
            "super" => {
                s.zeroize();
                Self::Super
            }
            "user1" => {
                s.zeroize();
                Self::User1
            }
            "user2" => {
                s.zeroize();
                Self::User2
            }
            "user3" => {
                s.zeroize();
                Self::User3
            }
            _ => Self::Custom(Redacted(s)),
        }
    }
}

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

    #[test]
    fn from_str_builtins() {
        assert_eq!(Password::from("super"), Password::Super);
        assert_eq!(Password::from("user1"), Password::User1);
        assert_eq!(Password::from("user2"), Password::User2);
        assert_eq!(Password::from("user3"), Password::User3);
    }

    #[test]
    fn from_str_custom() {
        let p = Password::from("custom_secret");
        assert_eq!(p, Password::Custom(Redacted("custom_secret".to_string())));
        assert_eq!(p.as_ref(), "custom_secret");
        assert_eq!(format!("{p:?}"), "Custom([REDACTED])");
        assert_eq!(format!("{p}"), "[REDACTED]");
    }

    #[test]
    fn from_string_builtins() {
        assert_eq!(Password::from(String::from("super")), Password::Super);
        assert_eq!(Password::from(String::from("user1")), Password::User1);
        assert_eq!(Password::from(String::from("user2")), Password::User2);
        assert_eq!(Password::from(String::from("user3")), Password::User3);
    }

    #[test]
    fn from_string_custom() {
        let p = Password::from(String::from("heap_secret"));
        assert_eq!(p, Password::Custom(Redacted("heap_secret".to_string())));
        assert_eq!(p.as_ref(), "heap_secret");
        assert_eq!(format!("{p:?}"), "Custom([REDACTED])");
        assert_eq!(format!("{p}"), "[REDACTED]");
    }

    #[test]
    fn display_and_debug_redaction() {
        assert_eq!(format!("{}", Password::Super), "super");
        assert_eq!(format!("{:?}", Password::Super), "Super");

        let custom = Password::Custom(Redacted("hidden_pass".to_string()));
        assert_eq!(format!("{custom}"), "[REDACTED]");
        assert_eq!(format!("{custom:?}"), "Custom([REDACTED])");
        assert_eq!(custom.as_ref(), "hidden_pass");
    }

    #[test]
    fn zeroize_custom_password() {
        let mut p = Password::from("temporary_secret");
        p.zeroize();
        if let Password::Custom(r) = &p {
            assert_eq!(&**r, "");
        } else {
            panic!("expected Custom variant");
        }
    }
}