From 51ceac1e2c43601fa6798da5ff2f71b21534b02c Mon Sep 17 00:00:00 2001 From: Jarek Samic Date: Fri, 19 Jun 2020 20:00:07 -0400 Subject: [PATCH 1/2] [chat] Support text hex colors in 1.16 --- protocol/src/chat.rs | 95 +++++++++++++++++++++++++++++-- protocol/test/chat/hex_color.json | 1 + 2 files changed, 92 insertions(+), 4 deletions(-) create mode 100644 protocol/test/chat/hex_color.json diff --git a/protocol/src/chat.rs b/protocol/src/chat.rs index 2276673..92b9077 100644 --- a/protocol/src/chat.rs +++ b/protocol/src/chat.rs @@ -65,8 +65,7 @@ use crate::impl_json_encoder_decoder; use serde::{Deserialize, Serialize}; use serde_json::Error; -#[derive(Debug, Eq, PartialEq, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] +#[derive(Debug, Eq, PartialEq)] pub enum Color { Black, DarkBlue, @@ -84,6 +83,70 @@ pub enum Color { LightPurple, Yellow, White, + /// A hex color string + /// + /// Support for this was added in 1.16. + /// + /// # Examples + /// + /// ``` + /// use minecraft_protocol::chat::Color; + /// + /// let color = Color::Hex("#f98aff".into()); + /// ``` + Hex(String), +} + +impl From for Color { + fn from(val: String) -> Self { + if val.starts_with("#") { + Self::Hex(val) + } else { + match val.as_ref() { + "black" => Self::Black, + "dark_blue" => Self::DarkBlue, + "dark_green" => Self::DarkGreen, + "dark_aqua" => Self::DarkAqua, + "dark_red" => Self::DarkRed, + "dark_purple" => Self::DarkPurple, + "gold" => Self::Gold, + "gray" => Self::Gray, + "dark_gray" => Self::DarkGray, + "blue" => Self::Blue, + "green" => Self::Green, + "aqua" => Self::Aqua, + "red" => Self::Red, + "light_purple" => Self::LightPurple, + "yellow" => Self::Yellow, + "white" => Self::White, + _ => unreachable!(), + } + } + } +} + +impl ToString for Color { + fn to_string(&self) -> String { + match self { + Color::Black => "black".into(), + Color::DarkBlue => "dark_blue".into(), + Color::DarkGreen => "dark_green".into(), + Color::DarkAqua => "dark_aqua".into(), + Color::DarkRed => "dark_red".into(), + Color::DarkPurple => "dark_purple".into(), + Color::Gold => "gold".into(), + Color::Gray => "gray".into(), + Color::DarkGray => "dark_gray".into(), + Color::Blue => "blue".into(), + Color::Green => "green".into(), + Color::Aqua => "aqua".into(), + Color::Red => "red".into(), + Color::LightPurple => "light_purple".into(), + Color::Yellow => "yellow".into(), + Color::White => "white".into(), + Color::Hex(val) => { val.into() } + } + } } #[derive(Debug, Eq, PartialEq, Serialize, Deserialize)] @@ -205,7 +268,7 @@ pub struct Message { #[serde(skip_serializing_if = "Option::is_none")] pub obfuscated: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub color: Option, + pub color: Option, #[serde(skip_serializing_if = "Option::is_none")] pub insertion: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -291,7 +354,7 @@ impl MessageBuilder { } pub fn color(mut self, color: Color) -> Self { - self.current.color = Some(color); + self.current.color = Some(color.to_string()); self } @@ -631,3 +694,27 @@ fn test_deserialize_hover_show_entity() { Message::from_json(include_str!("../test/chat/hover_show_entity.json")).unwrap() ); } + +#[test] +fn test_serialize_hex_color() { + let message = MessageBuilder::builder(Payload::text("Hello")) + .color(Color::Hex("#ffffff".into())) + .build(); + + assert_eq!( + message.to_json().unwrap(), + include_str!("../test/chat/hex_color.json") + ); +} + +#[test] +fn test_deserialize_hex_color() { + let expected_message = MessageBuilder::builder(Payload::text("Hello")) + .color(Color::Hex("#ffffff".into())) + .build(); + + assert_eq!( + Message::from_json(include_str!("../test/chat/hex_color.json")).unwrap(), + expected_message + ); +} diff --git a/protocol/test/chat/hex_color.json b/protocol/test/chat/hex_color.json new file mode 100644 index 0000000..a139581 --- /dev/null +++ b/protocol/test/chat/hex_color.json @@ -0,0 +1 @@ +{"color":"#ffffff","text":"Hello"} \ No newline at end of file From 541aa3342ed9a27d94ae5ad4d04f5ab75a5f8787 Mon Sep 17 00:00:00 2001 From: Jarek Samic Date: Sun, 21 Jun 2020 20:42:23 -0400 Subject: [PATCH 2/2] Custom `Serialize` / `Deserialize` impls for `Color` --- protocol/src/chat.rs | 124 ++++++++++++++++++++++++++----------------- 1 file changed, 75 insertions(+), 49 deletions(-) diff --git a/protocol/src/chat.rs b/protocol/src/chat.rs index 92b9077..8ad49c6 100644 --- a/protocol/src/chat.rs +++ b/protocol/src/chat.rs @@ -62,7 +62,10 @@ //! ``` use crate::impl_json_encoder_decoder; -use serde::{Deserialize, Serialize}; +use serde::{ + de::{self, Visitor}, + Deserialize, Serialize, +}; use serde_json::Error; #[derive(Debug, Eq, PartialEq)] @@ -97,55 +100,78 @@ pub enum Color { Hex(String), } -impl From for Color { - fn from(val: String) -> Self { - if val.starts_with("#") { - Self::Hex(val) - } else { - match val.as_ref() { - "black" => Self::Black, - "dark_blue" => Self::DarkBlue, - "dark_green" => Self::DarkGreen, - "dark_aqua" => Self::DarkAqua, - "dark_red" => Self::DarkRed, - "dark_purple" => Self::DarkPurple, - "gold" => Self::Gold, - "gray" => Self::Gray, - "dark_gray" => Self::DarkGray, - "blue" => Self::Blue, - "green" => Self::Green, - "aqua" => Self::Aqua, - "red" => Self::Red, - "light_purple" => Self::LightPurple, - "yellow" => Self::Yellow, - "white" => Self::White, - _ => unreachable!(), - } - } +impl Serialize for Color { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(match self { + Color::Black => "black", + Color::DarkBlue => "dark_blue", + Color::DarkGreen => "dark_green", + Color::DarkAqua => "dark_aqua", + Color::DarkRed => "dark_red", + Color::DarkPurple => "dark_purple", + Color::Gold => "gold", + Color::Gray => "gray", + Color::DarkGray => "dark_gray", + Color::Blue => "blue", + Color::Green => "green", + Color::Aqua => "aqua", + Color::Red => "red", + Color::LightPurple => "light_purple", + Color::Yellow => "yellow", + Color::White => "white", + Color::Hex(val) => val, + }) } } -impl ToString for Color { - fn to_string(&self) -> String { - match self { - Color::Black => "black".into(), - Color::DarkBlue => "dark_blue".into(), - Color::DarkGreen => "dark_green".into(), - Color::DarkAqua => "dark_aqua".into(), - Color::DarkRed => "dark_red".into(), - Color::DarkPurple => "dark_purple".into(), - Color::Gold => "gold".into(), - Color::Gray => "gray".into(), - Color::DarkGray => "dark_gray".into(), - Color::Blue => "blue".into(), - Color::Green => "green".into(), - Color::Aqua => "aqua".into(), - Color::Red => "red".into(), - Color::LightPurple => "light_purple".into(), - Color::Yellow => "yellow".into(), - Color::White => "white".into(), - Color::Hex(val) => { val.into() } - } +struct ColorVisitor; + +impl<'de> Visitor<'de> for ColorVisitor { + type Value = Color; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str("a hex color string or a pre-defined color name") + } + + fn visit_str(self, v: &str) -> Result + where + E: de::Error, + { + Ok(if v.starts_with("#") { + Color::Hex(v.into()) + } else { + match v { + "black" => Color::Black, + "dark_blue" => Color::DarkBlue, + "dark_green" => Color::DarkGreen, + "dark_aqua" => Color::DarkAqua, + "dark_red" => Color::DarkRed, + "dark_purple" => Color::DarkPurple, + "gold" => Color::Gold, + "gray" => Color::Gray, + "dark_gray" => Color::DarkGray, + "blue" => Color::Blue, + "green" => Color::Green, + "aqua" => Color::Aqua, + "red" => Color::Red, + "light_purple" => Color::LightPurple, + "yellow" => Color::Yellow, + "white" => Color::White, + _ => return Err(E::invalid_value(de::Unexpected::Str(v), &self)), + } + }) + } +} + +impl<'de> Deserialize<'de> for Color { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + deserializer.deserialize_str(ColorVisitor) } } @@ -268,7 +294,7 @@ pub struct Message { #[serde(skip_serializing_if = "Option::is_none")] pub obfuscated: Option, #[serde(skip_serializing_if = "Option::is_none")] - pub color: Option, + pub color: Option, #[serde(skip_serializing_if = "Option::is_none")] pub insertion: Option, #[serde(skip_serializing_if = "Option::is_none")] @@ -354,7 +380,7 @@ impl MessageBuilder { } pub fn color(mut self, color: Color) -> Self { - self.current.color = Some(color.to_string()); + self.current.color = Some(color); self }