passlane 2.3.2

A password manager for the command line
use std::fmt::{Display, Formatter};
use uuid::Uuid;
use std::num::ParseIntError;
use std::fmt;
use std::str::FromStr;

#[derive(Clone)]
pub struct Date(pub String);

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

#[derive(Clone)]
pub struct Credential {
    pub uuid: Uuid,
    pub password: String,
    pub service: String,
    pub username: String,
    pub notes: Option<String>,
}

pub struct PaymentCard {
    pub id: Uuid,
    pub name: String,
    pub name_on_card: String,
    pub number: String,
    pub cvv: String,
    pub expiry: Expiry,
    pub color: Option<String>,
    pub billing_address: Option<Address>,
}

impl PaymentCard {
    pub fn last_four(&self) -> String {
        self.number
            .chars()
            .skip(self.number.len() - 4)
            .take(4)
            .collect::<String>()
    }
    pub fn color_str(&self) -> String {
        if let Some(color) = &self.color {
            color.clone()
        } else {
            "".to_string()
        }
    }
    pub fn expiry_str(&self) -> String {
        self.expiry.to_string()
    }
}

pub struct Expiry {
    pub month: i32,
    pub year: i32,
}

impl Display for Expiry {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(f, "{}/{}", self.month, self.year)
    }
}

#[derive(Debug)]
pub enum ExpiryParseError {
    InvalidFormat,
    ParseError(ParseIntError),
}

impl fmt::Display for ExpiryParseError {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match self {
            ExpiryParseError::InvalidFormat => write!(f, "Invalid format. Expected MM/YYYY"),
            ExpiryParseError::ParseError(e) => e.fmt(f),
        }
    }
}

impl std::error::Error for ExpiryParseError {}

impl From<ParseIntError> for ExpiryParseError {
    fn from(err: ParseIntError) -> ExpiryParseError {
        ExpiryParseError::ParseError(err)
    }
}

impl FromStr for Expiry {
    type Err = ExpiryParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let parts: Vec<&str> = s.split('/').collect();
        if parts.len() != 2 {
            return Err(ExpiryParseError::InvalidFormat);
        }
        let month = parts[0].parse::<i32>().map_err(ExpiryParseError::ParseError)?;
        let year = parts[1].parse::<i32>().map_err(ExpiryParseError::ParseError)?;
        Ok(Expiry { month, year })
    }
}

pub struct Address {
    pub id: Uuid,
    pub street: String,
    pub city: String,
    pub country: String,
    pub state: Option<String>,
    pub zip: String,
}

pub struct Note {
    pub id: Uuid,
    pub title: String,
    pub content: String,
}


impl Display for Address {
    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
        write!(
            f,
            "{}, {}, {}, {}",
            self.street, self.zip, self.city, self.country
        )
    }
}

#[derive(Debug)]
pub enum AddressParseError {
    InvalidFormat,
    ParseError(ParseIntError),
}

impl fmt::Display for AddressParseError {
    fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
        match self {
            AddressParseError::InvalidFormat => write!(f, "Invalid format. Expected Street, Zip, City, Country"),
            AddressParseError::ParseError(e) => e.fmt(f),
        }
    }
}

impl std::error::Error for AddressParseError {}

impl From<ParseIntError> for AddressParseError {
    fn from(err: ParseIntError) -> AddressParseError {
        AddressParseError::ParseError(err)
    }
}

impl FromStr for Address {
    type Err = AddressParseError;

    fn from_str(s: &str) -> Result<Self, Self::Err> {
        let parts: Vec<&str> = s.split(',').collect();
        if parts.len() != 4 {
            return Err(AddressParseError::InvalidFormat);
        }
        let street = parts[0].trim().to_string();
        let zip = parts[1].trim().to_string();
        let city = parts[2].trim().to_string();
        let country = parts[3].trim().to_string();
        Ok(Address { id: Uuid::new_v4(), street, city, country, state: None, zip })
    }
}