From 41be9db7af4b2c2bd81e57a7d33aef563d8ea645 Mon Sep 17 00:00:00 2001 From: Alexander Kiselev Date: Wed, 18 Dec 2024 20:18:59 -0800 Subject: [PATCH] Add QJSValue, QJSEngine, and related types to cxx-qt-lib-extras - Implement QJSValue with serialization and deserialization support - Add QJSEngine with methods for creating and manipulating JavaScript values - Implement QJSValueIterator for iterating over object properties - Create QJSValueList for managing lists of QJSValues - Add serializer and deserializer for converting between Rust types and QJSValues - Update Cargo.toml to include serde as an optional dependency - Extend build script to include new QML-related headers and source files --- crates/cxx-qt-lib-extras/Cargo.toml | 4 +- crates/cxx-qt-lib-extras/build.rs | 9 + .../cxx-qt-lib-extras/include/qml/qjsengine.h | 81 ++ .../cxx-qt-lib-extras/include/qml/qjsvalue.h | 36 + .../include/qml/qjsvalueiterator.h | 15 + .../include/qml/qjsvaluelist.h | 32 + crates/cxx-qt-lib-extras/src/lib.rs | 3 + .../cxx-qt-lib-extras/src/qml/deserializer.rs | 807 ++++++++++++++++++ crates/cxx-qt-lib-extras/src/qml/mod.rs | 21 + .../cxx-qt-lib-extras/src/qml/qjsengine.cpp | 18 + crates/cxx-qt-lib-extras/src/qml/qjsengine.rs | 110 +++ crates/cxx-qt-lib-extras/src/qml/qjsvalue.cpp | 124 +++ crates/cxx-qt-lib-extras/src/qml/qjsvalue.rs | 209 +++++ .../src/qml/qjsvalueiterator.cpp | 25 + .../src/qml/qjsvalueiterator.rs | 54 ++ .../src/qml/qjsvaluelist.cpp | 26 + .../cxx-qt-lib-extras/src/qml/qjsvaluelist.rs | 39 + .../cxx-qt-lib-extras/src/qml/serializer.rs | 613 +++++++++++++ 18 files changed, 2225 insertions(+), 1 deletion(-) create mode 100644 crates/cxx-qt-lib-extras/include/qml/qjsengine.h create mode 100644 crates/cxx-qt-lib-extras/include/qml/qjsvalue.h create mode 100644 crates/cxx-qt-lib-extras/include/qml/qjsvalueiterator.h create mode 100644 crates/cxx-qt-lib-extras/include/qml/qjsvaluelist.h create mode 100644 crates/cxx-qt-lib-extras/src/qml/deserializer.rs create mode 100644 crates/cxx-qt-lib-extras/src/qml/mod.rs create mode 100644 crates/cxx-qt-lib-extras/src/qml/qjsengine.cpp create mode 100644 crates/cxx-qt-lib-extras/src/qml/qjsengine.rs create mode 100644 crates/cxx-qt-lib-extras/src/qml/qjsvalue.cpp create mode 100644 crates/cxx-qt-lib-extras/src/qml/qjsvalue.rs create mode 100644 crates/cxx-qt-lib-extras/src/qml/qjsvalueiterator.cpp create mode 100644 crates/cxx-qt-lib-extras/src/qml/qjsvalueiterator.rs create mode 100644 crates/cxx-qt-lib-extras/src/qml/qjsvaluelist.cpp create mode 100644 crates/cxx-qt-lib-extras/src/qml/qjsvaluelist.rs create mode 100644 crates/cxx-qt-lib-extras/src/qml/serializer.rs diff --git a/crates/cxx-qt-lib-extras/Cargo.toml b/crates/cxx-qt-lib-extras/Cargo.toml index 3319c80af..df938c0f2 100644 --- a/crates/cxx-qt-lib-extras/Cargo.toml +++ b/crates/cxx-qt-lib-extras/Cargo.toml @@ -17,12 +17,14 @@ rust-version.workspace = true cxx.workspace = true cxx-qt.workspace = true cxx-qt-lib = { workspace = true, features = ["qt_full"] } +serde = { version = "1", features=["derive"], optional = true } [build-dependencies] cxx-qt-build.workspace = true [features] -default = [] +default = ["serde"] +serde = ["dep:serde"] link_qt_object_files = ["cxx-qt-build/link_qt_object_files"] [lints] diff --git a/crates/cxx-qt-lib-extras/build.rs b/crates/cxx-qt-lib-extras/build.rs index b6beb8cec..144af24fd 100644 --- a/crates/cxx-qt-lib-extras/build.rs +++ b/crates/cxx-qt-lib-extras/build.rs @@ -37,6 +37,7 @@ fn write_headers() { write_headers_in("core"); write_headers_in("gui"); + write_headers_in("qml"); } fn main() { @@ -57,6 +58,10 @@ fn main() { "core/qcommandlineoption", "core/qcommandlineparser", "gui/qapplication", + "qml/qjsengine", + "qml/qjsvalue", + "qml/qjsvalueiterator", + "qml/qjsvaluelist", ]; for rust_source in &rust_bridges { @@ -68,6 +73,10 @@ fn main() { "core/qcommandlineoption", "core/qcommandlineparser", "gui/qapplication", + "qml/qjsengine", + "qml/qjsvalue", + "qml/qjsvalueiterator", + "qml/qjsvaluelist", ]; builder = builder.cc_builder(move |cc| { diff --git a/crates/cxx-qt-lib-extras/include/qml/qjsengine.h b/crates/cxx-qt-lib-extras/include/qml/qjsengine.h new file mode 100644 index 000000000..965aded52 --- /dev/null +++ b/crates/cxx-qt-lib-extras/include/qml/qjsengine.h @@ -0,0 +1,81 @@ +#pragma once + +#include + +#include + +#include "rust/cxx.h" + +namespace rust +{ + namespace cxxqtlib2 + { + ::std::unique_ptr + qjsengineNew(); + + template + ::std::unique_ptr + jsengineNewArray(T &engine, quint32 length) + { + auto ptr = ::std::make_unique(engine.newArray(length)); + Q_ASSERT(ptr != nullptr); + + return ptr; + } + + template + ::std::unique_ptr + jsengineNewObject(T &engine) + { + auto ptr = ::std::make_unique(engine.newObject()); + Q_ASSERT(ptr != nullptr); + + return ptr; + } + + template + ::std::unique_ptr + jsengineNewQObject(T &engine, QObject &object) + { + auto ptr = ::std::make_unique(engine.newQObject(&object)); + Q_ASSERT(ptr != nullptr); + + return ptr; + } + + template + ::std::unique_ptr + jsengineEvaluate( + T &engine, + const QString &program, + const QString &fileName, + int lineNumber) + { + auto ptr = ::std::make_unique( + engine.evaluate(program, fileName, lineNumber, nullptr)); + Q_ASSERT(ptr != nullptr); + + return ptr; + } + + template + ::std::unique_ptr + jsengineImportModule(T &engine, const QString &fileName) + { + auto ptr = ::std::make_unique(engine.importModule(fileName)); + Q_ASSERT(ptr != nullptr); + + return ptr; + } + + template + ::std::unique_ptr + jsengineGlobalObject(T &engine) + { + auto ptr = ::std::make_unique(engine.globalObject()); + Q_ASSERT(ptr != nullptr); + + return ptr; + } + } +} diff --git a/crates/cxx-qt-lib-extras/include/qml/qjsvalue.h b/crates/cxx-qt-lib-extras/include/qml/qjsvalue.h new file mode 100644 index 000000000..7c5b0a706 --- /dev/null +++ b/crates/cxx-qt-lib-extras/include/qml/qjsvalue.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +namespace rust +{ + namespace cxxqtlib2 + { + + ::std::unique_ptr qjsvalue_new(); + ::std::unique_ptr qjsvalue_new_null(); + ::std::unique_ptr qjsvalue_new_bool(bool value); + ::std::unique_ptr qjsvalue_new_int(int value); + ::std::unique_ptr qjsvalue_new_uint(uint value); + ::std::unique_ptr qjsvalue_new_double(double value); + ::std::unique_ptr qjsvalue_new_qstring(const QString &value); + + ::std::unique_ptr qjsvalue_from_jsvalue(const QJSValue &value); + + QString qjsvalue_to_string(const QJSValue &value); + + ::std::unique_ptr qjsvalue_property( + const QJSValue &value, + const QString &name); + ::std::unique_ptr qjsvalue_element( + const QJSValue &value, + quint32 index); + + QVariant qjsvalue_to_qvariant(const QJSValue &value); + QObject *qjsvalue_to_qobject(QJSValue &value); + + bool qvariantCanConvertQJSValue(const QVariant &variant); + ::std::unique_ptr qjsvalueFromQVariant(const QVariant &variant) noexcept; + } +} diff --git a/crates/cxx-qt-lib-extras/include/qml/qjsvalueiterator.h b/crates/cxx-qt-lib-extras/include/qml/qjsvalueiterator.h new file mode 100644 index 000000000..aa29618b8 --- /dev/null +++ b/crates/cxx-qt-lib-extras/include/qml/qjsvalueiterator.h @@ -0,0 +1,15 @@ +#pragma once + +#include +#include + +namespace rust +{ + namespace cxxqtlib2 + { + + ::std::unique_ptr qjsvalueiterator_new(const QJSValue &value); + + ::std::unique_ptr qjsvalueiterator_value(const QJSValueIterator &value); + } +} diff --git a/crates/cxx-qt-lib-extras/include/qml/qjsvaluelist.h b/crates/cxx-qt-lib-extras/include/qml/qjsvaluelist.h new file mode 100644 index 000000000..cb5bcc5fa --- /dev/null +++ b/crates/cxx-qt-lib-extras/include/qml/qjsvaluelist.h @@ -0,0 +1,32 @@ +#pragma once + +#include +#include + +// Define a proper operator== for QJSValue +#ifndef QJSVALUE_OPERATOR_EQ_DEFINED +#define QJSVALUE_OPERATOR_EQ_DEFINED + +inline bool operator==(const QJSValue& lhs, const QJSValue& rhs) +{ + return lhs.strictlyEquals(rhs); // Example using strictlyEquals method +} + +#endif // QJSVALUE_OPERATOR_EQ_DEFINED + +namespace rust +{ + namespace cxxqtlib2 + { + + class QJSValueList : public QList + { + public: + QJSValueList() : QList() {} + ~QJSValueList() {} + }; + + ::std::unique_ptr qjsvaluelistNew(); + ::std::unique_ptr qjsvaluelistClone(const QJSValueList &list); + } +} \ No newline at end of file diff --git a/crates/cxx-qt-lib-extras/src/lib.rs b/crates/cxx-qt-lib-extras/src/lib.rs index c0398df20..e0f26b406 100644 --- a/crates/cxx-qt-lib-extras/src/lib.rs +++ b/crates/cxx-qt-lib-extras/src/lib.rs @@ -11,3 +11,6 @@ pub use crate::core::*; mod gui; pub use crate::gui::*; + +mod qml; +pub use crate::qml::*; \ No newline at end of file diff --git a/crates/cxx-qt-lib-extras/src/qml/deserializer.rs b/crates/cxx-qt-lib-extras/src/qml/deserializer.rs new file mode 100644 index 000000000..22f618d18 --- /dev/null +++ b/crates/cxx-qt-lib-extras/src/qml/deserializer.rs @@ -0,0 +1,807 @@ +use core::pin::Pin; + +use serde::{ + de::{ + self, DeserializeSeed, EnumAccess, MapAccess, SeqAccess, Unexpected, VariantAccess, Visitor, + }, + Deserializer, +}; + +use cxx_qt_lib::QString; + +use crate::qml::{QJSEngine, QJSValue, QJSValueIterator}; + +pub struct JSEngineDeserializer<'a> { + value: &'a QJSValue, +} + +impl<'a> JSEngineDeserializer<'a> { + pub fn new(value: &'a QJSValue) -> Self { + Self { value } + } +} + +impl<'de, 'a> de::Deserializer<'de> for JSEngineDeserializer<'a> { + type Error = serde_json::Error; + + fn deserialize_any(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.value.is_bool() { + visitor.visit_bool(self.value.to_bool()) + } else if self.value.is_number() { + visitor.visit_f64(self.value.to_f64()) + } else if self.value.is_string() { + let s = self.value.to_qstring(); + visitor.visit_string(s.to_string()) + } else if self.value.is_array() { + self.deserialize_seq(visitor) + } else if self.value.is_object() { + self.deserialize_map(visitor) + } else if self.value.is_null() { + visitor.visit_unit() + } else { + Err(de::Error::invalid_type( + Unexpected::Other("unsupported type"), + &visitor, + )) + } + } + + fn deserialize_bool(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.value.is_bool() { + visitor.visit_bool(self.value.to_bool()) + } else { + Err(de::Error::invalid_type( + Unexpected::Other("not a bool"), + &visitor, + )) + } + } + + fn deserialize_i8(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.value.is_number() { + visitor.visit_i8(self.value.to_int() as i8) + } else { + Err(de::Error::invalid_type( + Unexpected::Other("not an i8"), + &visitor, + )) + } + } + + fn deserialize_i16(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.value.is_number() { + visitor.visit_i16(self.value.to_int() as i16) + } else { + Err(de::Error::invalid_type( + Unexpected::Other("not an i16"), + &visitor, + )) + } + } + + fn deserialize_i32(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.value.is_number() { + visitor.visit_i32(self.value.to_int()) + } else { + Err(de::Error::invalid_type( + Unexpected::Other("not an i32"), + &visitor, + )) + } + } + + fn deserialize_i64(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.value.is_number() { + visitor.visit_i64(self.value.to_int() as i64) + } else { + Err(de::Error::invalid_type( + Unexpected::Other("not an i64"), + &visitor, + )) + } + } + + fn deserialize_u8(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.value.is_number() { + visitor.visit_u8(self.value.to_uint() as u8) + } else { + Err(de::Error::invalid_type( + Unexpected::Other("not a u8"), + &visitor, + )) + } + } + + fn deserialize_u16(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.value.is_number() { + visitor.visit_u16(self.value.to_uint() as u16) + } else { + Err(de::Error::invalid_type( + Unexpected::Other("not a u16"), + &visitor, + )) + } + } + + fn deserialize_u32(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.value.is_number() { + visitor.visit_u32(self.value.to_uint()) + } else { + Err(de::Error::invalid_type( + Unexpected::Other("not a u32"), + &visitor, + )) + } + } + + fn deserialize_u64(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.value.is_number() { + visitor.visit_u64(self.value.to_uint() as u64) + } else { + Err(de::Error::invalid_type( + Unexpected::Other("not a u64"), + &visitor, + )) + } + } + + fn deserialize_f32(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.value.is_number() { + visitor.visit_f32(self.value.to_f64() as f32) + } else { + Err(de::Error::invalid_type( + Unexpected::Other("not a f32"), + &visitor, + )) + } + } + + fn deserialize_f64(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.value.is_number() { + visitor.visit_f64(self.value.to_f64()) + } else { + Err(de::Error::invalid_type( + Unexpected::Other("not a f64"), + &visitor, + )) + } + } + + fn deserialize_char(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.value.is_string() { + let s = self.value.to_qstring(); + let s = s.to_string(); + let mut chars = s.chars(); + if let Some(c) = chars.next() { + if chars.next().is_none() { + return visitor.visit_char(c); + } + } + Err(de::Error::invalid_type( + Unexpected::Str(&s.to_string()), + &visitor, + )) + } else { + Err(de::Error::invalid_type( + Unexpected::Other("not a char"), + &visitor, + )) + } + } + + fn deserialize_str(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.value.is_string() { + visitor.visit_str(&self.value.to_qstring().to_string()) + } else { + Err(de::Error::invalid_type( + Unexpected::Other("not a string"), + &visitor, + )) + } + } + + fn deserialize_string(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.value.is_string() { + visitor.visit_string(self.value.to_qstring().to_string()) + } else { + Err(de::Error::invalid_type( + Unexpected::Other("not a string"), + &visitor, + )) + } + } + + fn deserialize_bytes(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.value.is_array() { + let len = self.value.get_property(&QString::from("length")).to_uint() as usize; + let mut vec = Vec::with_capacity(len); + for i in 0..len { + let elem = self.value.get_element(i as u32); + if elem.is_number() { + vec.push(elem.to_uint() as u8); + } else { + return Err(de::Error::invalid_type( + Unexpected::Other("not a byte array"), + &visitor, + )); + } + } + visitor.visit_bytes(&vec) + } else { + Err(de::Error::invalid_type( + Unexpected::Other("not bytes"), + &visitor, + )) + } + } + + fn deserialize_byte_buf(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.value.is_array() { + let len = self.value.get_property(&QString::from("length")).to_uint() as usize; + let mut vec = Vec::with_capacity(len); + for i in 0..len { + let elem = self.value.get_element(i as u32); + if elem.is_number() { + vec.push(elem.to_uint() as u8); + } else { + return Err(de::Error::invalid_type( + Unexpected::Other("not a byte array"), + &visitor, + )); + } + } + visitor.visit_byte_buf(vec) + } else { + Err(de::Error::invalid_type( + Unexpected::Other("not bytes"), + &visitor, + )) + } + } + + fn deserialize_option(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.value.is_null() { + visitor.visit_none() + } else { + visitor.visit_some(self) + } + } + + fn deserialize_unit(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.value.is_null() { + visitor.visit_unit() + } else { + Err(de::Error::invalid_type( + Unexpected::Other("not a unit"), + &visitor, + )) + } + } + + fn deserialize_unit_struct( + self, + _name: &'static str, + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + self.deserialize_unit(visitor) + } + + fn deserialize_newtype_struct( + self, + _name: &'static str, + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + visitor.visit_newtype_struct(self) + } + + fn deserialize_seq(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.value.is_array() { + let len = self.value.get_property(&QString::from("length")).to_uint() as usize; + let seq = QJSSeqAccess { + array: self.value, + index: 0, + len, + }; + visitor.visit_seq(seq) + } else { + Err(de::Error::invalid_type( + Unexpected::Other("not a sequence"), + &visitor, + )) + } + } + + fn deserialize_tuple(self, _len: usize, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_seq(visitor) + } + + fn deserialize_tuple_struct( + self, + _name: &'static str, + _len: usize, + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + self.deserialize_seq(visitor) + } + + fn deserialize_map(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + if self.value.is_object() { + let keys = QJSValueIterator::new(self.value); + let map = QJSMapAccess { + object: self.value, + keys, + }; + visitor.visit_map(map) + } else { + Err(de::Error::invalid_type( + Unexpected::Other("not a map"), + &visitor, + )) + } + } + + fn deserialize_struct( + self, + _name: &'static str, + _fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + self.deserialize_map(visitor) + } + + fn deserialize_enum( + self, + _name: &'static str, + _variants: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + if self.value.is_string() { + visitor.visit_enum(QJSVariantAccess { + variant: self.value.to_qstring().to_string(), + value: self.value.clone(), + }) + } else if self.value.is_object() { + let mut keys = QJSValueIterator::new(self.value); + if keys.has_next() { + keys.as_mut().unwrap().next(); + let key = keys.name(); + let value = self.value.get_property(&key); + visitor.visit_enum(QJSVariantAccess { + variant: key.to_string(), + value, + }) + } else { + Err(de::Error::invalid_type( + Unexpected::Other("not a single key object"), + &visitor, + )) + } + } else { + Err(de::Error::invalid_type( + Unexpected::Other("not an enum"), + &visitor, + )) + } + } + + fn deserialize_identifier(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + self.deserialize_str(visitor) + } + + fn deserialize_ignored_any(self, visitor: V) -> Result + where + V: Visitor<'de>, + { + visitor.visit_unit() + } +} + +struct QJSSeqAccess<'a> { + array: &'a QJSValue, + index: usize, + len: usize, +} + +impl<'de, 'a> SeqAccess<'de> for QJSSeqAccess<'a> { + type Error = serde_json::Error; + + fn next_element_seed(&mut self, seed: T) -> Result, Self::Error> + where + T: DeserializeSeed<'de>, + { + if self.index >= self.len { + return Ok(None); + } + let elem = self.array.get_element(self.index as u32); + self.index += 1; + let de = JSEngineDeserializer::new(&elem); + seed.deserialize(de).map(Some) + } +} + +struct QJSMapAccess<'a> { + object: &'a QJSValue, + keys: cxx::UniquePtr, +} + +impl<'de, 'a> MapAccess<'de> for QJSMapAccess<'a> { + type Error = serde_json::Error; + + fn next_key_seed(&mut self, seed: K) -> Result, Self::Error> + where + K: DeserializeSeed<'de>, + { + if !self.keys.has_next() { + return Ok(None); + } + self.keys.as_mut().unwrap().next(); + let key = self.keys.name(); + let key_str = key.to_string(); + let value = QJSValue::from_str(&key_str); + let de = JSEngineDeserializer::new(&value); + seed.deserialize(de).map(Some) + } + + fn next_value_seed(&mut self, seed: V) -> Result + where + V: DeserializeSeed<'de>, + { + let key = self.keys.name(); + let value = self.object.get_property(&key); + let de = JSEngineDeserializer::new(&value); + seed.deserialize(de) + } +} + +struct QJSVariantAccess { + variant: String, + value: cxx::UniquePtr, +} + +impl<'de> EnumAccess<'de> for QJSVariantAccess { + type Error = serde_json::Error; + type Variant = Self; + + fn variant_seed(mut self, seed: V) -> Result<(V::Value, Self::Variant), Self::Error> + where + V: DeserializeSeed<'de>, + { + let binding = QJSValue::from_str(&self.variant); + let de = JSEngineDeserializer::new(&binding); + seed.deserialize(de).map(|v| (v, self)) + } +} + +impl<'de> VariantAccess<'de> for QJSVariantAccess { + type Error = serde_json::Error; + + fn unit_variant(self) -> Result<(), Self::Error> { + Ok(()) + } + + fn newtype_variant_seed(self, seed: T) -> Result + where + T: DeserializeSeed<'de>, + { + let de = JSEngineDeserializer::new(&self.value); + seed.deserialize(de) + } + + fn tuple_variant(self, _len: usize, visitor: V) -> Result + where + V: Visitor<'de>, + { + let de = JSEngineDeserializer::new(&self.value); + de.deserialize_seq(visitor) + } + + fn struct_variant( + self, + _fields: &'static [&'static str], + visitor: V, + ) -> Result + where + V: Visitor<'de>, + { + let de = JSEngineDeserializer::new(&self.value); + de.deserialize_map(visitor) + } +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use cxx::UniquePtr; + use cxx_qt_lib::{QCoreApplication, QString}; + use serde::{Deserialize, Serialize}; + + use crate::qml::*; + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + pub struct SerializeTest { + key: String, + value: i32, + } + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + pub struct DeserializeTest { + name: String, + age: i32, + list: Vec, + map: HashMap, + } + + /// Setup function to create a QJSEngine and evaluate a script + fn setup_and_evaluate( + value_str: &str, + ) -> ( + UniquePtr, + UniquePtr, + UniquePtr, + ) { + let app = QCoreApplication::new(); + let mut engine_ptr = QJSEngine::new(); + let mut engine = engine_ptr.as_mut().unwrap(); + let value = + engine + .as_mut() + .evaluate(&QString::from(value_str), &QString::from("filename"), 1); + (engine_ptr, value, app) + } + + #[test] + fn test_deserialize_bool() { + let (mut engine, value, _) = setup_and_evaluate("true"); + let deserialized: bool = engine.as_mut().unwrap().deserialize(&value).unwrap(); + assert_eq!(deserialized, true); + } + + #[test] + fn test_deserialize_string() { + let (mut engine, value, _) = setup_and_evaluate("'Hello, world!'"); + let deserialized: String = engine.as_mut().unwrap().deserialize(&value).unwrap(); + assert_eq!(deserialized, "Hello, world!"); + } + + #[test] + fn test_deserialize_integer() { + let (mut engine, value, _) = setup_and_evaluate("42"); + let deserialized: i32 = engine.as_mut().unwrap().deserialize(&value).unwrap(); + assert_eq!(deserialized, 42); + } + + #[test] + fn test_deserialize_float() { + let (mut engine, value, _) = setup_and_evaluate("3.14159"); + let deserialized: f64 = engine.as_mut().unwrap().deserialize(&value).unwrap(); + assert_eq!(deserialized, 3.14159); + } + + #[test] + fn test_deserialize_vector() { + let (mut engine, value, _) = setup_and_evaluate("['apple', 'banana', 'cherry']"); + let deserialized: Vec = engine.as_mut().unwrap().deserialize(&value).unwrap(); + assert_eq!(deserialized, vec!["apple", "banana", "cherry"]); + } + + #[test] + fn test_deserialize_map() { + let (mut engine, value, _) = setup_and_evaluate( + "JSON.parse('{\"mapkey\": { \"key\": \"example\", \"value\": 123 } }')", + ); + let deserialized: HashMap = + engine.as_mut().unwrap().deserialize(&value).unwrap(); + let inner = SerializeTest { + key: "example".to_string(), + value: 123, + }; + let mut map = HashMap::new(); + map.insert("mapkey".to_string(), inner); + assert_eq!(deserialized, map); + } + + #[test] + fn test_deserialize_custom_type() { + let (mut engine, value, _) = setup_and_evaluate( + "JSON.parse('{\"name\": \"Alice\", \"age\": 30, \"list\": [\"music\", \"books\"], \"map\": { \"key1\": { \"key\": \"item\", \"value\": 10 } } }')" + ); + let deserialized: DeserializeTest = engine.as_mut().unwrap().deserialize(&value).unwrap(); + let mut map = HashMap::new(); + map.insert( + "key1".to_string(), + SerializeTest { + key: "item".to_string(), + value: 10, + }, + ); + + let expected = DeserializeTest { + name: "Alice".to_string(), + age: 30, + list: vec!["music".to_string(), "books".to_string()], + map: map, + }; + assert_eq!(deserialized, expected); + } + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + pub struct TupleStruct(i32, String, f64); + + #[test] + fn test_deserialize_tuple() { + let (mut engine, value, _) = setup_and_evaluate("JSON.parse('[1, \"two\", 3.14]')"); + let deserialized: (i32, String, f64) = + engine.as_mut().unwrap().deserialize(&value).unwrap(); + assert_eq!(deserialized, (1, "two".to_string(), 3.14)); + } + + #[test] + fn test_deserialize_tuple_struct() { + let (mut engine, value, _) = setup_and_evaluate("JSON.parse('[42, \"Hello\", 2.718]')"); + let deserialized: TupleStruct = engine.as_mut().unwrap().deserialize(&value).unwrap(); + assert_eq!(deserialized, TupleStruct(42, "Hello".to_string(), 2.718)); + } + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + enum MyEnum { + Unit, + NewType(i32), + Tuple(i32, String), + Struct { id: u32, name: String }, + } + + #[test] + fn test_deserialize_enum_unit() { + let (mut engine, value, _) = setup_and_evaluate("JSON.parse('{\"Unit\": null}')"); + let deserialized: MyEnum = engine.as_mut().unwrap().deserialize(&value).unwrap(); + assert_eq!(deserialized, MyEnum::Unit); + } + #[test] + fn test_deserialize_enum_newtype() { + let (mut engine, value, _) = setup_and_evaluate("JSON.parse('{\"NewType\": 42}')"); + let deserialized: MyEnum = engine.as_mut().unwrap().deserialize(&value).unwrap(); + assert_eq!(deserialized, MyEnum::NewType(42)); + } + + #[test] + fn test_deserialize_enum_tuple() { + let (mut engine, value, _) = + setup_and_evaluate("JSON.parse('{\"Tuple\": [123, \"test\"]}')"); + let deserialized: MyEnum = engine.as_mut().unwrap().deserialize(&value).unwrap(); + assert_eq!(deserialized, MyEnum::Tuple(123, "test".to_string())); + } + + #[test] + fn test_deserialize_enum_struct() { + let (mut engine, value, _) = + setup_and_evaluate("JSON.parse('{\"Struct\": {\"id\": 1, \"name\": \"Alice\"}}')"); + let deserialized: MyEnum = engine.as_mut().unwrap().deserialize(&value).unwrap(); + assert_eq!( + deserialized, + MyEnum::Struct { + id: 1, + name: "Alice".to_string() + } + ); + } + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + struct UnitStruct; + + #[test] + fn test_deserialize_unit() { + let (mut engine, value, _) = setup_and_evaluate("null"); + let deserialized: () = engine.as_mut().unwrap().deserialize(&value).unwrap(); + assert_eq!(deserialized, ()); + } + + #[test] + fn test_deserialize_unit_struct() { + let (mut engine, value, _) = setup_and_evaluate("null"); + let deserialized: UnitStruct = engine.as_mut().unwrap().deserialize(&value).unwrap(); + assert_eq!(deserialized, UnitStruct); + } + + #[test] + fn test_deserialize_invalid_json() { + let (mut engine, value, _) = setup_and_evaluate("{ this is : 'invalid' }"); + let result: Result, _> = engine.as_mut().unwrap().deserialize(&value); + assert!(result.is_err(), "Expected an error for invalid JSON input"); + } + + #[test] + fn test_deserialize_excess_data() { + let (mut engine, value, _) = setup_and_evaluate( + "JSON.parse('{\"name\": \"Bob\", \"age\": 25, \"extra\": \"ignored\", \"list\": [], \"map\": {}}')", + ); + let deserialized: DeserializeTest = engine.as_mut().unwrap().deserialize(&value).unwrap(); + assert_eq!(deserialized.name, "Bob"); + assert_eq!(deserialized.age, 25); + // The test ensures that 'extra' field does not cause a failure + } +} diff --git a/crates/cxx-qt-lib-extras/src/qml/mod.rs b/crates/cxx-qt-lib-extras/src/qml/mod.rs new file mode 100644 index 000000000..6977f82b3 --- /dev/null +++ b/crates/cxx-qt-lib-extras/src/qml/mod.rs @@ -0,0 +1,21 @@ +mod qjsvalue; +pub use qjsvalue::QJSValue; + +mod qjsvaluelist; +pub use qjsvaluelist::QJSValueList; + +mod qjsvalueiterator; +pub use qjsvalueiterator::QJSValueIterator; + +mod qjsengine; +pub use qjsengine::QJSEngine; + +#[cfg(feature = "serde")] +mod deserializer; +#[cfg(feature = "serde")] +pub use deserializer::JSEngineDeserializer; + +#[cfg(feature = "serde")] +mod serializer; +#[cfg(feature = "serde")] +pub use serializer::JSEngineSerializer; \ No newline at end of file diff --git a/crates/cxx-qt-lib-extras/src/qml/qjsengine.cpp b/crates/cxx-qt-lib-extras/src/qml/qjsengine.cpp new file mode 100644 index 000000000..b974dc1fc --- /dev/null +++ b/crates/cxx-qt-lib-extras/src/qml/qjsengine.cpp @@ -0,0 +1,18 @@ +// #include "cxx-qt-lib-extras/qapplication.h" + +#include "cxx-qt-lib-extras/qjsengine.h" + +namespace rust +{ + namespace cxxqtlib1 + { + + ::std::unique_ptr qjsengineNew() + { + auto ptr = ::std::make_unique(); + Q_ASSERT(ptr != nullptr); + + return ptr; + } + } +} diff --git a/crates/cxx-qt-lib-extras/src/qml/qjsengine.rs b/crates/cxx-qt-lib-extras/src/qml/qjsengine.rs new file mode 100644 index 000000000..8798a56a4 --- /dev/null +++ b/crates/cxx-qt-lib-extras/src/qml/qjsengine.rs @@ -0,0 +1,110 @@ +use core::pin::Pin; + +#[cfg(feature = "serde")] +use serde::{de::DeserializeOwned, Serialize}; + +use cxx_qt_lib::QString; + +use crate::qml::QJSValue; +#[cfg(feature = "serde")] +use crate::qml::{JSEngineDeserializer, JSEngineSerializer}; + +#[cxx_qt::bridge] +mod qjsengine { + unsafe extern "C++" { + include!("cxx-qt-lib/qstring.h"); + type QString = cxx_qt_lib::QString; + + include!("cxx-qt-lib-extras/qjsengine.h"); + include!("cxx-qt-lib-extras/qjsvalue.h"); + type QJSValue = crate::qml::QJSValue; + } + + unsafe extern "C++Qt" { + #[qobject] + type QJSEngine; + } + + #[namespace = "rust::cxxqtlib1"] + unsafe extern "C++" { + #[doc(hidden)] + #[rust_name = "qjsengine_new"] + fn qjsengineNew() -> UniquePtr; + + #[doc(hidden)] + #[rust_name = "qjsengine_new_array"] + fn jsengineNewArray(engine: Pin<&mut QJSEngine>, length: u32) -> UniquePtr; + + #[doc(hidden)] + #[rust_name = "qjsengine_new_object"] + fn jsengineNewObject(engine: Pin<&mut QJSEngine>) -> UniquePtr; + + #[doc(hidden)] + #[rust_name = "qjsengine_evaluate"] + fn jsengineEvaluate( + engine: Pin<&mut QJSEngine>, + src: &QString, + filename: &QString, + line: i32, + ) -> UniquePtr; + + #[doc(hidden)] + #[rust_name = "qjsengine_import_module"] + fn jsengineImportModule(engine: Pin<&mut QJSEngine>, name: &QString) + -> UniquePtr; + + #[doc(hidden)] + #[rust_name = "qjsengine_global_object"] + fn jsengineGlobalObject(engine: Pin<&mut QJSEngine>) -> UniquePtr; + } +} + +pub use qjsengine::QJSEngine; + +impl QJSEngine { + pub fn new() -> cxx::UniquePtr { + qjsengine::qjsengine_new() + } + + pub fn new_array(self: Pin<&mut Self>, length: u32) -> cxx::UniquePtr { + qjsengine::qjsengine_new_array(self, length) + } + + pub fn new_object(self: Pin<&mut Self>) -> cxx::UniquePtr { + qjsengine::qjsengine_new_object(self) + } + + pub fn evaluate( + self: Pin<&mut Self>, + src: &QString, + filename: &QString, + line: i32, + ) -> cxx::UniquePtr { + qjsengine::qjsengine_evaluate(self, src, filename, line) + } + + pub fn import_module(self: Pin<&mut Self>, name: &QString) -> cxx::UniquePtr { + qjsengine::qjsengine_import_module(self, name) + } + + pub fn global_object(self: Pin<&mut Self>) -> cxx::UniquePtr { + qjsengine::qjsengine_global_object(self) + } + + #[cfg(feature = "serde")] + pub fn serialize( + self: Pin<&mut Self>, + value: &T, + ) -> Result, serde_json::Error> { + value.serialize(JSEngineSerializer::new(self)) + } + + #[cfg(feature = "serde")] + pub fn deserialize( + self: Pin<&mut Self>, + value: &QJSValue, + ) -> Result { + let de = JSEngineDeserializer::new(value); + T::deserialize(de) + } +} diff --git a/crates/cxx-qt-lib-extras/src/qml/qjsvalue.cpp b/crates/cxx-qt-lib-extras/src/qml/qjsvalue.cpp new file mode 100644 index 000000000..992b74bda --- /dev/null +++ b/crates/cxx-qt-lib-extras/src/qml/qjsvalue.cpp @@ -0,0 +1,124 @@ +// #include "cxx-qt-lib-extras/qapplication.h" + +#include "cxx-qt-lib-extras/qjsvalue.h" + +namespace rust +{ + namespace cxxqtlib1 + { + ::std::unique_ptr qjsvalue_new() + { + auto ptr = std::make_unique(QJSValue::UndefinedValue); + Q_ASSERT(ptr != nullptr); + + return ptr; + } + + ::std::unique_ptr qjsvalue_new_null() + { + auto ptr = std::make_unique(QJSValue::NullValue); + Q_ASSERT(ptr != nullptr); + + return ptr; + } + + ::std::unique_ptr qjsvalue_new_bool(bool value) + { + auto ptr = std::make_unique(value); + Q_ASSERT(ptr != nullptr); + return ptr; + } + + ::std::unique_ptr qjsvalue_new_int(int value) + { + auto ptr = std::make_unique(value); + Q_ASSERT(ptr != nullptr); + return ptr; + } + + ::std::unique_ptr qjsvalue_new_uint(uint value) + { + auto ptr = std::make_unique(value); + Q_ASSERT(ptr != nullptr); + return ptr; + } + + ::std::unique_ptr qjsvalue_new_double(double value) + { + auto ptr = std::make_unique(value); + Q_ASSERT(ptr != nullptr); + return ptr; + } + + ::std::unique_ptr qjsvalue_new_qstring(const QString &value) + { + auto ptr = std::make_unique(value); + Q_ASSERT(ptr != nullptr); + return ptr; + } + + ::std::unique_ptr qjsvalue_from_jsvalue(const QJSValue &value) + { + auto ptr = std::make_unique(value); + Q_ASSERT(ptr != nullptr); + return ptr; + } + + ::std::unique_ptr qjsvalue_get_property(const QString &value) + { + auto ptr = std::make_unique(value); + Q_ASSERT(ptr != nullptr); + return ptr; + } + + QString qjsvalue_to_string(const QJSValue &value) + { + return value.toString(); + // auto ptr = std::make_unique(value.toString()); + // return ptr; + } + + ::std::unique_ptr qjsvalue_property( + const QJSValue &value, + const QString &name) + { + auto ptr = ::std::make_unique(value.property(name)); + Q_ASSERT(ptr != nullptr); + + return ptr; + } + + ::std::unique_ptr qjsvalue_element( + const QJSValue &value, + quint32 index) + { + auto ptr = ::std::make_unique(value.property(index)); + Q_ASSERT(ptr != nullptr); + + return ptr; + } + + QVariant qjsvalue_to_qvariant(const QJSValue &value) + { + return value.toVariant(QJSValue::RetainJSObjects); + } + + QObject *qjsvalue_to_qobject(QJSValue &value) + { + return value.toQObject(); + } + + bool qvariantCanConvertQJSValue(const QVariant &variant) + { + return variant.canConvert(); + } + + ::std::unique_ptr qjsvalueFromQVariant(const QVariant &variant) noexcept + { + auto ptr = ::std::make_unique(variant.value()); + Q_ASSERT(ptr != nullptr); + + return ptr; + } + } +} diff --git a/crates/cxx-qt-lib-extras/src/qml/qjsvalue.rs b/crates/cxx-qt-lib-extras/src/qml/qjsvalue.rs new file mode 100644 index 000000000..3271dff0e --- /dev/null +++ b/crates/cxx-qt-lib-extras/src/qml/qjsvalue.rs @@ -0,0 +1,209 @@ +use core::pin::Pin; + +use cxx::UniquePtr; +#[cfg(feature = "serde")] +use serde::de::DeserializeOwned; + +use cxx_qt_lib::{QVariant, QVariantValue, QString}; + +#[cfg(feature = "serde")] +use crate::qml::QJSEngine; +use crate::qml::JSEngineDeserializer; + +#[cxx_qt::bridge] +mod qjsvalue { + + unsafe extern "C++" { + include!("cxx-qt-lib/qstring.h"); + type QString = cxx_qt_lib::QString; + include!("cxx-qt-lib/qvariant.h"); + type QVariant = cxx_qt_lib::QVariant; + + include!("cxx-qt-lib-extras/qjsvalue.h"); + type QJSValue; + + } + + #[namespace = "rust::cxxqtlib1"] + unsafe extern "C++" { + fn qjsvalue_new() -> UniquePtr; + fn qjsvalue_new_null() -> UniquePtr; + fn qjsvalue_new_bool(val: bool) -> UniquePtr; + fn qjsvalue_new_int(val: i32) -> UniquePtr; + fn qjsvalue_new_uint(val: u32) -> UniquePtr; + fn qjsvalue_new_double(val: f64) -> UniquePtr; + fn qjsvalue_new_qstring(val: &QString) -> UniquePtr; + + fn qjsvalue_from_jsvalue(jsvalue: &QJSValue) -> UniquePtr; + + fn qjsvalue_to_string(value: &QJSValue) -> QString; + + fn qjsvalue_property(value: &QJSValue, name: &QString) -> UniquePtr; + fn qjsvalue_element(value: &QJSValue, index: u32) -> UniquePtr; + + fn qjsvalue_to_qvariant(value: &QJSValue) -> QVariant; + + #[rust_name = "can_convert_qjsvalue"] + fn qvariantCanConvertQJSValue(variant: &QVariant) -> bool; + #[rust_name = "qjsvalue_from_qvariant"] + fn qjsvalueFromQVariant(variant: &QVariant) -> UniquePtr; + } + + #[namespace = "rust::cxxqtlib1"] + unsafe extern "C++" { + + #[rust_name = "is_bool"] + fn isBool(self: &QJSValue) -> bool; + #[rust_name = "is_array"] + fn isArray(self: &QJSValue) -> bool; + #[rust_name = "is_callable"] + fn isCallable(self: &QJSValue) -> bool; + #[rust_name = "is_date"] + fn isDate(self: &QJSValue) -> bool; + #[rust_name = "is_error"] + fn isError(self: &QJSValue) -> bool; + #[rust_name = "is_null"] + fn isNull(self: &QJSValue) -> bool; + #[rust_name = "is_number"] + fn isNumber(self: &QJSValue) -> bool; + #[rust_name = "is_object"] + fn isObject(self: &QJSValue) -> bool; + #[rust_name = "is_qmetaobject"] + fn isQMetaObject(self: &QJSValue) -> bool; + #[rust_name = "is_qobject"] + fn isQObject(self: &QJSValue) -> bool; + #[rust_name = "is_regexp"] + fn isRegExp(self: &QJSValue) -> bool; + #[rust_name = "is_string"] + fn isString(self: &QJSValue) -> bool; + #[rust_name = "is_undefined"] + fn isUndefined(self: &QJSValue) -> bool; + #[rust_name = "is_url"] + fn isUrl(self: &QJSValue) -> bool; + #[rust_name = "is_variant"] + fn isVariant(self: &QJSValue) -> bool; + + #[rust_name = "has_property"] + fn hasProperty(self: &QJSValue, name: &QString) -> bool; + #[rust_name = "set_property"] + fn setProperty(self: Pin<&mut QJSValue>, name: &QString, value: &QJSValue); + #[rust_name = "set_element"] + #[cxx_name = "setProperty"] + fn setElement(self: Pin<&mut QJSValue>, index: u32, value: &QJSValue); + + #[rust_name = "to_bool"] + fn toBool(self: &QJSValue) -> bool; + #[rust_name = "to_uint"] + fn toUInt(self: &QJSValue) -> u32; + #[rust_name = "to_int"] + fn toInt(self: &QJSValue) -> i32; + #[rust_name = "to_f64"] + fn toNumber(self: &QJSValue) -> f64; + + } +} + +pub use qjsvalue::QJSValue; + +impl QJSValue { + pub fn undefined() -> UniquePtr { + qjsvalue::qjsvalue_new() + } + + pub fn null() -> UniquePtr { + qjsvalue::qjsvalue_new_null() + } + + pub fn from_bool(val: bool) -> UniquePtr { + qjsvalue::qjsvalue_new_bool(val) + } + + pub fn from_int(val: i32) -> UniquePtr { + qjsvalue::qjsvalue_new_int(val) + } + + pub fn from_uint(val: u32) -> UniquePtr { + qjsvalue::qjsvalue_new_uint(val) + } + + pub fn from_f64(val: f64) -> UniquePtr { + qjsvalue::qjsvalue_new_double(val) + } + + pub fn from_qstring(val: &QString) -> UniquePtr { + qjsvalue::qjsvalue_new_qstring(val) + } + + pub fn from_str(val: &str) -> UniquePtr { + let qstring = QString::from(val); + qjsvalue::qjsvalue_new_qstring(&qstring) + } + + pub fn from_jsvalue(jsvalue: &QJSValue) -> UniquePtr { + qjsvalue::qjsvalue_from_jsvalue(jsvalue) + } + + pub fn from_map( + engine: Pin<&mut QJSEngine>, + map: &std::collections::HashMap>, + ) -> UniquePtr { + let mut obj = engine.new_object(); + let mut ptr = obj.as_mut().unwrap(); + for (key, value) in map.iter() { + ptr.as_mut().set_property(&QString::from(key), &value); + } + obj + } + + pub fn from_array( + engine: Pin<&mut QJSEngine>, + array: &Vec>, + ) -> UniquePtr { + let mut obj = engine.new_array(array.len() as u32); + let mut ptr = obj.as_mut().unwrap(); + for (index, value) in array.iter().enumerate() { + ptr.as_mut().set_element(index as u32, &value); + } + obj + } + + pub fn to_qstring(&self) -> QString { + qjsvalue::qjsvalue_to_string(self) + } + + pub fn get_property(&self, name: &QString) -> UniquePtr { + qjsvalue::qjsvalue_property(self, name) + } + + pub fn get_element(&self, index: u32) -> UniquePtr { + qjsvalue::qjsvalue_element(self, index) + } + + pub fn clone(&self) -> UniquePtr { + qjsvalue::qjsvalue_from_jsvalue(self) + } + + pub fn to_qvariant(&self) -> QVariant { + qjsvalue::qjsvalue_to_qvariant(self) + } + + #[cfg(feature = "serde")] + pub fn deserialize(self: &Self) -> Result { + let de = JSEngineDeserializer::new(self); + T::deserialize(de) + } + + pub fn from_qvariant(variant: &QVariant) -> Option> { + if qjsvalue::can_convert_qjsvalue(variant) { + Some(qjsvalue::qjsvalue_from_qvariant(variant)) + } else { + None + } + } +} + +impl std::fmt::Display for QJSValue { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.to_qstring().to_string()) + } +} diff --git a/crates/cxx-qt-lib-extras/src/qml/qjsvalueiterator.cpp b/crates/cxx-qt-lib-extras/src/qml/qjsvalueiterator.cpp new file mode 100644 index 000000000..b17926638 --- /dev/null +++ b/crates/cxx-qt-lib-extras/src/qml/qjsvalueiterator.cpp @@ -0,0 +1,25 @@ +// #include "cxx-qt-lib-extras/qapplication.h" + +#include "cxx-qt-lib-extras/qjsvalueiterator.h" + +namespace rust +{ + namespace cxxqtlib1 + { + + ::std::unique_ptr qjsvalueiterator_new(const QJSValue &value) + { + auto ptr = std::make_unique(value); + Q_ASSERT(ptr != nullptr); + + return ptr; + } + + ::std::unique_ptr qjsvalueiterator_value(const QJSValueIterator &iterator) + { + auto ptr = std::make_unique(iterator.value()); + Q_ASSERT(ptr != nullptr); + return ptr; + } + } +} diff --git a/crates/cxx-qt-lib-extras/src/qml/qjsvalueiterator.rs b/crates/cxx-qt-lib-extras/src/qml/qjsvalueiterator.rs new file mode 100644 index 000000000..a8d9411fd --- /dev/null +++ b/crates/cxx-qt-lib-extras/src/qml/qjsvalueiterator.rs @@ -0,0 +1,54 @@ +use cxx::UniquePtr; + +use crate::qml::QJSValue; + +#[cxx_qt::bridge] +mod qjsvalueiterator { + + unsafe extern "C++" { + include!("cxx-qt-lib/qstring.h"); + type QString = cxx_qt_lib::QString; + + include!("cxx-qt-lib-extras/qjsvalueiterator.h"); + type QJSValueIterator; + + type QJSValue = crate::qml::QJSValue; + } + + #[namespace = "rust::cxxqtlib1"] + unsafe extern "C++" { + fn qjsvalueiterator_new(value: &QJSValue) -> UniquePtr; + fn qjsvalueiterator_value(iterator: &QJSValueIterator) -> UniquePtr; + + #[rust_name = "qjsvalueiterator_name"] + fn name(self: &QJSValueIterator) -> QString; + #[rust_name = "qjsvalueiterator_has_next"] + fn hasNext(self: &QJSValueIterator) -> bool; + #[rust_name = "qjsvalueiterator_next"] + fn next(self: Pin<&mut QJSValueIterator>) -> bool; + } +} + +pub use qjsvalueiterator::QJSValueIterator; + +impl QJSValueIterator { + pub fn new(value: &QJSValue) -> UniquePtr { + qjsvalueiterator::qjsvalueiterator_new(value) + } + + pub fn value(&self) -> UniquePtr { + qjsvalueiterator::qjsvalueiterator_value(self) + } + + pub fn has_next(&self) -> bool { + qjsvalueiterator::qjsvalueiterator_has_next(self) + } + + pub fn next(self: Pin<&mut Self>) -> bool { + qjsvalueiterator::qjsvalueiterator_next(self) + } + + pub fn name(&self) -> QString { + qjsvalueiterator::qjsvalueiterator_name(self) + } +} diff --git a/crates/cxx-qt-lib-extras/src/qml/qjsvaluelist.cpp b/crates/cxx-qt-lib-extras/src/qml/qjsvaluelist.cpp new file mode 100644 index 000000000..d793da36a --- /dev/null +++ b/crates/cxx-qt-lib-extras/src/qml/qjsvaluelist.cpp @@ -0,0 +1,26 @@ +// #include "cxx-qt-lib-extras/qapplication.h" + +#include "cxx-qt-lib-extras/qjsvaluelist.h" + +namespace rust +{ + namespace cxxqtlib1 + { + ::std::unique_ptr qjsvaluelistNew() + { + auto ptr = std::make_unique(); + Q_ASSERT(ptr != nullptr); + + return ptr; + } + + ::std::unique_ptr qjsvaluelistClone(const QJSValueList &list) + { + auto ptr = std::make_unique(list); + Q_ASSERT(ptr != nullptr); + + return ptr; + } + + } +} diff --git a/crates/cxx-qt-lib-extras/src/qml/qjsvaluelist.rs b/crates/cxx-qt-lib-extras/src/qml/qjsvaluelist.rs new file mode 100644 index 000000000..b82e37a14 --- /dev/null +++ b/crates/cxx-qt-lib-extras/src/qml/qjsvaluelist.rs @@ -0,0 +1,39 @@ +#[cxx_qt::bridge] +mod qjsvaluelist { + unsafe extern "C++" { + include!("cxx-qt-lib-extras/qjsvalue.h"); + type QJSValue = crate::qml::QJSValue; + + include!("cxx-qt-lib-extras/qjsvaluelist.h"); + } + + #[namespace = "rust::cxxqtlib1"] + unsafe extern "C++" { + type QJSValueList; + + #[rust_name = "cxx_clear"] + fn clear(self: Pin<&mut QJSValueList>); + #[rust_name = "cxx_contains"] + fn contains(self: &QJSValueList, _: &QJSValue) -> bool; + } + + #[namespace = "rust::cxxqtlib1"] + unsafe extern "C++" { + #[rust_name = "qjsvaluelist_new"] + fn qjsvaluelistNew() -> UniquePtr; + #[rust_name = "qjsvaluelist_clone"] + fn qjsvaluelistClone(other: &QJSValueList) -> UniquePtr; + } +} + +pub use qjsvaluelist::QJSValueList; + +impl QJSValueList { + pub fn new() -> cxx::UniquePtr { + qjsvaluelist::qjsvaluelist_new() + } + + pub fn clone(self: &Self) -> cxx::UniquePtr { + qjsvaluelist::qjsvaluelist_clone(self) + } +} diff --git a/crates/cxx-qt-lib-extras/src/qml/serializer.rs b/crates/cxx-qt-lib-extras/src/qml/serializer.rs new file mode 100644 index 000000000..101a47b0c --- /dev/null +++ b/crates/cxx-qt-lib-extras/src/qml/serializer.rs @@ -0,0 +1,613 @@ +use core::pin::Pin; +use std::collections::HashMap; + +use cxx::UniquePtr; +#[cfg(feature = "serde")] +use serde::ser::*; + +use cxx_qt_lib::QString; + +use crate::qml::{QJSEngine, QJSValue}; + +pub struct JSEngineSerializer<'a> { + engine: Pin<&'a mut QJSEngine>, +} + +impl<'a> JSEngineSerializer<'a> { + pub fn new(engine: Pin<&'a mut QJSEngine>) -> Self { + Self { engine } + } +} + +impl<'a> Serializer for JSEngineSerializer<'a> { + type Ok = UniquePtr; + type Error = serde_json::Error; + + type SerializeSeq = QJSSerializeSeq<'a>; + type SerializeTuple = QJSSerializeSeq<'a>; + type SerializeTupleStruct = QJSSerializeSeq<'a>; + type SerializeTupleVariant = QJSSerializeTupleVariant<'a>; + type SerializeMap = QJSSerializeMap<'a>; + type SerializeStruct = QJSSerializeMap<'a>; + type SerializeStructVariant = QJSSerializeStructVariant<'a>; + + fn serialize_bool(self, v: bool) -> Result { + Ok(QJSValue::from_bool(v)) + } + + fn serialize_i8(self, v: i8) -> Result { + Ok(QJSValue::from_int(v as i32)) + } + + fn serialize_i16(self, v: i16) -> Result { + Ok(QJSValue::from_int(v as i32)) + } + + fn serialize_i32(self, v: i32) -> Result { + Ok(QJSValue::from_int(v)) + } + + fn serialize_i64(self, v: i64) -> Result { + Ok(QJSValue::from_int(v as i32)) // Assuming 32-bit int for simplicity + } + + fn serialize_u8(self, v: u8) -> Result { + Ok(QJSValue::from_uint(v as u32)) + } + + fn serialize_u16(self, v: u16) -> Result { + Ok(QJSValue::from_uint(v as u32)) + } + + fn serialize_u32(self, v: u32) -> Result { + Ok(QJSValue::from_uint(v)) + } + + fn serialize_u64(self, v: u64) -> Result { + Ok(QJSValue::from_f64(v as f64)) // Assuming 32-bit int for simplicity + } + + fn serialize_f32(self, v: f32) -> Result { + Ok(QJSValue::from_f64(v as f64)) + } + + fn serialize_f64(self, v: f64) -> Result { + Ok(QJSValue::from_f64(v)) + } + + fn serialize_char(self, v: char) -> Result { + let s: String = v.into(); + self.serialize_str(&s) + } + + fn serialize_str(self, v: &str) -> Result { + Ok(QJSValue::from_str(v)) + } + + fn serialize_bytes(mut self, v: &[u8]) -> Result { + let vec: Vec<_> = v.iter().map(|&b| QJSValue::from_uint(b as u32)).collect(); + Ok(QJSValue::from_array(self.engine.as_mut(), &vec)) + } + + fn serialize_none(self) -> Result { + Ok(QJSValue::null()) + } + + fn serialize_some(self, value: &T) -> Result + where + T: Serialize, + { + value.serialize(self) + } + + fn serialize_unit(self) -> Result { + Ok(QJSValue::null()) + } + + fn serialize_unit_struct(self, _name: &'static str) -> Result { + self.serialize_unit() + } + + fn serialize_unit_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + ) -> Result { + self.serialize_str(variant) + } + + fn serialize_newtype_struct( + self, + _name: &'static str, + value: &T, + ) -> Result + where + T: Serialize, + { + value.serialize(self) + } + + fn serialize_newtype_variant( + mut self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + value: &T, + ) -> Result + where + T: Serialize, + { + let mut map: HashMap> = HashMap::new(); + + let serializer = JSEngineSerializer::new(self.engine.as_mut()); + map.insert(variant.to_string(), value.serialize(serializer)?); + Ok(QJSValue::from_map(self.engine.as_mut(), &map)) + } + + fn serialize_seq(self, len: Option) -> Result { + let JSEngineSerializer { mut engine } = self; + let array = engine.as_mut().new_array(len.unwrap_or(0) as u32); + Ok(QJSSerializeSeq { + array, + index: 0, + engine: engine, + }) + } + + fn serialize_tuple(self, len: usize) -> Result { + self.serialize_seq(Some(len)) + } + + fn serialize_tuple_struct( + self, + _name: &'static str, + len: usize, + ) -> Result { + self.serialize_seq(Some(len)) + } + + fn serialize_tuple_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + len: usize, + ) -> Result { + let JSEngineSerializer { mut engine } = self; + let array = engine.as_mut().new_array(len as u32); + Ok(QJSSerializeTupleVariant { + name: variant.to_string(), + array, + index: 0, + engine: engine, + }) + } + + fn serialize_map(self, _len: Option) -> Result { + let JSEngineSerializer { mut engine } = self; + let object = engine.as_mut().new_object(); + Ok(QJSSerializeMap { + object, + key: None, + engine: engine, + }) + } + + fn serialize_struct( + self, + _name: &'static str, + len: usize, + ) -> Result { + self.serialize_map(Some(len)) + } + + fn serialize_struct_variant( + self, + _name: &'static str, + _variant_index: u32, + variant: &'static str, + _len: usize, + ) -> Result { + let JSEngineSerializer { mut engine } = self; + let object = engine.as_mut().new_object(); + Ok(QJSSerializeStructVariant { + name: variant.to_string(), + object, + engine: engine, + }) + } + + fn collect_str(self, value: &T) -> Result + where + T: std::fmt::Display, + { + let s = value.to_string(); + self.serialize_str(&s) + } +} + +pub struct QJSSerializeSeq<'a> { + array: UniquePtr, + index: usize, + engine: Pin<&'a mut QJSEngine>, +} + +impl<'a> SerializeSeq for QJSSerializeSeq<'a> { + type Ok = UniquePtr; + type Error = serde_json::Error; + + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + where + T: Serialize, + { + let value = { + let serializer = JSEngineSerializer::new(self.engine.as_mut()); + value.serialize(serializer)? + }; + self.array + .as_mut() + .unwrap() + .set_element(self.index as u32, &value); + self.index += 1; + Ok(()) + } + + fn end(self) -> Result { + Ok(self.array) + } +} + +impl<'a> SerializeTuple for QJSSerializeSeq<'a> { + type Ok = UniquePtr; + type Error = serde_json::Error; + + fn serialize_element(&mut self, value: &T) -> Result<(), Self::Error> + where + T: Serialize, + { + let value = { + let serializer = JSEngineSerializer::new(self.engine.as_mut()); + value.serialize(serializer)? + }; + self.array + .as_mut() + .unwrap() + .set_element(self.index as u32, &value); + self.index += 1; + Ok(()) + } + + fn end(self) -> Result { + Ok(self.array) + } +} + +impl<'a> SerializeTupleStruct for QJSSerializeSeq<'a> { + type Ok = UniquePtr; + type Error = serde_json::Error; + + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> + where + T: Serialize, + { + let value = { + let serializer = JSEngineSerializer::new(self.engine.as_mut()); + value.serialize(serializer)? + }; + self.array + .as_mut() + .unwrap() + .set_element(self.index as u32, &value); + self.index += 1; + Ok(()) + } + + fn end(self) -> Result { + Ok(self.array) + } +} + +pub struct QJSSerializeTupleVariant<'a> { + name: String, + array: UniquePtr, + index: usize, + engine: Pin<&'a mut QJSEngine>, +} + +impl<'a> SerializeTupleVariant for QJSSerializeTupleVariant<'a> { + type Ok = UniquePtr; + type Error = serde_json::Error; + + fn serialize_field(&mut self, value: &T) -> Result<(), Self::Error> + where + T: Serialize, + { + let value = { + let serializer = JSEngineSerializer::new(self.engine.as_mut()); + value.serialize(serializer)? + }; + self.array + .as_mut() + .unwrap() + .set_element(self.index as u32, &value); + self.index += 1; + Ok(()) + } + + fn end(mut self) -> Result { + let mut object = self.engine.as_mut().new_object(); + object + .as_mut() + .unwrap() + .set_property(&QString::from(&self.name), &self.array); + Ok(object) + } +} + +pub struct QJSSerializeMap<'a> { + object: UniquePtr, + key: Option, + engine: Pin<&'a mut QJSEngine>, +} + +impl<'a> SerializeMap for QJSSerializeMap<'a> { + type Ok = UniquePtr; + type Error = serde_json::Error; + + fn serialize_key(&mut self, key: &T) -> Result<(), Self::Error> + where + T: Serialize, + { + let serializer = JSEngineSerializer::new(self.engine.as_mut()); + self.key = Some(key.serialize(serializer)?.to_qstring()); + Ok(()) + } + + fn serialize_value(&mut self, value: &T) -> Result<(), Self::Error> + where + T: Serialize, + { + if let Some(ref key) = self.key { + let value = { + let serializer = JSEngineSerializer::new(self.engine.as_mut()); + value.serialize(serializer)? + }; + self.object.as_mut().unwrap().set_property(key, &value); + } + self.key = None; + Ok(()) + } + + fn end(self) -> Result { + Ok(self.object) + } +} + +impl<'a> SerializeStruct for QJSSerializeMap<'a> { + type Ok = UniquePtr; + type Error = serde_json::Error; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<(), Self::Error> + where + T: Serialize, + { + let value = { + let serializer = JSEngineSerializer::new(self.engine.as_mut()); + value.serialize(serializer)? + }; + self.object + .as_mut() + .unwrap() + .set_property(&QString::from(key), &value); + Ok(()) + } + + fn end(self) -> Result { + Ok(self.object) + } +} + +pub struct QJSSerializeStructVariant<'a> { + name: String, + object: UniquePtr, + engine: Pin<&'a mut QJSEngine>, +} + +impl<'a> SerializeStructVariant for QJSSerializeStructVariant<'a> { + type Ok = UniquePtr; + type Error = serde_json::Error; + + fn serialize_field( + &mut self, + key: &'static str, + value: &T, + ) -> Result<(), Self::Error> + where + T: Serialize, + { + let value = { + let serializer = JSEngineSerializer::new(self.engine.as_mut()); + value.serialize(serializer)? + }; + self.object + .as_mut() + .unwrap() + .set_property(&QString::from(key), &value); + Ok(()) + } + + fn end(mut self) -> Result { + let mut variant = self.engine.as_mut().new_object(); + variant + .as_mut() + .unwrap() + .set_property(&QString::from(&self.name), &self.object); + Ok(variant) + } +} + +#[cfg(test)] +mod tests { + use std::collections::HashMap; + + use crate::qml::*; + use cxx::UniquePtr; + use cxx_qt_lib::{QCoreApplication, QString}; + use serde::{Deserialize, Serialize}; + + #[derive(Serialize, Deserialize, PartialEq, Debug)] + pub struct SerializeTest { + key: String, + value: i32, + } + + fn setup_and_serialize( + value: &T, + ) -> ( + UniquePtr, + UniquePtr, + UniquePtr, + ) { + let app = QCoreApplication::new(); + let mut engine_ptr = QJSEngine::new(); + let mut engine = engine_ptr.as_mut().unwrap(); + let serialized_value = engine.as_mut().serialize(&value).unwrap(); + (engine_ptr, serialized_value, app) + } + + #[test] + fn test_serialize_bool() { + let (mut engine, value, _app) = setup_and_serialize(&true); + assert_eq!(engine.as_mut().unwrap().qjsvalue_to_json(&value), "true"); + } + + #[test] + fn test_serialize_integer() { + let (mut engine, value, _app) = setup_and_serialize(&42); + assert_eq!(engine.as_mut().unwrap().qjsvalue_to_json(&value), "42"); + } + + #[test] + fn test_serialize_string() { + let (mut engine, value, _app) = setup_and_serialize(&"Hello, world!"); + assert_eq!( + engine.as_mut().unwrap().qjsvalue_to_json(&value), + "\"Hello, world!\"" + ); + } + + #[test] + fn test_serialize_vector() { + let (mut engine, value, _app) = setup_and_serialize(&vec!["apple", "banana", "cherry"]); + assert_eq!( + engine.as_mut().unwrap().qjsvalue_to_json(&value), + "[\"apple\",\"banana\",\"cherry\"]" + ); + } + + #[test] + fn test_serialize_map() { + let mut map = HashMap::new(); + map.insert("key", "value"); + let (mut engine, value, _app) = setup_and_serialize(&map); + assert_eq!( + engine.as_mut().unwrap().qjsvalue_to_json(&value), + "{\"key\":\"value\"}" + ); + } + + #[test] + fn test_serialize_custom_type() { + let data = SerializeTest { + key: "example".to_string(), + value: 123, + }; + let (mut engine, value, _app) = setup_and_serialize(&data); + assert_eq!( + engine.as_mut().unwrap().qjsvalue_to_json(&value), + "{\"key\":\"example\",\"value\":123}" + ); + } + + #[test] + fn test_serialize_none() { + let none: Option = None; + let (mut engine, value, _app) = setup_and_serialize(&none); + assert_eq!(engine.as_mut().unwrap().qjsvalue_to_json(&value), "null"); + } + + #[test] + fn test_serialize_some() { + let some = Some(42); + let (mut engine, value, _app) = setup_and_serialize(&some); + assert_eq!(engine.as_mut().unwrap().qjsvalue_to_json(&value), "42"); + } + + #[test] + fn test_boolean_serialization() { + let (_, value, _app) = setup_and_serialize(&true); + assert!(value.is_bool()); + assert_eq!(value.to_bool(), true); + } + + #[test] + fn test_integer_serialization() { + let (_, value, _app) = setup_and_serialize(&42); + assert!(value.is_number()); + assert_eq!(value.to_int(), 42); + } + + #[test] + fn test_float_serialization() { + let (_, value, _app) = setup_and_serialize(&3.14f64); + assert!(value.is_number()); + assert_eq!(value.to_f64(), 3.14); + } + + #[test] + fn test_string_serialization() { + let (_, value, _app) = setup_and_serialize(&"Hello, world!"); + assert!(value.is_string()); + assert_eq!(value.to_qstring().to_string(), "Hello, world!"); + } + + #[test] + fn test_array_serialization() { + let list = vec![1, 2, 3]; + let (mut engine, value, _app) = setup_and_serialize(&list); + engine.as_mut().unwrap().qjsvalue_to_json(&value); + assert!(value.is_array()); + assert_eq!(value.get_element(0).to_int(), 1); + assert_eq!(value.get_element(1).to_int(), 2); + assert_eq!(value.get_element(2).to_int(), 3); + } + + #[test] + fn test_object_serialization() { + let mut map = HashMap::new(); + map.insert("key", 42); + let (_engine, value, _app) = setup_and_serialize(&map); + assert!(value.is_object()); + assert!(value.has_property(&QString::from("key"))); + assert_eq!(value.get_property(&QString::from("key")).to_int(), 42); + } + + #[test] + fn test_null_serialization() { + let none: Option = None; + let (mut engine, value, _app) = setup_and_serialize(&none); + engine.as_mut().unwrap().qjsvalue_to_json(&value); + assert!((*value).is_null()); + } + + #[test] + fn test_undefined_serialization() { + let value = QJSValue::undefined(); + assert!(value.is_undefined()); + } +}