Skip to content

Add serde support for cxx-qt-lib using proxy types and deriving #1154

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 35 commits into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
15c5917
cxx-qt-lib: deserialize QString from &str rather than String
jnbooth Jan 12, 2025
ffe93e3
cxx-qt-lib: (de)serialize QByteArray as &[u8]
jnbooth Jan 12, 2025
831332d
cxx-qt-lib: (de)serialize QUrl as &str
jnbooth Jan 12, 2025
a70d176
cxx-qt-lib: (de)serialize QDate, QDateTime, and QTime as ISO 8601
jnbooth Jan 12, 2025
2c5153a
cxx-qt-lib: (de)serialize QColor as hex code
jnbooth Jan 12, 2025
ad439e4
cxx-qt-lib: derive Deserialize and Serialize where possible
jnbooth Jan 12, 2025
525e761
cxx-qt-lib: deref QPolygon(F) as QList<QPoint(F)>
jnbooth Jan 12, 2025
b50c61c
cxx-qt-lib: add QSet::reserve
jnbooth Jan 12, 2025
87c5f8d
cxx-qt-lib: (de)serialize lists
jnbooth Jan 12, 2025
fc31d9b
add serde notes to CHANGELOG.md
jnbooth Jan 12, 2025
1b6d208
cxx-qt-lib: fix documentation typo
jnbooth Jan 12, 2025
6f59bea
cxx-qt-lib: use QVector rather than QList for QPolygon(F)
jnbooth Jan 12, 2025
cf2976f
cxx-qt-lib: format qpolygon/f files
jnbooth Jan 12, 2025
205ad64
cxx-qt-lib: fix FFI name
jnbooth Jan 12, 2025
7b37fe1
cxx-qt-lib: fix QColor serialization alpha check
jnbooth Jan 12, 2025
6509fdd
cxx-qt-lib: replace explicit Deref with **self
jnbooth Jan 12, 2025
8c881b7
cxx-qt-lib: move serde_impl from src/core to src
jnbooth Jan 12, 2025
a061037
implement std::fmt::Debug for lists
jnbooth Jan 12, 2025
ea17206
cxx-qt-lib: deserialize QByteArray and QString from bytes
jnbooth Jan 12, 2025
41ff1ae
cxx-qt-lib: better deserialization for QUrl and QColor via QString
jnbooth Jan 12, 2025
9d1afba
cxx-qt-lib: serde unit tests
jnbooth Jan 12, 2025
fd34a21
cxx-qt-lib: use debug_set instead of debug_list for QSet
jnbooth Jan 12, 2025
cae4be5
Merge branch 'main' into serde-containers
jnbooth Jan 13, 2025
1e9a400
cxx-qt-lib: don't implement serde for QDateTime on emscripten
jnbooth Jan 14, 2025
c5ae04e
Merge remote-tracking branch 'origin/main' into serde-containers
jnbooth Jan 14, 2025
d15945d
cxx-qt-lib: (de)serialization for QUuid
jnbooth Jan 14, 2025
636e4e7
cxx-qt-lib: expand CHANGELOG.md
jnbooth Jan 17, 2025
0169819
cxx-qt-lib: set copyright header in serde_impl to 2025
jnbooth Jan 17, 2025
f160d57
cxx-qt-lib: (de)serialize QFont as QString
jnbooth Jan 17, 2025
fd3cc14
cxx-qt-lib: rename to_format to format
jnbooth Jan 17, 2025
cb0f343
cxx-qt-lib: rename format to format_enum
jnbooth Jan 17, 2025
7f79cde
cxx-qt-lib: fix QFont serialization equality matching on Qt5
jnbooth Jan 18, 2025
cb42efa
cxx-qt-lib: use serde(from =, into =)
jnbooth Jan 22, 2025
08eba31
Revert "cxx-qt-lib: use serde(from =, into =)"
jnbooth Feb 14, 2025
8985a10
Merge remote-tracking branch 'origin/main' into serde-containers
jnbooth Mar 6, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -19,7 +19,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- `Deref` and `DerefMut` implementations to `QList` for `QStringList`, `QPolygon`, and `QPolygonF`.
- `QDate::format`, `QDateTime::format`, and `QTime::format` to create a `QString` with a specific `DateFormat`.
- `QDateTime::from_string` to parse `QDateTime` from a `QString`.
- `QSet::reserve` to reserve capacity up-front.
- Support for further types: `QUuid`
- New example: Basic greeter app
- Support for further types: `qreal`, `qint64`, `qintptr`, `qsizetype`, `quint64`, `quintptr`
@@ -30,6 +33,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
- Add pure virtual function specified through the `#[cxx_pure]` attribute
- Add wrappers for up and down casting, for all types which inherit from QObject, available for &T, &mut T and Pin<&mut T>
- Support for `QMessageLogContext` and sending log messages to the Qt message handler.
- Serde support for further types: `QByteArray`, `QColor`, `QDate`, `QDateTime`, `QFont`, `QLine`, `QLineF`, `QList`, `QMargins`, `QMarginsF`, `QPoint`, `QPointF`, `QPolygon`, `QPolygonF`, `QRect`, `QRectF`, `QSet`, `QSize`, `QSizeF`, `QStringList`, `QVector`, `QVector2D`, `QVector3D`, `QVector4D`, `QTime`, `QUrl`, `QUuid`

