Skip to content

Commit 5fd8889

Browse files
committed
glib: Add optional support for serialization and deserialization with 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 + ..>
1 parent acb7f0d commit 5fd8889

File tree

9 files changed

+470
-5
lines changed

9 files changed

+470
-5
lines changed

.github/workflows/CI.yml

+1
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ jobs:
2424
- { name: "gdk-pixbuf", features: "v2_42", nightly: "--all-features", test_sys: true }
2525
- { name: "gio", features: "v2_78", nightly: "--all-features", test_sys: true }
2626
- { name: "glib", features: "v2_78", nightly: "--all-features", test_sys: true }
27+
- { name: "glib", features: "v2_78,serde", nightly: "--all-features", test_sys: true }
2728
- { name: "graphene", features: "v1_12", nightly: "", test_sys: true }
2829
- { name: "pango", features: "v1_50", nightly: "--all-features", test_sys: true }
2930
- { name: "pangocairo", features: "", nightly: "--all-features", test_sys: true }

.github/workflows/windows.yml

+8-1
Original file line numberDiff line numberDiff line change
@@ -126,7 +126,14 @@ jobs:
126126
with:
127127
command: test
128128
args: --manifest-path ${{ matrix.conf.name }}/sys/Cargo.toml ${{ matrix.conf.args }}
129-
if: matrix.conf.name != 'examples' && matrix.conf.name != 'glib-build-tools'
129+
if: matrix.conf.name != 'examples' && matrix.conf.name != 'glib' && matrix.conf.name != 'glib-build-tools'
130+
131+
- name: test glib-sys
132+
uses: actions-rs/cargo@v1
133+
with:
134+
command: test
135+
args: --manifest-path ${{ matrix.conf.name }}/sys/Cargo.toml --features v2_74
136+
if: matrix.conf.name == 'glib'
130137

131138
- name: build
132139
uses: actions-rs/cargo@v1

glib/Cargo.toml

+9-4
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,7 @@ version = "0.19.0"
99
keywords = ["glib", "gtk-rs", "gnome", "GUI"]
1010
repository = "https://github.com/gtk-rs/gtk-rs-core"
1111
license = "MIT"
12-
exclude = [
13-
"gir-files/*",
14-
]
12+
exclude = ["gir-files/*"]
1513
edition = "2021"
1614
rust-version = "1.70"
1715

@@ -31,16 +29,22 @@ ffi = { package = "glib-sys", path = "sys" }
3129
gobject_ffi = { package = "gobject-sys", path = "gobject-sys" }
3230
glib-macros = { path = "../glib-macros" }
3331
rs-log = { package = "log", version = "0.4", optional = true }
34-
smallvec = { version = "1.11", features = ["union", "const_generics", "const_new"] }
32+
smallvec = { version = "1.11", features = [
33+
"union",
34+
"const_generics",
35+
"const_new",
36+
] }
3537
thiserror = "1"
3638
gio_ffi = { package = "gio-sys", path = "../gio/sys", optional = true }
3739
memchr = "2.7.1"
40+
serde = { version = "1.0", optional = true }
3841

3942
[dev-dependencies]
4043
tempfile = "3"
4144
gir-format-check = "^0.1"
4245
trybuild2 = "1"
4346
criterion = "0.5.1"
47+
serde_json = "1.0"
4448

4549
[features]
4650
default = ["gio"]
@@ -59,6 +63,7 @@ log = ["rs-log"]
5963
log_macros = ["log"]
6064
compiletests = []
6165
gio = ["gio_ffi"]
66+
serde = ["dep:serde"]
6267

6368
[package.metadata.docs.rs]
6469
all-features = true

glib/src/lib.rs

+3
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,9 @@ pub use self::thread_pool::{ThreadHandle, ThreadPool};
227227

228228
pub mod thread_guard;
229229

230+
#[cfg(feature = "serde")]
231+
mod serde;
232+
230233
// rustdoc-stripper-ignore-next
231234
/// This is the log domain used by the [`clone!`][crate::clone!] macro. If you want to use a custom
232235
/// logger (it prints to stdout by default), you can set your own logger using the corresponding

glib/src/serde/byte_array.rs

+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
// Take a look at the license at the top of the repository in the LICENSE file.
2+
3+
use serde::Deserializer;
4+
5+
use super::*;
6+
7+
use crate::ByteArray;
8+
9+
serialize_impl!(ByteArray, Bytes(b) => b);
10+
11+
deserialize_impl! {
12+
ByteArray,
13+
"a sequence of bytes",
14+
Deserializer::deserialize_seq => match impl {
15+
Bytes(b) => Ok(ByteArray::from(b)),
16+
ByteBuf(buf) => Ok(ByteArray::from(buf.as_slice())),
17+
Seq(s) => {
18+
// See https://docs.rs/serde/1.0.159/src/serde/de/impls.rs.html#1038
19+
// and https://docs.rs/serde/1.0.159/src/serde/private/size_hint.rs.html#13
20+
let mut bytes = Vec::with_capacity(min(s.size_hint().unwrap_or(0), 4096));
21+
22+
while let Some(byte) = s.next_element()? {
23+
bytes.push(byte)
24+
}
25+
26+
Ok(ByteArray::from(bytes.as_slice()))
27+
},
28+
}
29+
}
30+
31+
#[cfg(test)]
32+
mod tests {
33+
use crate::{gformat, ByteArray};
34+
35+
#[test]
36+
fn serialization() {
37+
let json = match serde_json::to_value(ByteArray::from(
38+
gformat!("Lorem ipsum dolor sit amet").as_bytes(),
39+
)) {
40+
Ok(v) => Some(v),
41+
Err(_) => None,
42+
};
43+
44+
assert_ne!(json, None);
45+
}
46+
47+
#[test]
48+
fn deserialization() {
49+
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]"#;
50+
51+
serde_json::from_str::<ByteArray>(json_str).unwrap();
52+
}
53+
}

