Skip to content
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

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

Open
wants to merge 33 commits into
base: main
Choose a base branch
from

Conversation

jnbooth
Copy link
Contributor

@jnbooth jnbooth commented Jan 12, 2025

Per #943, adds serialization and deserialization for a number of types, using various strategies.

Using #[derive(Serialize, Deserialize)]:

  • QLine / QLineF
  • QMargins / QMarginsF
  • QPoint / QPointF
  • QRect / QRectF
  • QSize / QSizeF
  • QVector2D / QVector3D / QVector4D

Proxied to QString:

  • QDate, QDateTime, and QTime (ISO-8601)
  • QFont
  • QUrl
  • QUuid
  • QColor (hex encoding)

Using a macro for serializing and deserializing Qt sequential containers:

  • QList
  • QPolygon / QPolygonF
  • QSet
  • QStringList
  • QVector

The goal of this PR is to avoid handwritten serde logic as much as possible in order to minimize maintenance cost. #[derive(Serialize, Deserialize)] does expose field names, but that is intended behavior. (If it's a problem, we can use #[serde(rename)].) QString proxies are used for QColor, QFont, QUuid, QUrl, and date/time types because a) it offloads ser/de logic to Qt, and b) the chrono and uuid crates use string proxies for ser/de anyway, so if we used them as proxies, it'd have the same effect. Ser/de has not been added for QHash/QMap because they currently use QVariant values, which aren't serializable except by turning them into non-human-readable byte arrays. If we add more QHashPair and QMapPair specializations, that could be worth revisiting.

In order to write round-trip unit tests, there needs to be a dev-dependency for a serde format. Currently the unit tests use serde_json.

Note: This PR also adds the missingQSet::reserve(&mut self, size: isize) function in order to reserve capacity before deserialization. That's where most of the changed files are coming from. It also adds a few missing Debug implementations so that assert_eq! works with those types.

Closes #943

Copy link

codecov bot commented Jan 12, 2025

Codecov Report

All modified and coverable lines are covered by tests ✅

Project coverage is 100.00%. Comparing base (e84662a) to head (cb42efa).
Report is 2 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff            @@
##              main     #1154   +/-   ##
=========================================
  Coverage   100.00%   100.00%           
=========================================
  Files           71        71           
  Lines        11967     11967           
=========================================
  Hits         11967     11967           

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

Copy link
Contributor

@redstrate redstrate left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

IMO it would be nice to have some of these useful doc and other additions unrelated to serde now, but I'll leave that up to Andrew & Leon if they want that separated :)

CHANGELOG.md Outdated Show resolved Hide resolved
crates/cxx-qt-lib/src/serde_impl.rs Outdated Show resolved Hide resolved
@redstrate
Copy link
Contributor

This should fix #943 right?

@jnbooth
Copy link
Contributor Author

jnbooth commented Jan 17, 2025

@redstrate I believe so.

Copy link
Collaborator

@LeonMatthesKDAB LeonMatthesKDAB left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hi @jnbooth , thank you for the contribution!

I haven't yet had the time to give this PR a full review, as it's quite large.

However, what I noticed is that there are more manual Deserialize/Serialize implementations than I would like to support in the future.
It is good that some of these already proxy through e.g. QString, however it would be best if we can use serdes from and into attributes for this, as we then can get rid of a lot of boilerplate.

See also: https://serde.rs/container-attrs.html#from

We may even want to implement e.g. From and Into for Vec<>, Hashmap<>, etc. to get rid of the manual container implementations.

crates/cxx-qt-lib/src/core/qstring.rs Show resolved Hide resolved
Comment on lines +219 to +226
#[cfg(feature = "serde")]
#[test]
fn qstringlist_serde() {
let mut qstringlist = QStringList::default();
qstringlist.append(QString::from("element 1"));
qstringlist.append(QString::from("element 2"));
assert_eq!(crate::serde_impl::roundtrip(&qstringlist), qstringlist)
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm confused, I can't find the Serialize/Deserialize impl for QStringList. Where is this implemented?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's implemented using the containers macro in serde_impl.rs.

Comment on lines +143 to +151
/// Returns the QTime represented by the string, using the format given, or None if the string cannot be parsed.
pub fn from_string_opt(string: &ffi::QString, format: &ffi::QString) -> Option<Self> {
let time = ffi::qtime_from_string(string, format);
if time.is_valid() {
Some(time)
} else {
None
}
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, it seems we deviated from our usual best-practice with the previous from_string impl.
Usually we try to adhere to Rust best practice first and expose the Qt-style version of the function with a suffix.

So in this case from_string would return an Option<Self> and from_string_or_default would return Self.

I'm unsure how we should go about it in this case, as changing the original behavior would be a breaking change.
Any ideas @ahayzen-kdab ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

(Side note: I've noticed in libraries such as chrono that if a function returns Option<Self> rather than Self, they add _opt to the name.)

crates/cxx-qt-lib/src/core/qurl.rs Outdated Show resolved Hide resolved
crates/cxx-qt-lib/src/serde_impl.rs Show resolved Hide resolved
@jnbooth
Copy link
Contributor Author

jnbooth commented Jan 22, 2025

@LeonMatthesKDAB Assuming this is what you wanted me to do, I've replaced handwritten serde implementations with the following new implementations:

  • From<&QFont> for QString + From<QFont> for QString
  • TryFrom<&QString> for QFont + TryFrom<QString> for QFont
  • From<QString> for QUrl
  • From<&QUrl> for QString + From<QUrl> for QString
  • From<QString> for QUuid
  • From<QString> for QColor
  • From<&QColor> for QString + From<QColor> for QString
  • From<&QByteArray> for Vec<u8> + From<QByteArray> for Vec<u8>
  • From<Vec<u8>> for QByteArray
  • TryFrom<&QString> for QDate + TryFrom<QString> for QDate
  • From<&QDate> for QString + From<QDate> for QString
  • TryFrom<&QString> for QDateTime + TryFrom<QString> for QDateTime
  • From<&QDateTime> for QString + From<QDateTime> for QString
  • TryFrom<&QString> for QTime + TryFrom<QString> for QTime
  • From<&QTime> for QString + From<QTime> for QString

However, I think there are some drawbacks to this approach that might make it worth reverting.

  1. That's a lot of new trait implementations, and all of the by-value ones are unnecessary except for the sake of serde attributes. Without serde's from and into, there doesn't need to be From<QString> for QUrl because there's already From<&QString>.
  2. All those implementations are tightly coupled to (de)serialization. If someone submits a pull request that changes any of those implementations, it will break deserialization. That's a lot of code fragility. (Fortunately, the serde unit tests will catch those issues, but still.)
  3. Since serde's from and into attributes use .clone(), every call to serialization now starts with .clone(). It's especially bad in the cases of QString and Vec<u8>, which now allocate unnecessary strings and vectors. And since QString is used as the proxy for all the above types, that means using into = "String" instead of a handwritten serialization function for QString will result in unnecessarily allocating a string for every type.
  4. This isn't less code to maintain. It's more.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add serde support for cxx-qt-lib using #[serde(into=...,from=...)]
3 participants