Skip to content

Commit 585b828

Browse files
committed
meow
1 parent 71e8f2e commit 585b828

File tree

4 files changed

+269
-10
lines changed

4 files changed

+269
-10
lines changed

godot-core/src/builtin/meta/godot_convert/convert_error.rs

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ use godot_ffi::VariantType;
1111

1212
use crate::builtin::{array_inner, meta::ClassName};
1313

14-
type Cause = Box<dyn Error + Send + Sync + 'static>;
14+
type Cause = Box<dyn Error + Send + Sync>;
1515

1616
/// Represents errors that can occur when converting values from Godot.
1717
#[derive(Debug)]
@@ -31,9 +31,9 @@ impl ConvertError {
3131
}
3232
}
3333

34-
/// Create a new custom for a conversion using a rust-error as the cause.
35-
pub fn custom<C: Into<Cause>>(cause: C) -> Self {
36-
Self::new(ErrorKind::Custom).with_cause(cause)
34+
/// Create a new custom error for a conversion.
35+
pub fn custom() -> Self {
36+
Self::new(ErrorKind::Custom)
3737
}
3838

3939
/// Add a rust-error as an underlying cause for the conversion error.
@@ -42,12 +42,22 @@ impl ConvertError {
4242
self
4343
}
4444

45+
/// Returns the rust-error that caused this error, if one exists.
46+
pub fn cause(&self) -> Option<&(dyn Error + Send + Sync)> {
47+
self.cause.as_deref()
48+
}
49+
4550
/// Add the value that failed to be converted.
4651
pub fn with_value<V: fmt::Debug + 'static>(mut self, value: V) -> Self {
4752
self.value = Some(Box::new(value));
4853
self
4954
}
5055

56+
/// Returns the value that failed to convert, if one exists.
57+
pub fn value(&self) -> Option<&(dyn fmt::Debug)> {
58+
self.value.as_deref()
59+
}
60+
5161
fn description(&self) -> Option<String> {
5262
self.kind.description()
5363
}