glib/src/serde/bytes.rs

+51
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
// Take a look at the license at the top of the repository in the LICENSE file.
2+
3+
use serde::Deserializer;
4+
5+
use super::*;
6+
7+
use crate::Bytes;
8+
9+
serialize_impl!(Bytes, Bytes(b) => b);
10+
11+
deserialize_impl! {
12+
Bytes,
13+
"a sequence of bytes",
14+
Deserializer::deserialize_seq => match impl {
15+
Bytes(b) => Ok(Bytes::from_owned(b.to_owned())),
16+
ByteBuf(buf) => Ok(Bytes::from_owned(buf)),
17+
Seq(s) => {
18+
let mut bytes = Vec::with_capacity(min(s.size_hint().unwrap_or(0), 4096));
19+
20+
while let Some(byte) = s.next_element()? {
21+
bytes.push(byte)
22+
}
23+
24+
Ok(Bytes::from_owned(bytes))
25+
},
26+
}
27+
}
28+
29+
#[cfg(test)]
30+
mod tests {
31+
use crate::{gformat, Bytes};
32+
33+
#[test]
34+
fn serialization() {
35+
let json = match serde_json::to_value(Bytes::from_owned(
36+
gformat!("Lorem ipsum dolor sit amet").into_bytes(),
37+
)) {
38+
Ok(v) => Some(v),
39+
Err(_) => None,
40+
};
41+
42+
assert_ne!(json, None);
43+
}
44+
45+
#[test]
46+
fn deserialization() {
47+
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]"#;
48+
49+
serde_json::from_str::<Bytes>(json_str).unwrap();
50+
}
51+
}

glib/src/serde/collections.rs

