use std::fmt;
use std::str::FromStr;
use css_color_parser::{Color as CssColor, ColorParseError as CssColorParseError};
use serde::de::{self, Deserialize, Visitor};
use super::super::Color;
const FIELDS: &'static [&'static str] = &["r", "g", "b"];
const EXPECTING_MSG: &'static str = "CSS color string or array/map of RGB values";
impl<'de> Deserialize<'de> for Color {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where D: de::Deserializer<'de>
{
deserializer.deserialize_any(ColorVisitor)
}
}
struct ColorVisitor;
impl<'de> Visitor<'de> for ColorVisitor {
type Value = Color;
fn expecting(&self, fmt: &mut fmt::Formatter) -> fmt::Result {
write!(fmt, "{}", EXPECTING_MSG)
}
fn visit_str<E: de::Error>(self, v: &str) -> Result<Self::Value, E> {
let color = Color::from_str(v).map_err(|e| {
warn!("Failed to parse color `{}`: {}", v, e);
E::custom(e)
})?;
Ok(color)
}
fn visit_seq<A>(self, mut seq: A) -> Result<Self::Value, A::Error>
where A: de::SeqAccess<'de>
{
if let Some(size) = seq.size_hint() {
if size != FIELDS.len() {
return Err(de::Error::invalid_length(
size, &(&format!("{}", FIELDS.len()) as &str)));
}
}
let mut channels = Vec::with_capacity(FIELDS.len());
while let Some(elem) = seq.next_element::<u8>()? {
channels.push(elem);
if channels.len() > FIELDS.len() {
return Err(de::Error::invalid_length(
channels.len(), &(&format!("{}", FIELDS.len()) as &str)));
}
}
let mut result = channels.into_iter();
Ok(Color(result.next().unwrap(),
result.next().unwrap(),
result.next().unwrap()))
}
fn visit_map<A>(self, mut map: A) -> Result<Self::Value, A::Error>
where A: de::MapAccess<'de>
{
if let Some(size) = map.size_hint() {
if size != FIELDS.len() {
return Err(de::Error::invalid_length(
size, &(&format!("{}", FIELDS.len()) as &str)));
}
}
let (mut r, mut g, mut b) = (None, None, None);
while let Some(key) = map.next_key::<String>()? {
let key = key.trim().to_lowercase();
match key.as_str() {
"r" | "red" => {
if r.is_some() {
return Err(de::Error::duplicate_field("r"));
}
r = Some(map.next_value()?);
}
"g" | "green" => {
if g.is_some() {
return Err(de::Error::duplicate_field("g"));
}
g = Some(map.next_value()?);
}
"b" | "blue" => {
if b.is_some() {
return Err(de::Error::duplicate_field("b"));
}
b = Some(map.next_value()?);
}
key => return Err(de::Error::unknown_field(key, FIELDS)),
}
}
let r = r.ok_or_else(|| de::Error::missing_field("r"))?;
let g = g.ok_or_else(|| de::Error::missing_field("g"))?;
let b = b.ok_or_else(|| de::Error::missing_field("b"))?;
Ok(Color(r, g, b))
}
}
impl FromStr for Color {
type Err = ColorParseError;
fn from_str(v: &str) -> Result<Self, Self::Err> {
let mut s = v.trim().to_lowercase();
let mut had_hex_prefix = false;
for &prefix in ["#", "0x", "$"].into_iter() {
if s.starts_with(prefix) {
s = s.trim_left_matches(prefix).to_owned();
if prefix != "#" && s.len() != 6 {
return Err(ColorParseError::Css(CssColorParseError));
}
had_hex_prefix = true;
break;
}
}
if had_hex_prefix {
s = format!("#{}", s);
}
let css_color: CssColor = s.parse()?;
if css_color.a != 1.0 {
return Err(ColorParseError::Alpha(css_color.a));
}
Ok(Color(css_color.r, css_color.g, css_color.b))
}
}
#[derive(Debug, Error)]
pub enum ColorParseError {
#[error(msg = "invalid CSS color syntax")]
Css(CssColorParseError),
#[error(no_from, non_std, msg =" color transparency not supported")]
Alpha(f32),
}
impl PartialEq<ColorParseError> for ColorParseError {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(&ColorParseError::Css(_), &ColorParseError::Css(_)) => true,
(&ColorParseError::Alpha(a1), &ColorParseError::Alpha(a2)) => a1 == a2,
_ => false,
}
}
}
#[cfg(test)]
mod tests {
mod generic {
use itertools::Itertools;
use serde_test::{assert_de_tokens, assert_de_tokens_error, Token as T};
use super::super::{Color, EXPECTING_MSG, FIELDS};
lazy_static! {
static ref EXPECTING_FIELD_MSG: String = format!("one of {}",
FIELDS.iter().format_with(", ", |x, f| f(&format_args!("`{}`", x))));
}
#[test]
fn must_be_valid_type() {
assert_de_tokens_error::<Color>(
&[T::Unit],
&format!("invalid type: unit value, expected {}", EXPECTING_MSG));
assert_de_tokens_error::<Color>(
&[T::Bool(false)],
&format!("invalid type: boolean `false`, expected {}", EXPECTING_MSG));
}
#[test]
fn can_be_css_color_name() {
assert_de_tokens(&Color(255, 0, 0), &[T::Str("red")]);
assert_de_tokens(&Color(255, 99, 71), &[T::Str("tomato")]);
assert_de_tokens_error::<Color>(&[T::Str("uwotm8")], "invalid CSS color syntax");
}
#[test]
fn can_be_rgb_sequence() {
assert_de_tokens(&Color(1, 2, 3), &[
T::Seq{len: Some(3)}, T::U8(1), T::U8(2), T::U8(3), T::SeqEnd]);
assert_de_tokens(&Color(1, 2, 3), &[
T::Seq{len: None}, T::U8(1), T::U8(2), T::U8(3), T::SeqEnd]);
assert_de_tokens(&Color(1, 2, 3), &[
T::Tuple{len: 3}, T::U8(1), T::U8(2), T::U8(3), T::TupleEnd]);
assert_de_tokens_error::<Color>(&[T::Seq{len: Some(7)}], "invalid length 7, expected 3");
assert_de_tokens_error::<Color>(&[
T::Seq{len: None}, T::U8(1), T::U8(2), T::U8(3), T::U8(4),
], "invalid length 4, expected 3");
}
#[test]
#[should_panic(expected = "remaining tokens")]
fn cannot_be_too_long_rgb_sequence() {
assert_de_tokens_error::<Color>(&[
T::Seq{len: None}, T::U8(1), T::U8(2), T::U8(3), T::U8(4), T::U8(5), T::U8(6),
], "invalid length 4, expected 3");
}
#[test]
fn can_be_valid_map() {
assert_de_tokens(&Color(1, 2, 3), &[
T::Map{len: None},
T::Str("r"), T::U8(1), T::Str("g"), T::U8(2), T::Str("b"), T::U8(3),
T::MapEnd,
]);
assert_de_tokens(&Color(1, 2, 3), &[
T::Map{len: None},
T::Str("red"), T::U8(1), T::Str("green"), T::U8(2), T::Str("blue"), T::U8(3),
T::MapEnd,
]);
assert_de_tokens(&Color(1, 2, 3), &[
T::Map{len: None},
T::Str("r"), T::U8(1), T::Str("green"), T::U8(2), T::Str("b"), T::U8(3),
T::MapEnd,
]);
}
#[test]
fn cannot_be_invalid_map() {
assert_de_tokens_error::<Color>(
&[T::Map{len: Some(0)}], "invalid length 0, expected 3");
assert_de_tokens_error::<Color>(
&[T::Map{len: None}, T::MapEnd], "missing field `r`");
assert_de_tokens_error::<Color>(
&[T::Map{len: None}, T::Str("weird"), T::Str("wat")],
&format!("unknown field `weird`, expected {}", *EXPECTING_FIELD_MSG));
assert_de_tokens_error::<Color>(&[
T::Map{len: None},
T::Str("r"), T::U8(255),
T::Str("b"), T::U8(0),
T::MapEnd,
], "missing field `g`");
assert_de_tokens_error::<Color>(
&[T::Map{len: Some(5)}], "invalid length 5, expected 3");
}
}
mod from_str {
use std::str::FromStr;
use spectral::prelude::*;
use super::super::{Color, ColorParseError};
#[test]
fn pure_named_colors() {
assert_that!(Color::from_str("black")).is_ok().is_equal_to(Color(0, 0, 0));
assert_that!(Color::from_str("white")).is_ok().is_equal_to(Color(0xff, 0xff, 0xff));
assert_that!(Color::from_str("red")).is_ok().is_equal_to(Color(0xff, 0, 0));
assert_that!(Color::from_str("lime")).is_ok().is_equal_to(Color(0, 0xff, 0)); assert_that!(Color::from_str("blue")).is_ok().is_equal_to(Color(0, 0, 0xff));
}
#[test]
fn common_named_colors() {
assert_that!(Color::from_str("gray")).is_ok().is_equal_to(Color(0x80, 0x80, 0x80));
assert_that!(Color::from_str("silver")).is_ok().is_equal_to(Color(192, 192, 192));
assert_that!(Color::from_str("teal")).is_ok().is_equal_to(Color(0, 0x80, 0x80));
assert_that!(Color::from_str("brown")).is_ok().is_equal_to(Color(165, 42, 42));
assert_that!(Color::from_str("maroon")).is_ok().is_equal_to(Color(0x80, 0, 0));
assert_that!(Color::from_str("navy")).is_ok().is_equal_to(Color(0, 0, 0x80));
assert_that!(Color::from_str("green")).is_ok().is_equal_to(Color(0, 0x80, 0));
assert_that!(Color::from_str("magenta")).is_ok().is_equal_to(Color(0xff, 0, 0xff));
assert_that!(Color::from_str("cyan")).is_ok().is_equal_to(Color(0, 0xff, 0xff));
assert_that!(Color::from_str("yellow")).is_ok().is_equal_to(Color(0xff, 0xff, 0));
}
#[test]
fn exotic_named_colors() {
assert_that!(Color::from_str("aquamarine")).is_ok().is_equal_to(Color(127, 255, 212));
assert_that!(Color::from_str("bisque")).is_ok().is_equal_to(Color(255, 228, 196));
assert_that!(Color::from_str("chocolate")).is_ok().is_equal_to(Color(210, 105, 30));
assert_that!(Color::from_str("crimson")).is_ok().is_equal_to(Color(220, 20, 60));
assert_that!(Color::from_str("darksalmon")).is_ok().is_equal_to(Color(233, 150, 122));
assert_that!(Color::from_str("firebrick")).is_ok().is_equal_to(Color(178, 34, 34));
assert_that!(Color::from_str("ivory")).is_ok().is_equal_to(Color(255, 255, 240));
assert_that!(Color::from_str("lavender")).is_ok().is_equal_to(Color(230, 230, 250));
assert_that!(Color::from_str("lightsteelblue")).is_ok().is_equal_to(Color(176, 196, 222));
assert_that!(Color::from_str("mediumseagreen")).is_ok().is_equal_to(Color(60, 179, 113));
assert_that!(Color::from_str("paleturquoise")).is_ok().is_equal_to(Color(175, 238, 238));
assert_that!(Color::from_str("sienna")).is_ok().is_equal_to(Color(160, 82, 45));
assert_that!(Color::from_str("tomato")).is_ok().is_equal_to(Color(255, 99, 71));
assert_that!(Color::from_str("wheat")).is_ok().is_equal_to(Color(245, 222, 179));
assert_that!(Color::from_str("yellowgreen")).is_ok().is_equal_to(Color(154, 205, 50));
}
#[test]
fn html_rgb() {
assert_that!(Color::from_str("#0f0")).is_ok().is_equal_to(Color(0, 0xff, 0));
assert_that!(Color::from_str("#00ff00")).is_ok().is_equal_to(Color(0, 0xff, 0));
assert_that!(Color::from_str("0xff0000")).is_ok().is_equal_to(Color(0xff, 0, 0));
assert_that!(Color::from_str("$0000ff")).is_ok().is_equal_to(Color(0, 0, 0xff));
assert_that!(Color::from_str("0xf0f")).is_err();
assert_that!(Color::from_str("$ff0")).is_err();
assert_that!(Color::from_str("$0x00ffff")).is_err();
assert_that!(Color::from_str("f0f0f0")).is_err();
}
#[test]
fn transparency_not_supported() {
assert_that!(Color::from_str("transparent"))
.is_err().is_equal_to(ColorParseError::Alpha(0.0));
assert_that!(Color::from_str("rgba(0, 0, 0, 0.5)"))
.is_err().is_equal_to(ColorParseError::Alpha(0.5));
}
}
}