godot-macros/src/derive/derive_from_variant.rs

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ pub fn derive_from_godot(decl: Declaration) -> ParseResult<TokenStream> {
2525

2626
let mut body = quote! {
2727
let root = variant.try_to::<::godot::builtin::Dictionary>()?;
28-
let root = root.get(#name_string).ok_or(ConvertError::custom(concat!("missing expected value ", #name_string)).with_value(root.clone()))?;
28+
let root = root.get(#name_string).ok_or(ConvertError::custom().with_cause(concat!("missing expected value ", #name_string)).with_value(root.clone()))?;
2929
};
3030

3131
match decl {
@@ -94,7 +94,7 @@ pub fn derive_from_godot(decl: Declaration) -> ParseResult<TokenStream> {
9494
body = quote! {
9595
#body
9696
#matches
97-
Err(ConvertError::custom("unknown error"))
97+
Err(ConvertError::custom())
9898
};
9999
}
100100
}
@@ -131,7 +131,7 @@ fn make_named_struct(
131131
} else {
132132
(
133133
quote! {
134-
let #ident = root.get(#string_ident).ok_or(ConvertError::custom(concat!("missing expected value ", #string_ident)).with_value(root.clone()))?;
134+
let #ident = root.get(#string_ident).ok_or(ConvertError::custom().with_cause(concat!("missing expected value ", #string_ident)).with_value(root.clone()))?;
135135
},
136136
quote! { #ident: #ident.try_to()? },
137137
)
@@ -165,7 +165,7 @@ fn make_tuple_struct(
165165
} else {
166166
quote! {
167167
let #ident = root.pop_front()
168-
.ok_or(ConvertError::custom("missing expected value").with_value(root.clone()))?
168+
.ok_or(ConvertError::custom().with_cause("missing expected value").with_value(root.clone()))?
169169
.try_to::<#field_type>()?;
170170
}
171171
},
@@ -252,7 +252,7 @@ fn make_enum_tuple(
252252
} else {
253253
quote! {
254254
let #ident = variant.pop_front()
255-
.ok_or(ConvertError::custom("missing expected value").with_value(variant.clone()))?
255+
.ok_or(ConvertError::custom().with_cause("missing expected value").with_value(variant.clone()))?
256256
.try_to::<#field_type>()?;
257257
}
258258
};
@@ -287,7 +287,7 @@ fn make_enum_named(
287287
} else {
288288
quote! {
289289
let #field_name = variant.get(#field_name_string)
290-
.ok_or(ConvertError::custom(concat!("missing expected value ", #field_name_string)).with_value(variant.clone()))?
290+
.ok_or(ConvertError::custom().with_cause(concat!("missing expected value ", #field_name_string)).with_value(variant.clone()))?
291291
.try_to::<#field_type>()?;
292292
}
293293
};
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
/*
2+
* This Source Code Form is subject to the terms of the Mozilla Public
3+
* License, v. 2.0. If a copy of the MPL was not distributed with this
4+
* file, You can obtain one at https://mozilla.org/MPL/2.0/.
5+
*/
6+
7+
use godot::builtin::meta::{FromGodot, GodotConvert, ToGodot};
8+
use godot::builtin::{
9+
dict, Array, ConvertError, Dictionary, GString, Variant, VariantArray, Vector2, Vector2Axis,
10+
};
11+
use godot::engine::{Node, Resource};
12+
use godot::obj::Gd;
13+
14+
use crate::framework::itest;
15+
16+
/// Ensure conversions we define have an associated value, and no underlying rust cause.
17+
#[itest]
18+
fn error_has_value_and_no_cause() {
19+
let mut errors: Vec<(ConvertError, &'static str)> = Vec::new();
20+
21+
errors.push((
22+
Variant::nil().try_to::<i64>().unwrap_err(),
23+
"`nil` -> `i64`",
24+
));
25+
26+
errors.push((
27+
VariantArray::new()
28+
.to_variant()
29+
.try_to::<GString>()
30+
.unwrap_err(),
31+
"`VariantArray` -> `GString`",
32+
));
33+
34+
errors.push((
35+
VariantArray::new()
36+
.to_variant()
37+
.try_to::<Array<i64>>()
38+
.unwrap_err(),
39+
"`VariantArray` -> `Array<i64>`",
40+
));
41+
42+
errors.push((
43+
Array::<Gd<Node>>::new()
44+
.to_variant()
45+
.try_to::<Array<Gd<Resource>>>()
46+
.unwrap_err(),
47+
"`Array<Gd<Node>>` -> `Array<Gd<Resource>>`",
48+
));
49+
50+
let node = Node::new_alloc();
51+
52+
errors.push((
53+
node.clone().to_variant().try_to::<f32>().unwrap_err(),
54+
"`Gd<Node>` -> `f32`",
55+
));
56+
57+
errors.push((
58+
node.clone()
59+
.to_variant()
60+
.try_to::<Gd<Resource>>()
61+
.unwrap_err(),
62+
"`Gd<Node>` -> `Gd<Resource>`",
63+
));
64+
65+
for (err, err_str) in errors.into_iter() {
66+
assert!(
67+
err.value().is_some(),
68+
"{err_str} conversion has no value: {err:?}"
69+
);
70+
assert!(
71+
err.cause().is_none(),
72+
"{err_str} conversion should have no rust cause: {err:?}"
73+
);
74+
}
75+
76+
node.free();
77+
}
78+
79+
/// Check that the value stored in an error is the same as what was given.
80+
#[itest]
81+
fn error_maintains_value() {
82+
// Need to use Debug to check equality here since we get a `dyn Debug` back.
83+
let value = i32::MAX;
84+
let err = Vector2Axis::try_from_godot(value).unwrap_err();
85+
assert_eq!(format!("{value:?}"), format!("{:?}", err.value().unwrap()));
86+
87+
let value = i64::MAX;
88+
let err = value.to_variant().try_to::<i32>().unwrap_err();
89+
assert_eq!(format!("{value:?}"), format!("{:?}", err.value().unwrap()));
90+
91+
let value = f64::MAX;
92+
let err = value.to_variant().try_to::<i32>().unwrap_err();
93+
assert_eq!(
94+
format!("{:?}", value.to_variant()),
95+
format!("{:?}", err.value().unwrap())
96+
);
97+
}
98+
99+
// Manual implementation of `GodotConvert` and related traits to ensure conversion works.
100+
#[derive(Debug, PartialEq)]
101+
struct Foo {
102+
a: i32,
103+
b: f32,
104+
}
105+
106+
impl Foo {
107+
const MISSING_KEY_A: &'static str = "missing `a` key";
108+
const MISSING_KEY_B: &'static str = "missing `a` key";
109+
const TOO_MANY_KEYS: &'static str = "missing `a` key";
110+
}
111+
112+
impl GodotConvert for Foo {
113+
type Via = Dictionary;
114+
}
115+
116+
impl ToGodot for Foo {
117+
fn to_godot(&self) -> Self::Via {
118+
dict! {
119+
"a": self.a,
120+
"b": self.b,
121+
}
122+
}
123+
}
124+
125+
impl FromGodot for Foo {
126+
fn try_from_godot(via: Self::Via) -> Result<Self, ConvertError> {
127+
let a = match via.get("a") {
128+
Some(a) => a,
129+
None => {
130+
return Err(ConvertError::custom()
131+
.with_cause(Self::MISSING_KEY_A)
132+
.with_value(via))
133+
}
134+
};
135+
136+
let b = match via.get("b") {
137+
Some(b) => b,
138+
None => {
139+
return Err(ConvertError::custom()
140+
.with_cause(Self::MISSING_KEY_B)
141+
.with_value(via))
142+
}
143+
};
144+
145+
if via.len() > 2 {
146+
return Err(ConvertError::custom()
147+
.with_cause(Self::TOO_MANY_KEYS)
148+
.with_value(via));
149+
}
150+
151+
Ok(Self {
152+
a: a.try_to()?,
153+
b: b.try_to()?,
154+
})
155+
}
156+
}
157+
158+
#[itest]
159+
fn custom_convert_roundtrip() {
160+
let foo = Foo { a: 10, b: 12.34 };
161+
162+
let as_dict = foo.to_godot();
163+
assert_eq!(as_dict.get("a"), Some(foo.a.to_variant()));
164+
assert_eq!(as_dict.get("b"), Some(foo.b.to_variant()));
165+
166+
let foo2 = as_dict.to_variant().to::<Foo>();
167+
assert_eq!(foo, foo2, "from_variant");
168+
169+
let foo3 = Foo::from_godot(as_dict);
170+
assert_eq!(foo, foo3, "from_godot");
171+
}
172+
173+
// Ensure all failure states for the `FromGodot` conversion of `Foo` are propagated through the `try_to`
174+
// method of `Variant` as they should be.
175+
#[itest]
176+
fn custom_convert_error_from_variant() {
177+
let missing_a = dict! {
178+
"b": -0.001
179+
};
180+
let err = missing_a
181+
.to_variant()
182+
.try_to::<Foo>()
183+
.expect_err("should be missing key `a`");
184+
assert_eq!(err.cause().unwrap().to_string(), Foo::MISSING_KEY_A);
185+
186+
let missing_b = dict! {
187+
"a": 58,
188+
};
189+
let err = missing_b
190+
.to_variant()
191+
.try_to::<Foo>()
192+
.expect_err("should be missing key `b`");
193+
assert_eq!(err.cause().unwrap().to_string(), Foo::MISSING_KEY_B);
194+
195+
let too_many_keys = dict! {
196+
"a": 12,
197+
"b": 777.777,
198+
"c": "bar"
199+
};
200+
too_many_keys
201+
.to_variant()
202+
.try_to::<Foo>()
203+
.expect_err("should have too many keys");
204+
assert_eq!(err.cause().unwrap().to_string(), Foo::TOO_MANY_KEYS);
205+
206+
let wrong_type_a = dict! {
207+
"a": "hello",
208+
"b": 28.41,
209+
};
210+
let err = wrong_type_a
211+
.to_variant()
212+
.try_to::<Foo>()
213+
.expect_err("should have wrongly typed key `a`");
214+
assert!(err.cause().is_none());
215+
assert_eq!(
216+
format!("{:?}", err.value().unwrap()),
217+
format!("{:?}", "hello".to_variant())
218+
);
219+
220+
let wrong_type_b = dict! {
221+
"a": 29,
222+
"b": Vector2::new(1.0, 23.4),
223+
};
224+
let err = wrong_type_b
225+
.to_variant()
226+
.try_to::<Foo>()
227+
.expect_err("should have wrongly typed key `b`");
228+
assert!(err.cause().is_none());
229+
assert_eq!(
230+
format!("{:?}", err.value().unwrap()),
231+
format!("{:?}", Vector2::new(1.0, 23.4).to_variant())
232+
);
233+
234+
let too_big_value = dict! {
235+
"a": i64::MAX,
236+
"b": f32::NAN
237+
};
238+
let err = too_big_value
239+
.to_variant()
240+
.try_to::<Foo>()
241+
.expect_err("should have too big value for field `a`");
242+
assert!(err.cause().is_none());
243+
assert_eq!(
244+
format!("{:?}", err.value().unwrap()),
245+
format!("{:?}", i64::MAX)
246+
);
247+
}

itest/rust/src/builtin_tests/mod.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,3 +32,5 @@ mod string {
3232
}
3333

3434
mod color_test;
35+
36+
mod convert_test;

0 commit comments

Comments
 (0)