+146
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,146 @@
1+
// Take a look at the license at the top of the repository in the LICENSE file.
2+
3+
use super::*;
4+
5+
use crate::{
6+
translate::{TransparentPtrType, TransparentType},
7+
List, PtrSlice, SList, Slice, StrV,
8+
};
9+
10+
serialize_impl!(Slice<T: TransparentType>, Sequence(iter) => iter);
11+
12+
deserialize_impl! {
13+
Slice<T: TransparentType>,
14+
"a sequence of GLib transparent values",
15+
Deserializer::deserialize_seq => match impl {
16+
Seq(s) => {
17+
let mut slice = Slice::with_capacity(min(s.size_hint().unwrap_or(0), 4096));
18+
19+
while let Some(item) = s.next_element()? {
20+
slice.push(item)
21+
}
22+
23+
Ok(slice)
24+
},
25+
}
26+
}
27+
28+
serialize_impl!(PtrSlice<T: TransparentPtrType>, Sequence(iter) => iter);
29+
30+
deserialize_impl! {
31+
PtrSlice<T: TransparentPtrType>,
32+
"a sequence of GLib transparent pointer values",
33+
Deserializer::deserialize_seq => match impl {
34+
Seq(s) => {
35+
let mut slice = PtrSlice::with_capacity(min(s.size_hint().unwrap_or(0), 4096));
36+
37+
while let Some(item) = s.next_element()? {
38+
slice.push(item)
39+
}
40+
41+
Ok(slice)
42+
},
43+
}
44+
}
45+
46+
serialize_impl!(List<T: TransparentPtrType>, Sequence(iter) => iter.iter());
47+
48+
deserialize_impl! {
49+
List<T: TransparentPtrType>,
50+
"a sequence of GLib transparent pointer values",
51+
Deserializer::deserialize_seq => match impl {
52+
Seq(s) => {
53+
let mut list = List::new();
54+
55+
while let Some(item) = s.next_element()? {
56+
list.push_front(item)
57+
}
58+
list.reverse();
59+
60+
Ok(list)
61+
},
62+
}
63+
}
64+
65+
serialize_impl!(SList<T: TransparentPtrType>, Sequence(iter) => iter.iter());
66+
67+
deserialize_impl! {
68+
SList<T: TransparentPtrType>,
69+
"a sequence of GLib transparent pointer values",
70+
Deserializer::deserialize_seq => match impl {
71+
Seq(s) => {
72+
let mut list = SList::new();
73+
74+
while let Some(item) = s.next_element()? {
75+
list.push_front(item)
76+
}
77+
list.reverse();
78+
79+
Ok(list)
80+
},
81+
}
82+
}
83+
84+
serialize_impl!(StrV, Sequence(iter) => iter);
85+
86+
#[cfg(test)]
87+
mod tests {
88+
use serde_json::json;
89+
90+
use crate::{gformat, Bytes, List, PtrSlice, SList, Slice};
91+
92+
#[test]
93+
fn serialization() {
94+
let bytes = gformat!("Lorem ipsum dolor sit amet").into_bytes();
95+
96+
let slice = Slice::from([
97+
Bytes::from_owned(bytes[..].to_vec()),
98+
Bytes::from_owned(bytes[1..].to_vec()),
99+
Bytes::from_owned(bytes[2..].to_vec()),
100+
Bytes::from_owned(bytes[3..].to_vec()),
101+
]);
102+
103+
let ptr_slice = PtrSlice::from([
104+
Bytes::from_owned(bytes[..].to_vec()),
105+
Bytes::from_owned(bytes[1..].to_vec()),
106+
Bytes::from_owned(bytes[2..].to_vec()),
107+
Bytes::from_owned(bytes[3..].to_vec()),
108+
]);
109+
110+
let mut list = List::<Bytes>::new();
111+
list.push_front(Bytes::from_owned(bytes[..].to_vec()));
112+
list.push_front(Bytes::from_owned(bytes[1..].to_vec()));
113+
list.push_front(Bytes::from_owned(bytes[2..].to_vec()));
114+
list.push_front(Bytes::from_owned(bytes[3..].to_vec()));
115+
list.reverse();
116+
117+
let mut slist = SList::<Bytes>::new();
118+
slist.push_front(Bytes::from_owned(bytes[..].to_vec()));
119+
slist.push_front(Bytes::from_owned(bytes[1..].to_vec()));
120+
slist.push_front(Bytes::from_owned(bytes[2..].to_vec()));
121+
slist.push_front(Bytes::from_owned(bytes[3..].to_vec()));
122+
slist.reverse();
123+
124+
assert_eq!(json!(&slice), json!(&list));
125+
assert_eq!(json!(&slice), json!(&slist));
126+
assert_eq!(json!(&ptr_slice), json!(&list));
127+
assert_eq!(json!(&ptr_slice), json!(&slist));
128+
assert_eq!(json!(&slice), json!(&ptr_slice));
129+
assert_eq!(json!(&list), json!(&slist));
130+
}
131+
132+
#[test]
133+
fn deserialization() {
134+
let json_str = r#"
135+
[
136+
[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],
137+
[111,114,101,109,32,105,112,115,117,109,32,100,111,108,111,114,32,115,105,116,32,97,109,101],
138+
[114,101,109,32,105,112,115,117,109,32,100,111,108,111,114,32,115,105,116,32,97,109,101],
139+
[101,109,32,105,112,115,117,109,32,100,111,108,111,114,32,115,105,116,32,97,109,101]
140+
]"#;
141+
serde_json::from_str::<Slice<Bytes>>(json_str).unwrap();
142+
serde_json::from_str::<PtrSlice<Bytes>>(json_str).unwrap();
143+
serde_json::from_str::<List<Bytes>>(json_str).unwrap();
144+
serde_json::from_str::<SList<Bytes>>(json_str).unwrap();
145+
}
146+
}

glib/src/serde/gstring.rs

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
// Take a look at the license at the top of the repository in the LICENSE file.
2+
3+
use super::*;
4+
5+
use crate::{gformat, GStr, GString, GStringPtr};
6+
use serde::de;
7+
8+
serialize_impl!(GStr, str(s) => s.as_str());
9+
10+
serialize_impl!(GString, str(s) => s.as_str());
11+
12+
deserialize_impl! {
13+
GString,
14+
"a valid UTF-8 string",
15+
Deserializer::deserialize_string => match impl {
16+
str(s) => Ok(gformat!("{s}")),
17+
String(s) => GString::from_string_checked(s).map_err(|e| de::Error::custom(e)),
18+
}
19+
}
20+
21+
serialize_impl!(GStringPtr, str(s) => s.to_str());
22+
23+
#[cfg(test)]
24+
mod tests {
25+
use crate::{translate::ToGlibPtr, GString, StrV};
26+
use serde_json::json;
27+
28+
use crate::gformat;
29+
30+
#[test]
31+
fn serialization() {
32+
let gstring = gformat!("Lorem ipsum dolor sit amet");
33+
let gstr = gstring.as_gstr();
34+
let gstringptr =
35+
&unsafe { StrV::from_glib_none(vec![gstring.to_owned()].to_glib_none().0) }[0];
36+
37+
assert_eq!(json!(&gstring), json!(gstr));
38+
assert_eq!(json!(&gstring), json!(gstringptr));
39+
assert_eq!(json!(gstr), json!(gstringptr));
40+
}
41+
42+
#[test]
43+
fn deserialization() {
44+
let json_str = r#""Lorem ipsum dolor sit amet""#;
45+
46+
serde_json::from_str::<GString>(json_str).unwrap();
47+
}
48+
}

0 commit comments

Comments
 (0)