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 (with in-place deserialization)

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 Apr 26, 2023
1 parent 9c172ea commit 89d0f10
Show file tree
Hide file tree
Showing 9 changed files with 636 additions and 2 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/CI.yml
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ jobs:
- { name: "cairo", features: "png,pdf,svg,ps,use_glib,v1_18,freetype,script,xcb,xlib,win32-surface", nightly: "--features 'png,pdf,svg,ps,use_glib,v1_18,freetype,script,xcb,xlib,win32-surface'", test_sys: true }
- { name: "gdk-pixbuf", features: "v2_42", nightly: "--all-features", test_sys: true }
- { name: "gio", features: "v2_74", nightly: "--all-features", test_sys: true }
- { name: "glib", features: "v2_74", nightly: "--all-features", test_sys: true }
- { name: "glib", features: "v2_74,serde", nightly: "--all-features", test_sys: true }
- { name: "graphene", features: "", 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
3 changes: 3 additions & 0 deletions glib/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,12 +35,14 @@ smallvec = { version = "1.0", features = ["union", "const_generics", "const_new"
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"
gir-format-check = "^0.1"
trybuild2 = "1"
criterion = "0.4.0"
serde_json = "1.0"

[features]
default = ["gio"]
Expand All @@ -59,6 +61,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"]
Expand Down
3 changes: 3 additions & 0 deletions glib/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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
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 byte_vec = Vec::with_capacity(min(s.size_hint().unwrap_or(0), 4096));

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

Ok(ByteArray::from(byte_vec.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 byte_vec = Vec::with_capacity(min(s.size_hint().unwrap_or(0), 4096));

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

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

#[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();
}
}
194 changes: 194 additions & 0 deletions glib/src/serde/collections.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
// 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)
},
@in_place(this) => match impl {
Seq(s) => {
this.clear();
this.reserve(min(s.size_hint().unwrap_or(0), 4096));

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

Ok(())
},
},
}
}

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)
},
@in_place(this) => match impl {
Seq(s) => {
this.clear();
this.reserve(min(s.size_hint().unwrap_or(0), 4096));

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

Ok(())
},
},
}
}

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)
},
@in_place(this) => match impl {
Seq(s) => {
this.clear();

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

Ok(())
},
},
}
}

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)
},
@in_place(this) => match impl {
Seq(s) => {
this.clear();

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

Ok(())
},
},
}
}

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_equality() {
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();
}
}
Loading

0 comments on commit 89d0f10

Please sign in to comment.