From 407d50b31ef9ed710df04b965e9d611396417e6f Mon Sep 17 00:00:00 2001 From: Paskal Sitepu Date: Tue, 11 Apr 2023 06:31:39 +0700 Subject: [PATCH] glib: Add optional support for serialization and deserialization with serde This feature is gated as `serde` Supports both serialization and deserialization: - glib::Bytes - glib::GString (with in-place deserialization) Supports serialization only: - glib::ByteArray - glib::GStr - glib::StrV Collection types are also supported as long as the type parameters implement the necessary traits: - glib::Slice - glib::PtrSlice - glib::List - glib::SList --- glib/Cargo.toml | 2 + glib/README.md | 10 ++ glib/src/lib.rs | 3 + glib/src/serde.rs | 401 ++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 416 insertions(+) create mode 100644 glib/src/serde.rs diff --git a/glib/Cargo.toml b/glib/Cargo.toml index 6ca08d8c6e9d..80fd8dd24418 100644 --- a/glib/Cargo.toml +++ b/glib/Cargo.toml @@ -35,6 +35,7 @@ smallvec = "1.0" thiserror = "1" gio_ffi = { package = "gio-sys", path = "../gio/sys", optional = true } memchr = "2.5.0" +serde = { version = "1.0", optional = true } [dev-dependencies] tempfile = "3" @@ -59,6 +60,7 @@ log_macros = ["log"] dox = ["ffi/dox", "gobject_ffi/dox", "log_macros"] compiletests = [] gio = ["gio_ffi"] +serde = ["dep:serde"] [package.metadata.docs.rs] features = ["dox"] diff --git a/glib/README.md b/glib/README.md index 73b0d3bccbbd..ce103b41ae4d 100644 --- a/glib/README.md +++ b/glib/README.md @@ -96,6 +96,16 @@ glib = "0.13" glib = { git = "https://github.com/gtk-rs/gtk-rs-core.git", package = "glib" } ``` +### Serialization with Serde + +If you want to serialize (and deserialize) some GLib types (e.g. `GString`) with Serde, you can enable the `serde` feature: + +```toml +glib = { version = "0.18", features = ["serde"] } +``` + +This library implements serialization and deserialization as described in the `serde@^1.0` API. + ## License __glib__ is available under the MIT License, please refer to it. diff --git a/glib/src/lib.rs b/glib/src/lib.rs index eb276045ba5a..d3b1cf76648d 100644 --- a/glib/src/lib.rs +++ b/glib/src/lib.rs @@ -213,6 +213,9 @@ pub use self::thread_pool::{ThreadHandle, ThreadPool}; pub mod thread_guard; +#[cfg(feature = "serde")] +mod serde; + // rustdoc-stripper-ignore-next /// This is the log domain used by the [`clone!`][crate::clone!] macro. If you want to use a custom /// logger (it prints to stdout by default), you can set your own logger using the corresponding diff --git a/glib/src/serde.rs b/glib/src/serde.rs new file mode 100644 index 000000000000..d5fdb5d4eb7d --- /dev/null +++ b/glib/src/serde.rs @@ -0,0 +1,401 @@ +// Take a look at the license at the top of the repository in the LICENSE file. + +use std::cmp::min; + +macro_rules! serialize_impl { + ($ty:ty, Bytes($bind:ident) => $expr:expr) => { + impl ::serde::Serialize for $ty { + fn serialize(&self, serializer: S) -> Result + where + S: ::serde::Serializer, + { + let $bind = self; + + serializer.serialize_bytes($expr) + } + } + }; + ($ty:ty, Str($bind:ident) => $expr:expr) => { + impl ::serde::Serialize for $ty { + fn serialize(&self, serializer: S) -> Result + where + S: ::serde::Serializer, + { + let $bind = self; + + serializer.serialize_str($expr) + } + } + }; + ($ty:ident$(<$($generic:ident $(: $bound:tt $(+ $bound2:tt)*)?),+>)?, Iterator($bind:ident) => $expr:expr) => { + impl$(<$($generic $(: ::serde::Serialize + $bound $(+ $bound2)*)?),+>)? ::serde::Serialize for $ty$(<$($generic),+>)? { + fn serialize(&self, serializer: S) -> Result + where + S: ::serde::Serializer + { + let $bind = self; + serializer.collect_seq($expr) + } + } + }; +} + +macro_rules! deserialize_in_place_gen { + ($ty:ty,) => {}; // No-op expansion for simple types + ($ty:ident$(<$($generic:ident$(: $bound:tt $(+ $bound2:tt)*)?),+>)?, $expect:literal,) => {}; // No-op expansion for parametrized types with bounds + ($ty:ty, Bytes($self:ident, $arg:ident) => $visit:expr) => { + // This is unused for now, but it will be, once GByteArray bindings improve and the APIs get exposed + + fn deserialize_in_place(deserializer: D, place: &mut Self) -> Result<(), D::Error> + where + D: ::serde::Deserializer<'de>, + { + struct InPlaceVisitor<'a>(&'a mut $ty); + + impl<'a, 'de> ::serde::de::Visitor<'de> for InPlaceVisitor<'a> { + type Value = (); + + fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + formatter.write_str("an array of bytes") + } + + fn visit_seq(self, mut $arg: A) -> Result + where + A: ::serde::de::SeqAccess<'de>, + { + let $self = self.0; + + $visit + } + } + + deserializer.deserialize_seq(InPlaceVisitor(place)) + } + }; + ($ty:ty, String($self:ident, $arg:ident) => $visit:expr) => { + fn deserialize_in_place(deserializer: D, place: &mut Self) -> Result<(), D::Error> + where + D: ::serde::Deserializer<'de>, + { + struct InPlaceVisitor<'a>(&'a mut $ty); + + impl<'a, 'de> ::serde::de::Visitor<'de> for InPlaceVisitor<'a> { + type Value = (); + + fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + formatter.write_str("an array of bytes") + } + + fn visit_str(self, _: &str) -> Result + where + E: ::serde::de::Error, + { + unimplemented!() + } + + fn visit_string(self, $arg: String) -> Result + where + E: ::serde::de::Error, + { + let $self = self.0; + + $visit + } + } + + deserializer.deserialize_string(InPlaceVisitor(place)) + } + }; + ($ty:ident$(<$($generic:ident$(: $bound:tt $(+ $bound2:tt)*)?),+>)?, $expect:literal, Collection($self:ident, $arg:ident) => $visit:expr) => { + fn deserialize_in_place(deserializer: D, place: &mut Self) -> Result<(), D::Error> + where + D: ::serde::Deserializer<'de>, + { + struct InPlaceVisitor<'a, 'de, $($($generic $(: ::serde::Deserialize<'de> + $bound $(+ $bound2)*)?),+)?>(&'a mut $ty$(<$($generic),+>)?, ::std::marker::PhantomData<&'de ()>); + + impl<'a, 'de, $($($generic $(: ::serde::Deserialize<'de> + $bound $(+ $bound2)*)?),+)?> ::serde::de::Visitor<'de> for InPlaceVisitor<'a, 'de$(, $($generic),+)?> { + type Value = (); + + fn expecting( + &self, + formatter: &mut ::std::fmt::Formatter, + ) -> ::std::fmt::Result { + formatter.write_str("a valid UTF-8 string") + } + + fn visit_seq(self, mut $arg: A) -> Result + where + A: ::serde::de::SeqAccess<'de>, + { + let $self = self.0; + + $visit + } + } + + deserializer.deserialize_seq(InPlaceVisitor(place, ::std::marker::PhantomData)) + } + }; +} + +macro_rules! deserialize_impl { + ($ty:ty, Bytes($arg:ident) => $visit:expr $(, @in_place ($self_inplace:ident, $arg_inplace:ident) => $visit_inplace:expr)?) => { + impl<'de> ::serde::Deserialize<'de> for $ty { + fn deserialize(deserializer: D) -> Result + where + D: ::serde::Deserializer<'de>, + { + struct Visitor; + + impl<'a> ::serde::de::Visitor<'a> for Visitor { + type Value = $ty; + + fn expecting( + &self, + formatter: &mut ::std::fmt::Formatter, + ) -> ::std::fmt::Result { + formatter.write_str("an array of bytes") + } + + fn visit_seq(self, mut $arg: A) -> Result + where + A: ::serde::de::SeqAccess<'a>, + { + $visit + } + } + + deserializer.deserialize_seq(Visitor) + } + + deserialize_in_place_gen!($ty, $(Bytes($self_inplace, $arg_inplace) => $visit_inplace)?); + } + }; + ($ty:ty, String($arg:ident) => $visit:expr $(, @in_place ($self_inplace:ident, $arg_inplace:ident) => $visit_inplace:expr)?) => { + impl<'de> ::serde::Deserialize<'de> for $ty { + fn deserialize(deserializer: D) -> Result + where + D: ::serde::Deserializer<'de>, + { + struct Visitor; + + impl<'a> ::serde::de::Visitor<'a> for Visitor { + type Value = $ty; + + fn expecting( + &self, + formatter: &mut ::std::fmt::Formatter, + ) -> ::std::fmt::Result { + formatter.write_str("a valid UTF-8 string") + } + + fn visit_str(self, _: &str) -> Result + where + E: ::serde::de::Error, + { + unimplemented!() + } + + fn visit_string(self, $arg: String) -> Result + where + E: ::serde::de::Error, + { + $visit + } + } + + deserializer.deserialize_string(Visitor) + } + + deserialize_in_place_gen!($ty, $(String($self_inplace, $arg_inplace) => $visit_inplace)?); + } + }; + + ($ty:ident$(<$($generic:ident$(: $bound:tt $(+ $bound2:tt)*)?),+>)?, $expect:literal, Collection($arg:ident) => $visit:expr $(, @in_place ($self_inplace:ident, $arg_inplace:ident) => $visit_inplace:expr)?) => { + impl<'de, $($($generic $(: ::serde::Deserialize<'de> + $bound $(+ $bound2)*)?),+)?> ::serde::Deserialize<'de> for $ty$(<$($generic),+>)? { + fn deserialize(deserializer: D) -> Result + where + D: ::serde::Deserializer<'de>, + { + struct Visitor<'v, $($($generic $(: ::serde::Deserialize<'v> + $bound $(+ $bound2)*)?),+)?>(::std::marker::PhantomData<&'v ()>, $($(::std::marker::PhantomData $generic>),+)?); + + impl<'a, $($($generic $(: ::serde::Deserialize<'a> + $bound $(+ $bound2)*)?),+)?> ::serde::de::Visitor<'a> for Visitor<'a, $($($generic),+)?> { + type Value = $ty$(<$($generic),+>)?; + + fn expecting(&self, formatter: &mut ::std::fmt::Formatter) -> ::std::fmt::Result { + formatter.write_str($expect) + } + + fn visit_seq(self, mut $arg: A) -> Result + where + A: ::serde::de::SeqAccess<'a>, + { + $visit + } + } + + deserializer.deserialize_seq(Visitor(::std::marker::PhantomData, $($(::std::marker::PhantomData:: $generic>::default()),+)?)) + } + + deserialize_in_place_gen!($ty$(<$($generic$(: $bound $(+ $bound2)*)?),+>)?, $expect, $(Collection($self_inplace, $arg_inplace) => $visit_inplace)?); + } + }; +} + +mod byte_array { + use crate::ByteArray; + + serialize_impl!(ByteArray, Bytes(b) => b); +} + +mod bytes { + use super::*; + + use crate::Bytes; + + serialize_impl!(Bytes, Bytes(b) => b); + + deserialize_impl!( + Bytes, + Bytes(seq) => { + // See https://docs.rs/serde/1.0.159/src/serde/de/impls.rs.html#1038 + // and https://docs.rs/serde/1.0.159/src/serde/private/size_hint.rs.html#13 + let mut byte_vec = Vec::with_capacity(min(seq.size_hint().unwrap_or(0), 4096)); + + while let Some(byte) = seq.next_element()? { + byte_vec.push(byte) + } + + Ok(Bytes::from_owned(byte_vec)) + } + ); +} + +mod gstring { + use crate::{GStr, GString, GStringPtr}; + use serde::de; + + serialize_impl!(GStr, Str(s) => s.as_str()); + + serialize_impl!(GString, Str(s) => s.as_str()); + + deserialize_impl!( + GString, + String(s) => GString::from_string_checked(s).map_err(|e| de::Error::custom(e)), + @in_place (this, s) => { + *this = GString::from_string_checked(s).map_err(|e| de::Error::custom(e))?; + Ok(()) + } + ); + + serialize_impl!(GStringPtr, Str(s) => s.to_str()); +} + +mod collections { + use super::*; + + use crate::{ + translate::{TransparentPtrType, TransparentType}, + List, PtrSlice, SList, Slice, StrV, + }; + + serialize_impl!(Slice, Iterator(iter) => iter); + + deserialize_impl!(Slice, "a sequence of GLib transparent values", + Collection(seq) => { + let mut slice = Slice::with_capacity(min(seq.size_hint().unwrap_or(0), 4096)); + + while let Some(item) = seq.next_element()? { + slice.push(item) + } + + Ok(slice) + }, + @in_place (this, seq) => { + this.clear(); + this.reserve(min(seq.size_hint().unwrap_or(0), 4096)); + + while let Some(item) = seq.next_element()? { + this.push(item) + } + + Ok(()) + } + ); + + serialize_impl!(PtrSlice, Iterator(iter) => iter); + + deserialize_impl!(PtrSlice, "a sequence of GLib transparent pointer values", + Collection(seq) => { + let mut slice = PtrSlice::with_capacity(min(seq.size_hint().unwrap_or(0), 4096)); + + while let Some(item) = seq.next_element()? { + slice.push(item) + } + + Ok(slice) + }, + @in_place (this, seq) => { + this.clear(); + this.reserve(min(seq.size_hint().unwrap_or(0), 4096)); + + while let Some(item) = seq.next_element()? { + this.push(item) + } + + Ok(()) + } + ); + + serialize_impl!(List, Iterator(iter) => iter.iter()); + + deserialize_impl!(List, "a sequence of GLib transparent pointer values", + Collection(seq) => { + let mut list = List::new(); + + while let Some(item) = seq.next_element()? { + list.push_front(item) + } + list.reverse(); + + Ok(list) + }, + @in_place (this, seq) => { + this.clear(); + + while let Some(item) = seq.next_element()? { + this.push_front(item) + } + this.reverse(); + + Ok(()) + } + ); + + serialize_impl!(SList, Iterator(iter) => iter.iter()); + + deserialize_impl!(SList, "a sequence of GLib transparent pointer values", + Collection(seq) => { + let mut list = SList::new(); + + while let Some(item) = seq.next_element()? { + list.push_front(item) + } + list.reverse(); + + Ok(list) + }, + @in_place (this, seq) => { + this.clear(); + + while let Some(item) = seq.next_element()? { + this.push_front(item) + } + this.reverse(); + + Ok(()) + } + ); + + serialize_impl!(StrV, Iterator(iter) => iter); +}