nbssh 0.7.0

Simple SSH utility module
Documentation
use serde::{Serialize, Deserialize};
use std::error::Error;
use std::fmt;
use std::fmt::Display;

#[derive(Clone, Debug, Deserialize, Eq, Hash, PartialEq, Serialize)]
pub struct Address {
    pub host: String,
    pub port: u16,
}

#[derive(Debug, Deserialize, Eq, PartialEq, Serialize)]
pub enum AddressError {
    InvalidFormat,
    InvalidPort,
}

impl Display for AddressError {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        match self {
            AddressError::InvalidFormat => write!(f, "invalid address format"),
            AddressError::InvalidPort => write!(f, "invalid address port"),
        }
    }
}

impl Error for AddressError {}

impl Address {
    pub fn new(host: &str, port: u16) -> Address {
        Address {
            host: host.to_string(),
            port,
        }
    }

    pub fn parse(address: &str) -> Result<Address, AddressError> {
        let parts: Vec<&str> = address.split(':').collect();
        if parts.len() == 2 {
            if let Ok(port) = parts[1].parse() {
                Ok(Address::new(parts[0], port))
            } else {
                Err(AddressError::InvalidPort)
            }
        } else if parts.len() == 1 {
            Ok(Address::new(address, 22))
        } else {
            Err(AddressError::InvalidFormat)
        }
    }

    pub fn parse_vec(addresses: &[String]) -> Result<Vec<Address>, AddressError> {
        let mut result = Vec::new();
        for elem in addresses {
            match Address::parse(elem) {
                Ok(addr) => result.push(addr),
                Err(err) => return Err(err),
            }
        }
        Ok(result)
    }

    pub fn port_str(&self) -> String {
        self.port.to_string()
    }

    pub fn to_string(&self) -> String {
        if self.port == 22 {
            self.host.clone()
        } else {
            format!("{}:{}", self.host, self.port)
        }
    }
}

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

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

    #[test]
    fn test_parse() {
        assert_eq!(Address::parse("a"), Ok(Address::new("a", 22)));
        assert_eq!(Address::parse("a:1234"), Ok(Address::new("a", 1234)));
        assert_eq!(Address::parse("a:b"), Err(AddressError::InvalidPort));
        assert_eq!(
            Address::parse("a:1234:5678"),
            Err(AddressError::InvalidFormat)
        );
    }

    #[test]
    fn test_parse_vec() {
        assert_eq!(
            Address::parse_vec(&["a".to_string(), "b:9222".to_string()]),
            Ok(vec![Address::new("a", 22), Address::new("b", 9222)])
        );

        assert_eq!(
            Address::parse_vec(&["a".to_string(), "b:abcd".to_string()]),
            Err(AddressError::InvalidPort)
        );
    }

    #[test]
    fn test_to_string() {
        assert_eq!(Address::new("abc", 22).to_string(), "abc");
        assert_eq!(Address::new("abc", 123).to_string(), "abc:123");
    }

    #[test]
    fn test_display() {
        let addr = Address::new("abc", 22);
        assert_eq!(format!("{}", addr), "abc");
        let addr = Address::new("abc", 123);
        assert_eq!(format!("{}", addr), "abc:123");
    }

    #[test]
    fn test_error_trait() {
        let err: Box<Error> = Box::new(AddressError::InvalidPort);
        assert!(err.cause().is_none());
    }
}