use std::error;
use std::fmt;
use std::result::Result;
const HEX_STRING_LENGTH: usize = 7;
#[derive(Clone, Debug, Eq, PartialEq, PartialOrd, Ord)]
pub struct Color {
pub red: u8,
pub green: u8,
pub blue: u8,
}
impl Color {
pub fn from_hex_string(hex: &str) -> Result<Color, ColorError> {
let chars: Vec<char> = hex.chars().collect();
let count: usize = chars.len();
if count < HEX_STRING_LENGTH {
Err(ColorError::HexStringTooShort)
} else if count > HEX_STRING_LENGTH {
Err(ColorError::HexStringTooLong)
} else if !hex.starts_with("#") {
Err(ColorError::HexStringNoHashSign)
} else {
let red = from_hex_string_red(hex)?;
let green = from_hex_string_green(hex)?;
let blue = from_hex_string_blue(hex)?;
Ok(Color {
red: red,
green: green,
blue: blue,
})
}
}
pub fn to_hex_string(&self) -> String {
format!("#{0:02x}{1:02x}{2:02x}", self.red, self.green, self.blue)
}
}
#[derive(Debug, PartialEq)]
pub enum ColorError {
HexStringNoHashSign,
HexStringTooLong,
HexStringTooShort,
InvalidBlueValue,
InvalidGreenValue,
InvalidRedValue,
}
impl ColorError {
fn msg(&self) -> &str {
match *self {
ColorError::HexStringNoHashSign => "hex string without hash sign",
ColorError::HexStringTooLong => "hex string too long",
ColorError::HexStringTooShort => "hex string too short",
ColorError::InvalidBlueValue => "invalid blue value",
ColorError::InvalidGreenValue => "invalid green value",
ColorError::InvalidRedValue => "invalid red value",
}
}
}
impl fmt::Display for ColorError {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
ColorError::HexStringNoHashSign => write!(f, "Color error: {}", self.msg()),
ColorError::HexStringTooLong => write!(f, "Color error: {}", self.msg()),
ColorError::HexStringTooShort => write!(f, "Color error: {}", self.msg()),
ColorError::InvalidBlueValue => write!(f, "Color error: {}", self.msg()),
ColorError::InvalidGreenValue => write!(f, "Color error: {}", self.msg()),
ColorError::InvalidRedValue => write!(f, "Color error: {}", self.msg()),
}
}
}
impl error::Error for ColorError {
fn description(&self) -> &str {
self.msg()
}
fn cause(&self) -> Option<&dyn error::Error> {
None
}
}
fn from_hex_string_blue(hex_str: &str) -> Result<u8, ColorError> {
match u8::from_str_radix(&hex_str[5..7], 16) {
Ok(val) => Ok(val),
Err(_) => Err(ColorError::InvalidBlueValue),
}
}
fn from_hex_string_green(hex_str: &str) -> Result<u8, ColorError> {
match u8::from_str_radix(&hex_str[3..5], 16) {
Ok(val) => Ok(val),
Err(_) => Err(ColorError::InvalidGreenValue),
}
}
fn from_hex_string_red(hex_str: &str) -> Result<u8, ColorError> {
match u8::from_str_radix(&hex_str[1..3], 16) {
Ok(val) => Ok(val),
Err(_) => Err(ColorError::InvalidRedValue),
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_from_hex_string_without_hash_sign_returns_error() {
let expected = Err(ColorError::HexStringNoHashSign);
let actual = Color::from_hex_string("1234567");
assert_eq!(actual, expected);
}
#[test]
fn test_from_hex_string_with_too_short_hex_string_returns_error() {
let expected = Err(ColorError::HexStringTooShort);
let actual = Color::from_hex_string("#12345");
assert_eq!(actual, expected);
}
#[test]
fn test_from_hex_string_with_too_long_hex_string_returns_error() {
let expected = Err(ColorError::HexStringTooLong);
let actual = Color::from_hex_string("#1234567");
assert_eq!(actual, expected);
}
#[test]
fn test_from_hex_string_with_invalid_blue_value_returns_error() {
let expected = Err(ColorError::InvalidBlueValue);
let actual = Color::from_hex_string("#0000fg");
assert_eq!(actual, expected);
}
#[test]
fn test_from_hex_string_with_invalid_green_value_returns_error() {
let expected = Err(ColorError::InvalidGreenValue);
let actual = Color::from_hex_string("#00fg00");
assert_eq!(actual, expected);
}
#[test]
fn test_from_hex_string_with_invalid_red_value_returns_error() {
let expected = Err(ColorError::InvalidRedValue);
let actual = Color::from_hex_string("#fg0000");
assert_eq!(actual, expected);
}
#[test]
fn test_from_hex_string_with_valid_hex_string_returns_color() {
for tuple in get_test_tuples() {
let color = Color {
red: tuple.1,
green: tuple.2,
blue: tuple.3,
};
let expected = Ok(color);
let actual = Color::from_hex_string(tuple.0);
assert_eq!(actual, expected);
}
}
#[test]
fn test_to_hex_string_with_valid_color_returns_hex_string() {
for tuple in get_test_tuples() {
let color = Color {
red: tuple.1,
green: tuple.2,
blue: tuple.3,
};
let expected = tuple.0;
let actual = color.to_hex_string();
assert_eq!(actual, expected);
}
}
quickcheck! {
fn test_from_hex_string_inverses_to_hex_string(red: u8, green: u8, blue: u8) -> bool {
let color = Color { red: red, green: green, blue: blue };
Color::from_hex_string(&color.to_hex_string()) == Ok(color)
}
}
fn get_test_tuples() -> Vec<(&'static str, u8, u8, u8)> {
vec![
("#000000", 0, 0, 0),
("#ff0000", 255, 0, 0),
("#00ff00", 0, 255, 0),
("#0000ff", 0, 0, 255),
("#ffffff", 255, 255, 255),
]
}
}