Skip to content

Commit

Permalink
Add serde support for GString, StringName, NodePath and Array
Browse files Browse the repository at this point in the history
  • Loading branch information
kuruk-mm committed Nov 29, 2023
1 parent 05a1b09 commit 2db0576
Show file tree
Hide file tree
Showing 9 changed files with 295 additions and 0 deletions.
3 changes: 3 additions & 0 deletions check.sh
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,9 @@ for arg in "$@"; do
echo "$HELP_TEXT"
exit 0
;;
--use-serde)
extraCargoArgs+=("--features" "serde")
;;
--double)
extraCargoArgs+=("--features" "godot/double-precision")
;;
Expand Down
1 change: 1 addition & 0 deletions godot-core/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ serde = { version = "1", features = ["derive"], optional = true }
# Reverse dev dependencies so doctests can use `godot::` prefix
[dev-dependencies]
godot = { path = "../godot" }
serde_json = { version = "1.0" }

[build-dependencies]
godot-bindings = { path = "../godot-bindings" }
Expand Down
59 changes: 59 additions & 0 deletions godot-core/src/builtin/array.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1063,3 +1063,62 @@ impl fmt::Debug for TypeInfo {
write!(f, "{:?}{}", self.variant_type, class_str)
}
}

#[cfg(feature = "serde")]
mod serialize {
use super::*;
use serde::de::{SeqAccess, Visitor};
use serde::ser::SerializeSeq;
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::marker::PhantomData;

impl<T: Serialize + GodotType> Serialize for Array<T> {
#[inline]
fn serialize<S>(
&self,
serializer: S,
) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
where
S: Serializer,
{
let mut sequence = serializer.serialize_seq(Some(self.len()))?;
for e in self.iter_shared() {
sequence.serialize_element(&e)?
}
sequence.end()
}
}

impl<'de, T: Deserialize<'de> + GodotType> Deserialize<'de> for Array<T> {
#[inline]
fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
where
D: Deserializer<'de>,
{
struct ArrayVisitor<T>(PhantomData<T>);
impl<'de, T: Deserialize<'de> + GodotType> Visitor<'de> for ArrayVisitor<T> {
type Value = Array<T>;

fn expecting(&self, formatter: &mut std::fmt::Formatter) -> fmt::Result {
formatter.write_str(std::any::type_name::<Self::Value>())
}

fn visit_seq<A>(
self,
mut seq: A,
) -> Result<Self::Value, <A as SeqAccess<'de>>::Error>
where
A: SeqAccess<'de>,
{
let mut vec = seq.size_hint().map_or_else(Vec::new, Vec::with_capacity);
while let Some(val) = seq.next_element::<T>()? {
vec.push(val);
}
Ok(Self::Value::from(vec.as_slice()))
}
}

deserializer.deserialize_seq(ArrayVisitor::<T>(PhantomData))
}
}
}
48 changes: 48 additions & 0 deletions godot-core/src/builtin/string/gstring.rs
Original file line number Diff line number Diff line change
Expand Up @@ -313,3 +313,51 @@ impl From<NodePath> for GString {
Self::from(&path)
}
}

#[cfg(feature = "serde")]
mod serialize {
use super::*;
use serde::de::{Error, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt::Formatter;

impl Serialize for GString {
#[inline]
fn serialize<S>(
&self,
serializer: S,
) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}

#[cfg(feature = "serde")]
impl<'de> serialize::Deserialize<'de> for GString {
#[inline]
fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
where
D: Deserializer<'de>,
{
struct GStringVisitor;
impl<'de> Visitor<'de> for GStringVisitor {
type Value = GString;

fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
formatter.write_str("a GString")
}

fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: Error,
{
Ok(GString::from(s))
}
}

deserializer.deserialize_str(GStringVisitor)
}
}
}
58 changes: 58 additions & 0 deletions godot-core/src/builtin/string/node_path.rs
Original file line number Diff line number Diff line change
Expand Up @@ -150,3 +150,61 @@ impl From<StringName> for NodePath {
Self::from(GString::from(string_name))
}
}

#[cfg(feature = "serde")]
mod serialize {
use super::*;
use serde::de::{Error, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt::Formatter;

impl Serialize for NodePath {
#[inline]
fn serialize<S>(
&self,
serializer: S,
) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
where
S: Serializer,
{
serializer.serialize_newtype_struct("NodePath", &*self.to_string())
}
}

impl<'de> Deserialize<'de> for NodePath {
#[inline]
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: Deserializer<'de>,
{
struct NodePathVisitor;

impl<'de> Visitor<'de> for NodePathVisitor {
type Value = NodePath;

fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
formatter.write_str("a NodePath")
}

fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: Error,
{
Ok(NodePath::from(s))
}

fn visit_newtype_struct<D>(
self,
deserializer: D,
) -> Result<Self::Value, <D as Deserializer<'de>>::Error>
where
D: Deserializer<'de>,
{
deserializer.deserialize_str(self)
}
}

deserializer.deserialize_newtype_struct("NodePath", NodePathVisitor)
}
}
}
47 changes: 47 additions & 0 deletions godot-core/src/builtin/string/string_name.rs
Original file line number Diff line number Diff line change
Expand Up @@ -245,3 +245,50 @@ impl From<NodePath> for StringName {
Self::from(GString::from(path))
}
}

