Skip to content

Commit

Permalink
glib: Add optional support for serialization and deserialization with…
Browse files Browse the repository at this point in the history
… serde

This feature is gated as `serde`

Supports both serialization and deserialization:
- glib::ByteArray
- glib::Bytes
- glib::GString

Supports serialization only:
- glib::GStr
- glib::StrV

Collection types are also supported  as long as the type parameters implement the necessary traits:
- glib::Slice<T: TransparentType + ..>
- glib::PtrSlice<T: TransparentPtrType + ..>
- glib::List<T: TransparentPtrType + ..>
- glib::SList<T: TransparentPtrType + ..>
  • Loading branch information
rmnscnce committed Jan 13, 2024
1 parent acb7f0d commit 5fd8889
Show file tree
Hide file tree
Showing 9 changed files with 470 additions and 5 deletions.
1 change: 1 addition & 0 deletions .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ jobs:
- { name: "gdk-pixbuf", features: "v2_42", nightly: "--all-features", test_sys: true }
- { name: "gio", features: "v2_78", nightly: "--all-features", test_sys: true }
- { name: "glib", features: "v2_78", nightly: "--all-features", test_sys: true }
- { name: "glib", features: "v2_78,serde", nightly: "--all-features", test_sys: true }
- { name: "graphene", features: "v1_12", nightly: "", test_sys: true }
- { name: "pango", features: "v1_50", nightly: "--all-features", test_sys: true }
- { name: "pangocairo", features: "", nightly: "--all-features", test_sys: true }
Expand Down
9 changes: 8 additions & 1 deletion .github/workflows/windows.yml
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,14 @@ jobs:
with:
command: test
args: --manifest-path ${{ matrix.conf.name }}/sys/Cargo.toml ${{ matrix.conf.args }}
if: matrix.conf.name != 'examples' && matrix.conf.name != 'glib-build-tools'
if: matrix.conf.name != 'examples' && matrix.conf.name != 'glib' && matrix.conf.name != 'glib-build-tools'

- name: test glib-sys
uses: actions-rs/cargo@v1
with:
command: test
args: --manifest-path ${{ matrix.conf.name }}/sys/Cargo.toml --features v2_74
if: matrix.conf.name == 'glib'

- name: build
uses: actions-rs/cargo@v1
Expand Down
13 changes: 9 additions & 4 deletions glib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,7 @@ version = "0.19.0"
keywords = ["glib", "gtk-rs", "gnome", "GUI"]
repository = "https://github.com/gtk-rs/gtk-rs-core"
license = "MIT"
exclude = [
"gir-files/*",
]
exclude = ["gir-files/*"]
edition = "2021"
rust-version = "1.70"

Expand All @@ -31,16 +29,22 @@ ffi = { package = "glib-sys", path = "sys" }
gobject_ffi = { package = "gobject-sys", path = "gobject-sys" }
glib-macros = { path = "../glib-macros" }
rs-log = { package = "log", version = "0.4", optional = true }
smallvec = { version = "1.11", features = ["union", "const_generics", "const_new"] }
smallvec = { version = "1.11", features = [
"union",
"const_generics",
"const_new",
] }
thiserror = "1"
gio_ffi = { package = "gio-sys", path = "../gio/sys", optional = true }
memchr = "2.7.1"
serde = { version = "1.0", optional = true }

[dev-dependencies]
tempfile = "3"
gir-format-check = "^0.1"
trybuild2 = "1"
criterion = "0.5.1"
serde_json = "1.0"

[features]
default = ["gio"]
Expand All @@ -59,6 +63,7 @@ log = ["rs-log"]
log_macros = ["log"]
compiletests = []
gio = ["gio_ffi"]
serde = ["dep:serde"]

[package.metadata.docs.rs]
all-features = true
Expand Down
3 changes: 3 additions & 0 deletions glib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -227,6 +227,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
Expand Down
53 changes: 53 additions & 0 deletions glib/src/serde/byte_array.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
// Take a look at the license at the top of the repository in the LICENSE file.

use serde::Deserializer;

use super::*;

use crate::ByteArray;

serialize_impl!(ByteArray, Bytes(b) => b);

