From 2c79383d11543a5d555ad40bd8a1f7bc9295eb45 Mon Sep 17 00:00:00 2001 From: Ross MacArthur Date: Wed, 24 Jul 2024 13:41:09 +0200 Subject: [PATCH] Add `serde` support for types --- .gitignore | 1 + Cargo.lock | 111 +++++++++++++++++++++++++++++++++++++------ Cargo.toml | 8 ++++ generate/src/main.rs | 4 ++ src/gen/mod.rs | 1 + src/lib.rs | 39 +++++++++++++++ tests/serde.rs | 70 +++++++++++++++++++++++++++ 7 files changed, 220 insertions(+), 14 deletions(-) create mode 100644 tests/serde.rs diff --git a/.gitignore b/.gitignore index ea8c4bf..81cf465 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ /target +/.vscode diff --git a/Cargo.lock b/Cargo.lock index db6e136..740c8f8 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -58,8 +58,17 @@ name = "emojis" version = "0.6.2" dependencies = [ "phf", + "serde", + "serde_json", + "toml", ] +[[package]] +name = "equivalent" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" + [[package]] name = "generate" version = "0.0.0" @@ -67,7 +76,7 @@ dependencies = [ "anyhow", "curl", "heck", - "indexmap", + "indexmap 1.9.3", "phf_codegen", "serde", "serde_json", @@ -80,6 +89,12 @@ version = "0.12.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8a9ee70c43aaf417c914396645a0fa852624801b24ebb7ae78fe8272889ac888" +[[package]] +name = "hashbrown" +version = "0.14.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e5274423e17b7c9fc20b6e7e208532f9b19825d82dfd615708b70edd83df41f1" + [[package]] name = "heck" version = "0.3.3" @@ -96,7 +111,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bd070e393353796e801d209ad339e89596eb4c8d430d18ede6a1cced8fafbd99" dependencies = [ "autocfg", - "hashbrown", + "hashbrown 0.12.3", +] + +[[package]] +name = "indexmap" +version = "2.2.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "168fb715dda47215e360912c096649d23d58bf392ac62f73919e831745e40f26" +dependencies = [ + "equivalent", + "hashbrown 0.14.5", ] [[package]] @@ -123,6 +148,12 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "memchr" +version = "2.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" + [[package]] name = "openssl-probe" version = "0.1.5" @@ -187,18 +218,18 @@ checksum = "26072860ba924cbfa98ea39c8c19b4dd6a4a25423dbdf219c1eca91aa0cf6964" [[package]] name = "proc-macro2" -version = "1.0.66" +version = "1.0.86" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18fb31db3f9bddb2ea821cde30a9f70117e3f119938b5ee630b7403aa6e2ead9" +checksum = "5e719e8df665df0d1c8fbfd238015744736151d4445ec0836b8e628aae103b77" dependencies = [ "unicode-ident", ] [[package]] name = "quote" -version = "1.0.33" +version = "1.0.36" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5267fca4496028628a95160fc423a33e8b2e6af8a5302579e322e4b520293cae" +checksum = "0fa76aaf39101c457836aec0ce2316dbdc3ab723cdda1c6bd4e6ad4208acaca7" dependencies = [ "proc-macro2", ] @@ -235,18 +266,18 @@ dependencies = [ [[package]] name = "serde" -version = "1.0.188" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cf9e0fcba69a370eed61bcf2b728575f726b50b55cba78064753d708ddc7549e" +checksum = "bc76f558e0cbb2a839d37354c575f1dc3fdc6546b5be373ba43d95f231bf7c12" dependencies = [ "serde_derive", ] [[package]] name = "serde_derive" -version = "1.0.188" +version = "1.0.204" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4eca7ac642d82aa35b60049a6eccb4be6be75e599bd2e9adb5f875a737654af2" +checksum = "e0cd7e117be63d3c3678776753929474f3b04a43a080c744d6b0ae2a8c28e222" dependencies = [ "proc-macro2", "quote", @@ -255,15 +286,24 @@ dependencies = [ [[package]] name = "serde_json" -version = "1.0.105" +version = "1.0.120" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "693151e1ac27563d6dbcec9dee9fbd5da8539b20fa14ad3752b2e6d363ace360" +checksum = "4e0d21c9a8cae1235ad58a00c11cb40d4b1e5c784f1ef2c537876ed6ffd8b7c5" dependencies = [ "itoa", "ryu", "serde", ] +[[package]] +name = "serde_spanned" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "79e674e01f999af37c49f70a6ede167a8a60b2503e56c5599532a65baa5969a0" +dependencies = [ + "serde", +] + [[package]] name = "siphasher" version = "0.3.11" @@ -282,9 +322,9 @@ dependencies = [ [[package]] name = "syn" -version = "2.0.31" +version = "2.0.72" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "718fa2415bcb8d8bd775917a1bf12a7931b6dfa890753378538118181e0cb398" +checksum = "dc4b9b9bf2add8093d3f2c0204471e951b2285580335de42f9d2534f3ae7a8af" dependencies = [ "proc-macro2", "quote", @@ -297,6 +337,40 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "48ed51d496b818a158761fb6b75e24df2380588996e609afe3fd3993577ff628" +[[package]] +name = "toml" +version = "0.8.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac2caab0bf757388c6c0ae23b3293fdb463fee59434529014f85e3263b995c28" +dependencies = [ + "serde", + "serde_spanned", + "toml_datetime", + "toml_edit", +] + +[[package]] +name = "toml_datetime" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" +dependencies = [ + "serde", +] + +[[package]] +name = "toml_edit" +version = "0.22.16" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "278f3d518e152219c994ce877758516bca5e118eaed6996192a774fb9fbf0788" +dependencies = [ + "indexmap 2.2.6", + "serde", + "serde_spanned", + "toml_datetime", + "winnow", +] + [[package]] name = "unicode-ident" version = "1.0.11" @@ -402,3 +476,12 @@ name = "windows_x86_64_msvc" version = "0.48.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ed94fce61571a4006852b7389a063ab983c02eb1bb37b47f8272ce92d06d9538" + +[[package]] +name = "winnow" +version = "0.6.15" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "557404e450152cd6795bb558bca69e43c585055f4606e3bcae5894fc6dac9ba0" +dependencies = [ + "memchr", +] diff --git a/Cargo.toml b/Cargo.toml index a40eebd..7fd1332 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -16,3 +16,11 @@ members = ["generate"] [dependencies] phf = { version = "0.11.1", default-features = false } +serde = { version = "1.0.124", default-features = false, features = ["derive"], optional = true } + +[dev-dependencies] +serde_json = "1.0.120" +toml = "0.8.15" + +[features] +serde = ["dep:serde"] diff --git a/generate/src/main.rs b/generate/src/main.rs index 5fdaaa6..ac5de95 100644 --- a/generate/src/main.rs +++ b/generate/src/main.rs @@ -19,6 +19,10 @@ fn write_group_enum(w: &mut W, unicode_data: &unicode::ParsedData) w, "#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]" )?; + writeln!( + w, + r#"#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]"#, + )?; writeln!(w, "pub enum Group {{")?; for name in unicode_data.keys() { if name == "Component" { diff --git a/src/gen/mod.rs b/src/gen/mod.rs index f5bc509..e315c24 100644 --- a/src/gen/mod.rs +++ b/src/gen/mod.rs @@ -11,6 +11,7 @@ use crate::{Emoji, SkinTone, UnicodeVersion}; /// /// Based on Unicode CLDR data. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub enum Group { SmileysAndEmotion, PeopleAndBody, diff --git a/src/lib.rs b/src/lib.rs index e320200..d75b58a 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -139,6 +139,7 @@ pub struct Emoji { /// A Unicode version. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] pub struct UnicodeVersion { major: u32, minor: u32, @@ -146,6 +147,7 @@ pub struct UnicodeVersion { /// The skin tone of an emoji. #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] #[non_exhaustive] pub enum SkinTone { Default, @@ -459,6 +461,43 @@ impl fmt::Display for Emoji { } } +#[cfg(feature = "serde")] +impl serde::Serialize for &'static Emoji { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + serializer.serialize_str(self.as_str()) + } +} + +#[cfg(feature = "serde")] +impl<'de> serde::Deserialize<'de> for &'static Emoji { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + struct Visitor; + + impl<'de> serde::de::Visitor<'de> for Visitor { + type Value = &'static Emoji; + + fn expecting(&self, formatter: &mut core::fmt::Formatter) -> core::fmt::Result { + formatter.write_str("a string representing an emoji") + } + + fn visit_str(self, value: &str) -> Result + where + E: serde::de::Error, + { + crate::get(value).ok_or_else(|| E::custom("invalid emoji")) + } + } + + deserializer.deserialize_str(Visitor) + } +} + impl Group { /// Returns an iterator over all groups. /// diff --git a/tests/serde.rs b/tests/serde.rs new file mode 100644 index 0000000..4c6f6b5 --- /dev/null +++ b/tests/serde.rs @@ -0,0 +1,70 @@ +#![cfg(feature = "serde")] + +use emojis::{Emoji, Group, SkinTone, UnicodeVersion}; +use serde::{Deserialize, Serialize}; + +#[derive(Debug, PartialEq, Eq, Deserialize, Serialize)] +struct Test { + version: UnicodeVersion, + skin_tone: SkinTone, + group: Group, + emoji: &'static Emoji, +} + +#[test] +fn test_serialize_roundtrip_json() { + let test = Test { + emoji: emojis::get("🚀").unwrap(), + version: UnicodeVersion::new(13, 0), + skin_tone: SkinTone::Default, + group: Group::Activities, + }; + let serialized = serde_json::to_string_pretty(&test).unwrap(); + assert_eq!( + serialized, + r#"{ + "version": { + "major": 13, + "minor": 0 + }, + "skin_tone": "Default", + "group": "Activities", + "emoji": "🚀" +}"# + ); + let deserialized: Test = serde_json::from_str(&serialized).unwrap(); + + assert_eq!(deserialized, test); +} + +#[test] +fn test_serialize_roundtrip_toml() { + let test = Test { + emoji: emojis::get("🚀").unwrap(), + version: UnicodeVersion::new(13, 0), + skin_tone: SkinTone::Default, + group: Group::Activities, + }; + let serialized = toml::to_string(&test).unwrap(); + assert_eq!( + serialized, + r#"skin_tone = "Default" +group = "Activities" +emoji = "🚀" + +[version] +major = 13 +minor = 0 +"# + ); + + let deserialized: Test = toml::from_str(&serialized).unwrap(); + + assert_eq!(deserialized, test); +} + +#[test] +fn emoji_deserialize_invalid() { + let err = serde_json::from_str::(r#"{"emoji":"invalid"}"#).unwrap_err(); + assert_eq!(err.to_string(), "invalid emoji at line 1 column 18"); +}