diff options
| author | EightFactorial <murphkev000@gmail.com> | 2022-12-03 17:08:05 -0800 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2022-12-03 19:08:05 -0600 |
| commit | 3b93fc641250c4d01ab7f0764b7d5faec2f8ae5b (patch) | |
| tree | 727b94c9f90bba59f8571ecebeab1a33e0f4af07 | |
| parent | 661c3622bebc111a1523bc80792dc90d9d571b24 (diff) | |
| download | azalea-drasl-3b93fc641250c4d01ab7f0764b7d5faec2f8ae5b.tar.xz | |
Serialize Component (#47)
* Serializing ClientboundStatusResponsePacket
Enable serialization of ClientboundStatusResponsePacket
* Update clientbound_status_response_packet.rs
Add options previewsChat and enforcesSecureChat
* Serialize Style and TextColor
* Serialize BaseComponent
* Serialize TextComponent
* Fix Style
* Serialize Component
* Fix multiple formats per message, fix reset tag
* Fix Style, again
* Use FlatMapSerializer
* Forgot italics
* Count struct fields, reorganize logic
* Serialize TranslatableComponent
* Rewrite TextComponent Serializer
* Fix using TextColor::Parse
* Code Cleanup
* Add default attribute, just in case
* Clippy
* use serde derive feature + preferred formatting choices
Co-authored-by: BuildTools <unconfigured@null.spigotmc.org>
Co-authored-by: mat <github@matdoes.dev>
| -rw-r--r-- | Cargo.lock | 12 | ||||
| -rwxr-xr-x | azalea-chat/Cargo.toml | 8 | ||||
| -rwxr-xr-x | azalea-chat/src/base_component.rs | 5 | ||||
| -rwxr-xr-x | azalea-chat/src/component.rs | 14 | ||||
| -rwxr-xr-x | azalea-chat/src/style.rs | 68 | ||||
| -rwxr-xr-x | azalea-chat/src/text_component.rs | 19 | ||||
| -rwxr-xr-x | azalea-chat/src/translatable_component.rs | 17 | ||||
| -rwxr-xr-x | azalea-protocol/src/packets/status/clientbound_status_response_packet.rs | 32 |
8 files changed, 143 insertions, 32 deletions
@@ -1690,9 +1690,9 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.147" +version = "1.0.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d193d69bae983fc11a79df82342761dfbf28a99fc8d203dca4c3c1b590948965" +checksum = "e53f64bb4ba0191d6d0676e1b141ca55047d83b74f5607e6d8eb88126c52c2dc" dependencies = [ "serde_derive", ] @@ -1709,9 +1709,9 @@ dependencies = [ [[package]] name = "serde_derive" -version = "1.0.147" +version = "1.0.148" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4f1d362ca8fc9c3e3a7484440752472d68a6caa98f1ab81d99b5dfe517cec852" +checksum = "a55492425aa53521babf6137309e7d34c20bbfbbfcfe2c7f3a047fd1f6b92c0c" dependencies = [ "proc-macro2", "quote", @@ -1800,9 +1800,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.103" +version = "1.0.105" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a864042229133ada95abf3b54fdc62ef5ccabe9515b64717bcb9a1919e59445d" +checksum = "60b9b43d45702de4c839cb9b51d9f529c5dd26a4aff255b42b1ebc03e88ee908" dependencies = [ "proc-macro2", "quote", diff --git a/azalea-chat/Cargo.toml b/azalea-chat/Cargo.toml index b85726a3..a1caccf2 100755 --- a/azalea-chat/Cargo.toml +++ b/azalea-chat/Cargo.toml @@ -3,14 +3,14 @@ description = "Parse Minecraft chat messages." edition = "2021" license = "MIT" name = "azalea-chat" -version = "0.4.0" repository = "https://github.com/mat-1/azalea/tree/main/azalea-chat" +version = "0.4.0" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -azalea-buf = {path = "../azalea-buf", features = ["serde_json"], version = "^0.4.0" } -azalea-language = {path = "../azalea-language", version = "^0.4.0" } +azalea-buf = {path = "../azalea-buf", features = ["serde_json"], version = "^0.4.0"} +azalea-language = {path = "../azalea-language", version = "^0.4.0"} once_cell = "1.16.0" -serde = "^1.0.130" +serde = {version = "^1.0.148", features = ["derive"]} serde_json = "^1.0.72" diff --git a/azalea-chat/src/base_component.rs b/azalea-chat/src/base_component.rs index e532de11..ab4f5e5d 100755 --- a/azalea-chat/src/base_component.rs +++ b/azalea-chat/src/base_component.rs @@ -1,9 +1,12 @@ use crate::{style::Style, Component}; +use serde::Serialize; -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Serialize)] pub struct BaseComponent { // implements mutablecomponent + #[serde(skip_serializing_if = "Vec::is_empty")] pub siblings: Vec<Component>, + #[serde(flatten)] pub style: Style, } diff --git a/azalea-chat/src/component.rs b/azalea-chat/src/component.rs index 9362a66b..95387248 100755 --- a/azalea-chat/src/component.rs +++ b/azalea-chat/src/component.rs @@ -6,14 +6,15 @@ use crate::{ }; use azalea_buf::{BufReadError, McBufReadable, McBufWritable}; use once_cell::sync::Lazy; -use serde::{de, Deserialize, Deserializer}; +use serde::{de, Deserialize, Deserializer, Serialize}; use std::{ fmt::Display, io::{Cursor, Write}, }; /// A chat component, basically anything you can see in chat. -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Serialize)] +#[serde(untagged)] pub enum Component { Text(TextComponent), Translatable(TranslatableComponent), @@ -262,11 +263,10 @@ impl McBufReadable for Component { } impl McBufWritable for Component { - fn write_into(&self, _buf: &mut impl Write) -> Result<(), std::io::Error> { - // let json = serde_json::to_string(self).unwrap(); - // json.write_into(_buf); - // Ok(()) - todo!() + fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { + let json = serde_json::to_string(self).unwrap(); + json.write_into(buf)?; + Ok(()) } } diff --git a/azalea-chat/src/style.rs b/azalea-chat/src/style.rs index cdf8f86f..cb708982 100755 --- a/azalea-chat/src/style.rs +++ b/azalea-chat/src/style.rs @@ -2,6 +2,7 @@ use std::{collections::HashMap, fmt}; use azalea_buf::McBuf; use once_cell::sync::Lazy; +use serde::{ser::SerializeStruct, Serialize, Serializer}; use serde_json::Value; #[derive(Clone, PartialEq, Eq, Debug)] @@ -10,6 +11,19 @@ pub struct TextColor { pub name: Option<String>, } +impl Serialize for TextColor { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + if self.name.is_some() { + serializer.serialize_str(&self.name.as_ref().unwrap().to_ascii_lowercase()) + } else { + serializer.serialize_str(&self.format()) + } + } +} + impl TextColor { pub fn parse(value: String) -> Option<TextColor> { if value.starts_with('#') { @@ -276,17 +290,67 @@ impl TryFrom<ChatFormatting> for TextColor { #[derive(Clone, Debug, Default, PartialEq)] pub struct Style { - // these are options instead of just bools because None is different than false in this case + // These are options instead of just bools because None is different than false in this case pub color: Option<TextColor>, pub bold: Option<bool>, pub italic: Option<bool>, pub underlined: Option<bool>, pub strikethrough: Option<bool>, pub obfuscated: Option<bool>, - /// Whether it should reset the formatting before applying these styles + /// Whether formatting should be reset before applying these styles pub reset: bool, } +impl Serialize for Style { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + let len = if self.reset { + 6 + } else { + usize::from(self.color.is_some()) + + usize::from(self.bold.is_some()) + + usize::from(self.italic.is_some()) + + usize::from(self.underlined.is_some()) + + usize::from(self.strikethrough.is_some()) + + usize::from(self.obfuscated.is_some()) + }; + let mut state = serializer.serialize_struct("Style", len)?; + if let Some(color) = &self.color { + state.serialize_field("color", color)?; + } else if self.reset { + state.serialize_field("color", "white")?; + } + if let Some(bold) = &self.bold { + state.serialize_field("bold", bold)?; + } else if self.reset { + state.serialize_field("bold", &false)?; + } + if let Some(italic) = &self.italic { + state.serialize_field("italic", italic)?; + } else if self.reset { + state.serialize_field("italic", &false)?; + } + if let Some(underlined) = &self.underlined { + state.serialize_field("underlined", underlined)?; + } else if self.reset { + state.serialize_field("underlined", &false)?; + } + if let Some(strikethrough) = &self.strikethrough { + state.serialize_field("strikethrough", strikethrough)?; + } else if self.reset { + state.serialize_field("strikethrough", &false)?; + } + if let Some(obfuscated) = &self.obfuscated { + state.serialize_field("obfuscated", obfuscated)?; + } else if self.reset { + state.serialize_field("obfuscated", &false)?; + } + state.end() + } +} + impl Style { pub fn empty() -> Self { Self::default() diff --git a/azalea-chat/src/text_component.rs b/azalea-chat/src/text_component.rs index 0d88ca05..6715c93e 100755 --- a/azalea-chat/src/text_component.rs +++ b/azalea-chat/src/text_component.rs @@ -1,6 +1,6 @@ -use std::fmt::Display; - use crate::{base_component::BaseComponent, style::ChatFormatting, Component}; +use serde::{ser::SerializeMap, Serialize, Serializer, __private::ser::FlatMapSerializer}; +use std::fmt::Display; /// A component that contains text that's the same in all locales. #[derive(Clone, Debug, Default, PartialEq)] @@ -9,6 +9,21 @@ pub struct TextComponent { pub text: String, } +impl Serialize for TextComponent { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + let mut state = serializer.serialize_map(None)?; + state.serialize_entry("text", &self.text)?; + Serialize::serialize(&self.base, FlatMapSerializer(&mut state))?; + if !self.base.siblings.is_empty() { + state.serialize_entry("extra", &self.base.siblings)?; + } + state.end() + } +} + const LEGACY_FORMATTING_CODE_SYMBOL: char = 'ยง'; /// Convert a legacy color code string into a Component diff --git a/azalea-chat/src/translatable_component.rs b/azalea-chat/src/translatable_component.rs index 28725c44..7819d5ff 100755 --- a/azalea-chat/src/translatable_component.rs +++ b/azalea-chat/src/translatable_component.rs @@ -3,8 +3,10 @@ use std::fmt::{self, Display, Formatter}; use crate::{ base_component::BaseComponent, style::Style, text_component::TextComponent, Component, }; +use serde::{ser::SerializeMap, Serialize, Serializer, __private::ser::FlatMapSerializer}; -#[derive(Clone, Debug, PartialEq)] +#[derive(Clone, Debug, PartialEq, Serialize)] +#[serde(untagged)] pub enum StringOrComponent { String(String), Component(Component), @@ -18,6 +20,19 @@ pub struct TranslatableComponent { pub args: Vec<StringOrComponent>, } +impl Serialize for TranslatableComponent { + fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error> + where + S: Serializer, + { + let mut state = serializer.serialize_map(None)?; + state.serialize_entry("translate", &self.key)?; + Serialize::serialize(&self.base, FlatMapSerializer(&mut state))?; + state.serialize_entry("with", &self.args)?; + state.end() + } +} + impl TranslatableComponent { pub fn new(key: String, args: Vec<StringOrComponent>) -> Self { Self { diff --git a/azalea-protocol/src/packets/status/clientbound_status_response_packet.rs b/azalea-protocol/src/packets/status/clientbound_status_response_packet.rs index 64ea14f9..efa6080c 100755 --- a/azalea-protocol/src/packets/status/clientbound_status_response_packet.rs +++ b/azalea-protocol/src/packets/status/clientbound_status_response_packet.rs @@ -1,23 +1,23 @@ use azalea_buf::{BufReadError, McBufReadable, McBufWritable}; use azalea_chat::Component; use azalea_protocol_macros::ClientboundStatusPacket; -use serde::Deserialize; -use serde_json::Value; +use serde::{Deserialize, Serialize}; +use serde_json::{value::Serializer, Value}; use std::io::{Cursor, Write}; -#[derive(Clone, Debug, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct Version { - pub name: Component, + pub name: String, pub protocol: i32, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct SamplePlayer { pub id: String, pub name: String, } -#[derive(Clone, Debug, Deserialize)] +#[derive(Clone, Debug, Serialize, Deserialize)] pub struct Players { pub max: i32, pub online: i32, @@ -26,12 +26,22 @@ pub struct Players { } // the entire packet is just json, which is why it has deserialize -#[derive(Clone, Debug, Deserialize, ClientboundStatusPacket)] +#[derive(Clone, Debug, Serialize, Deserialize, ClientboundStatusPacket)] pub struct ClientboundStatusResponsePacket { pub description: Component, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] pub favicon: Option<String>, pub players: Players, pub version: Version, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "previewsChat")] + pub previews_chat: Option<bool>, + #[serde(default)] + #[serde(skip_serializing_if = "Option::is_none")] + #[serde(rename = "enforcesSecureChat")] + pub enforces_secure_chat: Option<bool>, } impl McBufReadable for ClientboundStatusResponsePacket { @@ -44,7 +54,11 @@ impl McBufReadable for ClientboundStatusResponsePacket { } impl McBufWritable for ClientboundStatusResponsePacket { - fn write_into(&self, _buf: &mut impl Write) -> Result<(), std::io::Error> { - todo!() + fn write_into(&self, buf: &mut impl Write) -> Result<(), std::io::Error> { + let status_string = ClientboundStatusResponsePacket::serialize(self, Serializer) + .unwrap() + .to_string(); + status_string.write_into(buf)?; + Ok(()) } } |