### Removed

@@ -144,7 +148,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

- Do not use -bundle otherwise CMake builds are missing qt-static-initalizers (note this is broken in rustc 1.69)
- Do not import `Pin` in hidden module as invokables are outside now, resolving IDE integration
- Rust always links against a non-debug Windows runtime with *-msvc targets, so we need to link to MultiThreadedDLL
- Rust always links against a non-debug Windows runtime with \*-msvc targets, so we need to link to MultiThreadedDLL

### Removed

1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 3 additions & 0 deletions crates/cxx-qt-lib/Cargo.toml
Original file line number Diff line number Diff line change
@@ -42,6 +42,9 @@ image-v0-25 = { version = "0.25", optional = true, package = "image", default-fe
cxx-qt-build.workspace = true
qt-build-utils.workspace = true

[dev-dependencies]
serde_json = "1.0.135"

[features]
full = ["qt_full", "serde", "url", "uuid", "time", "rgb", "http", "chrono", "bytes", "image-v0-24", "image-v0-25"]
default = []
13 changes: 13 additions & 0 deletions crates/cxx-qt-lib/include/core/qset/qset_private.h
Original file line number Diff line number Diff line change
@@ -58,6 +58,19 @@ qsetLen(const QSet<T>& s) noexcept
return static_cast<::rust::isize>(s.size());
}

template<typename T>
void
qsetReserve(QSet<T>& s, ::rust::isize size) noexcept
{
Q_ASSERT(size >= 0);
// Qt 5 has an int Qt 6 has a qsizetype
#if (QT_VERSION >= QT_VERSION_CHECK(6, 0, 0))
s.reserve(static_cast<qsizetype>(size));
#else
s.reserve(static_cast<int>(size));
#endif
}

}
}
}
4 changes: 1 addition & 3 deletions crates/cxx-qt-lib/include/core/quuid.h
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@

#include "rust/cxx.h"

using QUuidStringFormat = QUuid::StringFormat;
using QUuidVariant = QUuid::Variant;
using QUuidVersion = QUuid::Version;

@@ -24,9 +25,6 @@ quuidCreateUuid();
QUuid
quuidCreateUuidV5(const QUuid& ns, ::rust::Slice<const ::std::uint8_t> slice);

QString
quuidToString(const QUuid& uuid);

QUuid
quuidFromString(const QString& string);

55 changes: 55 additions & 0 deletions crates/cxx-qt-lib/src/core/qbytearray.rs
Original file line number Diff line number Diff line change
@@ -321,6 +321,54 @@ impl QByteArray {
}
}

#[cfg(feature = "serde")]
impl serde::Serialize for QByteArray {
fn serialize<S: serde::Serializer>(&self, serializer: S) -> Result<S::Ok, S::Error> {
serializer.serialize_bytes(self.as_slice())
}
}

#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for QByteArray {
fn deserialize<D: serde::Deserializer<'de>>(deserializer: D) -> Result<Self, D::Error> {
use serde::de::{Error as DeError, SeqAccess, Visitor};

struct BytesVisitor;

impl<'de> Visitor<'de> for BytesVisitor {
type Value = QByteArray;

fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result {
formatter.write_str("an array of bytes")
}

fn visit_bytes<E: DeError>(self, v: &[u8]) -> Result<Self::Value, E> {
Ok(Self::Value::from(v))
}

fn visit_str<E: DeError>(self, v: &str) -> Result<Self::Value, E> {
Ok(Self::Value::from(v))
}

fn visit_seq<A: SeqAccess<'de>>(self, mut seq: A) -> Result<Self::Value, A::Error> {
let mut values = Self::Value::default();
if let Some(size_hint) = seq.size_hint() {
if size_hint != 0 && size_hint <= isize::MAX as usize {
values.reserve(size_hint as isize);
}
}
while let Some(value) = seq.next_element()? {
values.append(value);
}
Ok(values)
}
}

