diff --git a/protocol/src/chat.rs b/protocol/src/chat.rs index 2276673..8ad49c6 100644 --- a/protocol/src/chat.rs +++ b/protocol/src/chat.rs @@ -62,11 +62,13 @@ //! ``` 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, Serialize, Deserialize)] -#[serde(rename_all = "snake_case")] +#[derive(Debug, Eq, PartialEq)] pub enum Color { Black, DarkBlue, @@ -84,6 +86,93 @@ 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 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, + }) + } +} + +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) + } } #[derive(Debug, Eq, PartialEq, Serialize, Deserialize)] @@ -631,3 +720,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