deserialize_impl! {
ByteArray,
"a sequence of bytes",
Deserializer::deserialize_seq => match impl {
Bytes(b) => Ok(ByteArray::from(b)),
ByteBuf(buf) => Ok(ByteArray::from(buf.as_slice())),
Seq(s) => {
// 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 bytes = Vec::with_capacity(min(s.size_hint().unwrap_or(0), 4096));

while let Some(byte) = s.next_element()? {
bytes.push(byte)
}

Ok(ByteArray::from(bytes.as_slice()))
},
}
}

#[cfg(test)]
mod tests {
use crate::{gformat, ByteArray};

#[test]
fn serialization() {
let json = match serde_json::to_value(ByteArray::from(
gformat!("Lorem ipsum dolor sit amet").as_bytes(),
)) {
Ok(v) => Some(v),
Err(_) => None,
};

assert_ne!(json, None);
}

#[test]
fn deserialization() {
let json_str = r#"[76,111,114,101,109,32,105,112,115,117,109,32,100,111,108,111,114,32,115,105,116,32,97,109,101]"#;

serde_json::from_str::<ByteArray>(json_str).unwrap();
}
}
51 changes: 51 additions & 0 deletions glib/src/serde/bytes.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
// Take a look at the license at the top of the repository in the LICENSE file.

use serde::Deserializer;

use super::*;

use crate::Bytes;

serialize_impl!(Bytes, Bytes(b) => b);

deserialize_impl! {
Bytes,
"a sequence of bytes",
Deserializer::deserialize_seq => match impl {
Bytes(b) => Ok(Bytes::from_owned(b.to_owned())),
ByteBuf(buf) => Ok(Bytes::from_owned(buf)),
Seq(s) => {
let mut bytes = Vec::with_capacity(min(s.size_hint().unwrap_or(0), 4096));

while let Some(byte) = s.next_element()? {
bytes.push(byte)
}

Ok(Bytes::from_owned(bytes))
},
}
}

#[cfg(test)]
mod tests {
use crate::{gformat, Bytes};

#[test]
fn serialization() {
let json = match serde_json::to_value(Bytes::from_owned(
gformat!("Lorem ipsum dolor sit amet").into_bytes(),
)) {
Ok(v) => Some(v),
Err(_) => None,
};

assert_ne!(json, None);
}

#[test]
fn deserialization() {
let json_str = r#"[76,111,114,101,109,32,105,112,115,117,109,32,100,111,108,111,114,32,115,105,116,32,97,109,101]"#;

serde_json::from_str::<Bytes>(json_str).unwrap();
}
}
146 changes: 146 additions & 0 deletions glib/src/serde/collections.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
// Take a look at the license at the top of the repository in the LICENSE file.

use super::*;

use crate::{
translate::{TransparentPtrType, TransparentType},
List, PtrSlice, SList, Slice, StrV,
};

serialize_impl!(Slice<T: TransparentType>, Sequence(iter) => iter);

deserialize_impl! {
Slice<T: TransparentType>,
"a sequence of GLib transparent values",
Deserializer::deserialize_seq => match impl {
Seq(s) => {
let mut slice = Slice::with_capacity(min(s.size_hint().unwrap_or(0), 4096));

while let Some(item) = s.next_element()? {
slice.push(item)
}

Ok(slice)
},
}
}

serialize_impl!(PtrSlice<T: TransparentPtrType>, Sequence(iter) => iter);

deserialize_impl! {
PtrSlice<T: TransparentPtrType>,
"a sequence of GLib transparent pointer values",
Deserializer::deserialize_seq => match impl {
Seq(s) => {
let mut slice = PtrSlice::with_capacity(min(s.size_hint().unwrap_or(0), 4096));

while let Some(item) = s.next_element()? {
slice.push(item)
}

Ok(slice)
},
}
}

serialize_impl!(List<T: TransparentPtrType>, Sequence(iter) => iter.iter());

deserialize_impl! {
List<T: TransparentPtrType>,
"a sequence of GLib transparent pointer values",
Deserializer::deserialize_seq => match impl {
Seq(s) => {
let mut list = List::new();

while let Some(item) = s.next_element()? {
list.push_front(item)
}
list.reverse();

Ok(list)
},
}
}

serialize_impl!(SList<T: TransparentPtrType>, Sequence(iter) => iter.iter());

deserialize_impl! {
SList<T: TransparentPtrType>,
"a sequence of GLib transparent pointer values",
Deserializer::deserialize_seq => match impl {
Seq(s) => {
let mut list = SList::new();

while let Some(item) = s.next_element()? {
list.push_front(item)
}
list.reverse();

Ok(list)
},
}
}