#[cfg(feature = "serde")]
mod serialize {
use super::*;
use serde::de::{Error, Visitor};
use serde::{Deserialize, Deserializer, Serialize, Serializer};
use std::fmt::Formatter;

impl Serialize for StringName {
#[inline]
fn serialize<S>(
&self,
serializer: S,
) -> Result<<S as Serializer>::Ok, <S as Serializer>::Error>
where
S: Serializer,
{
serializer.serialize_str(&self.to_string())
}
}

impl<'de> serialize::Deserialize<'de> for StringName {
#[inline]
fn deserialize<D>(deserializer: D) -> Result<Self, <D as Deserializer<'de>>::Error>
where
D: Deserializer<'de>,
{
struct StringNameVisitor;
impl<'de> Visitor<'de> for StringNameVisitor {
type Value = StringName;

fn expecting(&self, formatter: &mut Formatter) -> fmt::Result {
formatter.write_str("a StringName")
}

fn visit_str<E>(self, s: &str) -> Result<Self::Value, E>
where
E: Error,
{
Ok(StringName::from(s))
}
}

deserializer.deserialize_str(StringNameVisitor)
}
}
}
3 changes: 3 additions & 0 deletions itest/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,14 @@ crate-type = ["cdylib"]

[features]
default = []
serde = ["dep:serde", "dep:serde_json", "godot/serde"]
# Do not add features here that are 1:1 forwarded to the `godot` crate.
# Instead, compile itest with `--features godot/my-feature`.

[dependencies]
godot = { path = "../../godot", default-features = false }
serde = { version = "1", features = ["derive"], optional = true }
serde_json = { version = "1.0", optional = true }

[build-dependencies]
godot-bindings = { path = "../../godot-bindings" } # emit_godot_version_cfg
Expand Down
3 changes: 3 additions & 0 deletions itest/rust/src/builtin_tests/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,6 @@ mod string {
mod color_test;

mod convert_test;

#[cfg(feature = "serde")]
mod serde_test;
73 changes: 73 additions & 0 deletions itest/rust/src/builtin_tests/serde_test.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
use crate::framework::itest;
use godot::builtin::{array, Array, GString, NodePath, StringName, Vector2i};
use serde::{Deserialize, Serialize};

fn serde_roundtrip<T>(value: &T, expected_json: &str)
where
T: for<'a> Deserialize<'a> + Serialize + PartialEq + std::fmt::Debug,
{
let json: String = serde_json::to_string(value).unwrap();
let back: T = serde_json::from_str(json.as_str()).unwrap();

assert_eq!(back, *value, "serde round-trip changes value");
assert_eq!(
json, expected_json,
"value does not conform to expected JSON"
);
}

#[itest]
fn serde_gstring() {
let value = GString::from("hello world");

let expected_json = "\"hello world\"";

serde_roundtrip(&value, expected_json);
}

#[itest]
fn serde_node_path() {
let value = NodePath::from("res://icon.png");
let expected_json = "\"res://icon.png\"";

serde_roundtrip(&value, expected_json);
}

#[itest]
fn serde_string_name() {
let value = StringName::from("hello world");
let expected_json = "\"hello world\"";

serde_roundtrip(&value, expected_json);
}

#[itest]
fn serde_array_rust_native_type() {
let value: Array<i32> = array![1, 2, 3, 4, 5, 6];

let expected_json = r#"[1,2,3,4,5,6]"#;

serde_roundtrip(&value, expected_json)
}

#[itest]
fn serde_array_godot_builtin_type() {
let value: Array<GString> = array!["Godot".into(), "Rust".into(), "Rocks".into()];

let expected_json = r#"["Godot","Rust","Rocks"]"#;

serde_roundtrip(&value, expected_json)
}

#[itest]
fn serde_array_godot_type() {
let value: Array<Vector2i> = array![
Vector2i::new(1, 1),
Vector2i::new(2, 2),
Vector2i::new(3, 3)
];

let expected_json = r#"[{"x":1,"y":1},{"x":2,"y":2},{"x":3,"y":3}]"#;

serde_roundtrip(&value, expected_json)
}

0 comments on commit 2db0576

Please sign in to comment.