let visitor = BytesVisitor;
deserializer.deserialize_byte_buf(visitor)
}
}

// Safety:
//
// Static checks on the C++ side to ensure the size is the same.
@@ -334,6 +382,13 @@ mod tests {
#[cfg(feature = "bytes")]
use super::*;

#[cfg(feature = "serde")]
#[test]
fn qbytearray_serde() {
let qbytearray = QByteArray::from("KDAB");
assert_eq!(crate::serde_impl::roundtrip(&qbytearray), qbytearray)
}

#[cfg(feature = "bytes")]
#[test]
fn test_bytes() {
11 changes: 9 additions & 2 deletions crates/cxx-qt-lib/src/core/qdate.rs
Original file line number Diff line number Diff line change
@@ -176,7 +176,7 @@ impl QDate {
Self { jd }
}

/// Returns the QTime represented by the string, using the format given, or None if the string cannot be parsed.
/// Returns the QDate represented by the string, using the format given, or None if the string cannot be parsed.
pub fn from_string(string: &ffi::QString, format: &ffi::QString) -> Option<Self> {
let date = ffi::qdate_from_string(string, format);
if date.is_valid() {
@@ -186,7 +186,7 @@ impl QDate {
}
}

/// Returns the time represented in the string as a QTime using the format given, or None if this is not possible.
/// Returns the time represented in the string as a QDate using the format given, or None if this is not possible.
pub fn from_string_enum(string: &ffi::QString, format: ffi::DateFormat) -> Option<Self> {
let date = ffi::qdate_from_string_enum(string, format);
if date.is_valid() {
@@ -290,6 +290,13 @@ mod test {
assert_eq!(QDate::from(naive), qdate);
}

#[cfg(feature = "serde")]
#[test]
fn qdate_serde() {
let qdate = QDate::new(2023, 1, 1);
assert_eq!(crate::serde_impl::roundtrip(&qdate), qdate);
}

#[cfg(feature = "chrono")]
#[test]
fn qdate_to_chrono_naive() {
12 changes: 12 additions & 0 deletions crates/cxx-qt-lib/src/core/qdatetime.rs
Original file line number Diff line number Diff line change
@@ -195,6 +195,7 @@ mod ffi {
fn qdatetimeTimeZone(datetime: &QDateTime) -> UniquePtr<QTimeZone>;
#[rust_name = "qdatetime_settimezone"]
fn qdatetimeSetTimeZone(datetime: &mut QDateTime, time_zone: &QTimeZone);
#[doc(hidden)]
#[rust_name = "qdatetime_from_string"]
fn qdatetimeFromQString(string: &QString, format: DateFormat) -> QDateTime;
}
@@ -514,6 +515,17 @@ mod test {
assert_eq!(qdatetime_b.cmp(&qdatetime_a), Ordering::Greater);
assert_eq!(qdatetime_a.cmp(&qdatetime_a), Ordering::Equal);
}

#[cfg(feature = "serde")]
#[test]
fn qdatetime_serde() {
let qdatetime = QDateTime::from_date_and_time_time_zone(
&QDate::new(2023, 1, 1),
&QTime::new(1, 1, 1, 1),
&ffi::QTimeZone::utc(),
);
assert_eq!(crate::serde_impl::roundtrip(&qdatetime), qdatetime);
}
}

#[cfg(test)]
4 changes: 4 additions & 0 deletions crates/cxx-qt-lib/src/core/qline.rs
Original file line number Diff line number Diff line change
@@ -98,8 +98,12 @@ mod ffi {
}
}

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

/// The QLine class provides a two-dimensional vector using integer precision
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(C)]
pub struct QLine {
pt1: QPoint,
4 changes: 4 additions & 0 deletions crates/cxx-qt-lib/src/core/qlinef.rs
Original file line number Diff line number Diff line change
@@ -131,8 +131,12 @@ mod ffi {
}
}

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

/// The QLineF class provides a two-dimensional vector using floating point precision.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(C)]
pub struct QLineF {
pt1: QPointF,
7 changes: 7 additions & 0 deletions crates/cxx-qt-lib/src/core/qlist/mod.rs
Original file line number Diff line number Diff line change
@@ -400,4 +400,11 @@ mod test {
let qlist = QList::<u8>::from(array);
assert_eq!(Vec::from(&qlist), array);
}

#[cfg(feature = "serde")]
#[test]
fn qlist_serde() {
let qlist = QList::<u8>::from([0, 1, 2]);
assert_eq!(crate::serde_impl::roundtrip(&qlist), qlist);
}
}
4 changes: 4 additions & 0 deletions crates/cxx-qt-lib/src/core/qmargins.rs
Original file line number Diff line number Diff line change
@@ -96,8 +96,12 @@ mod ffi {
}
}

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

/// The QMargins class defines the four margins of a rectangle.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(C)]
pub struct QMargins {
left: i32,
4 changes: 4 additions & 0 deletions crates/cxx-qt-lib/src/core/qmarginsf.rs
Original file line number Diff line number Diff line change
@@ -92,8 +92,12 @@ mod ffi {
}
}

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

/// The QMarginsF class defines the four margins of a rectangle.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(C)]
pub struct QMarginsF {
left: f64,
4 changes: 4 additions & 0 deletions crates/cxx-qt-lib/src/core/qpoint.rs
Original file line number Diff line number Diff line change
@@ -93,8 +93,12 @@ mod ffi {
}
}

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

/// The QPoint struct defines a point in the plane using integer precision.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(C)]
pub struct QPoint {
x: i32,
3 changes: 3 additions & 0 deletions crates/cxx-qt-lib/src/core/qpointf.rs
Original file line number Diff line number Diff line change
@@ -86,9 +86,12 @@ mod ffi {
fn operatorDiv(a: f64, b: &QPointF) -> QPointF;
}
}
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

/// The QPointF struct defines a point in the plane using floating point precision.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(C)]
pub struct QPointF {
x: f64,
4 changes: 4 additions & 0 deletions crates/cxx-qt-lib/src/core/qrect.rs
Original file line number Diff line number Diff line change
@@ -268,8 +268,12 @@ mod ffi {
}
}

#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

/// The QRect struct defines a rectangle in the plane using integer precision.
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(C)]
pub struct QRect {
// Note that Qt stores QRect as two points rather than a point and size (which QRectF is)
3 changes: 3 additions & 0 deletions crates/cxx-qt-lib/src/core/qrectf.rs
Original file line number Diff line number Diff line change
@@ -269,9 +269,12 @@ mod ffi {
fn operatorMinus(a: &QRectF, b: &QMarginsF) -> QRectF;
}
}
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};