serialize_impl!(StrV, Sequence(iter) => iter);

#[cfg(test)]
mod tests {
use serde_json::json;

use crate::{gformat, Bytes, List, PtrSlice, SList, Slice};

#[test]
fn serialization() {
let bytes = gformat!("Lorem ipsum dolor sit amet").into_bytes();

let slice = Slice::from([
Bytes::from_owned(bytes[..].to_vec()),
Bytes::from_owned(bytes[1..].to_vec()),
Bytes::from_owned(bytes[2..].to_vec()),
Bytes::from_owned(bytes[3..].to_vec()),
]);

let ptr_slice = PtrSlice::from([
Bytes::from_owned(bytes[..].to_vec()),
Bytes::from_owned(bytes[1..].to_vec()),
Bytes::from_owned(bytes[2..].to_vec()),
Bytes::from_owned(bytes[3..].to_vec()),
]);

let mut list = List::<Bytes>::new();
list.push_front(Bytes::from_owned(bytes[..].to_vec()));
list.push_front(Bytes::from_owned(bytes[1..].to_vec()));
list.push_front(Bytes::from_owned(bytes[2..].to_vec()));
list.push_front(Bytes::from_owned(bytes[3..].to_vec()));
list.reverse();

let mut slist = SList::<Bytes>::new();
slist.push_front(Bytes::from_owned(bytes[..].to_vec()));
slist.push_front(Bytes::from_owned(bytes[1..].to_vec()));
slist.push_front(Bytes::from_owned(bytes[2..].to_vec()));
slist.push_front(Bytes::from_owned(bytes[3..].to_vec()));
slist.reverse();

assert_eq!(json!(&slice), json!(&list));
assert_eq!(json!(&slice), json!(&slist));
assert_eq!(json!(&ptr_slice), json!(&list));
assert_eq!(json!(&ptr_slice), json!(&slist));
assert_eq!(json!(&slice), json!(&ptr_slice));
assert_eq!(json!(&list), json!(&slist));
}

#[test]
fn deserialization() {
let json_str = r#"
[
[76,111,114,101,109,32,105,112,115,117,109,32,100,111,108,111,114,32,115,105,116,32,97,109,101],
[111,114,101,109,32,105,112,115,117,109,32,100,111,108,111,114,32,115,105,116,32,97,109,101],
[114,101,109,32,105,112,115,117,109,32,100,111,108,111,114,32,115,105,116,32,97,109,101],
[101,109,32,105,112,115,117,109,32,100,111,108,111,114,32,115,105,116,32,97,109,101]
]"#;
serde_json::from_str::<Slice<Bytes>>(json_str).unwrap();
serde_json::from_str::<PtrSlice<Bytes>>(json_str).unwrap();
serde_json::from_str::<List<Bytes>>(json_str).unwrap();
serde_json::from_str::<SList<Bytes>>(json_str).unwrap();
}
}
48 changes: 48 additions & 0 deletions glib/src/serde/gstring.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
// Take a look at the license at the top of the repository in the LICENSE file.

use super::*;

use crate::{gformat, 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,
"a valid UTF-8 string",
Deserializer::deserialize_string => match impl {
str(s) => Ok(gformat!("{s}")),
String(s) => GString::from_string_checked(s).map_err(|e| de::Error::custom(e)),
}
}

serialize_impl!(GStringPtr, str(s) => s.to_str());

Check warning on line 21 in glib/src/serde/gstring.rs

View workflow job for this annotation

GitHub Actions / build

use of deprecated method `gstring::GStringPtr::to_str`: Use as_str instead

#[cfg(test)]
mod tests {
use crate::{translate::ToGlibPtr, GString, StrV};
use serde_json::json;

use crate::gformat;

#[test]
fn serialization() {
let gstring = gformat!("Lorem ipsum dolor sit amet");
let gstr = gstring.as_gstr();
let gstringptr =
&unsafe { StrV::from_glib_none(vec![gstring.to_owned()].to_glib_none().0) }[0];

assert_eq!(json!(&gstring), json!(gstr));
assert_eq!(json!(&gstring), json!(gstringptr));
assert_eq!(json!(gstr), json!(gstringptr));
}

#[test]
fn deserialization() {
let json_str = r#""Lorem ipsum dolor sit amet""#;

serde_json::from_str::<GString>(json_str).unwrap();
}
}
Loading

0 comments on commit 5fd8889

Please sign in to comment.