/// The QRectF struct defines a rectangle in the plane using floating point precision.
#[derive(Debug, Clone, PartialEq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
#[repr(C)]
pub struct QRectF {
xp: f64,
12 changes: 12 additions & 0 deletions crates/cxx-qt-lib/src/core/qset/generate.sh
Original file line number Diff line number Diff line change
@@ -72,6 +72,8 @@ pub mod ffi {
fn qsetInsert(_: &mut QSet_$1, _: &$1);
#[rust_name = "len_$1"]
fn qsetLen(_: &QSet_$1) -> isize;
#[rust_name = "reserve_$1"]
fn qsetReserve(_: &mut QSet_$1, size: isize);
}
}
@@ -98,6 +100,10 @@ pub(crate) fn insert(s: &mut ffi::QSet_$1, value: &$1) {
pub(crate) fn len(s: &ffi::QSet_$1) -> isize {
ffi::len_$1(s)
}
pub(crate) fn reserve(s: &mut ffi::QSet_$1, size: isize) {
ffi::reserve_$1(s, size);
}
EOF
rustfmt "$SCRIPTPATH/qset_$1.rs"
}
@@ -148,6 +154,8 @@ pub mod ffi {
fn qsetInsert(_: &mut QSet_$1, _: &$1);
#[rust_name = "len_$1"]
fn qsetLen(_: &QSet_$1) -> isize;
#[rust_name = "reserve_$1"]
fn qsetReserve(_: &mut QSet_$1, size: isize);
}
}
@@ -174,6 +182,10 @@ pub(crate) fn insert(s: &mut ffi::QSet_$1, value: &ffi::$1) {
pub(crate) fn len(s: &ffi::QSet_$1) -> isize {
ffi::len_$1(s)
}
pub(crate) fn reserve(s: &mut ffi::QSet_$1, size: isize) {
ffi::reserve_$1(s, size);
}
EOF
rustfmt "$SCRIPTPATH/qset_$2.rs"
}
Loading