Skip to main content

ferroui_base/data/converters/
object_converters.rs

1use super::{FuncMultiValueConverter, FuncValueConverter, FuncValueConverterWithParameter};
2use super::{IMultiValueConverter, IValueConverter};
3use crate::data::core::ValueTypes;
4use crate::BoxedValue;
5use std::rc::Rc;
6
7/// Provides a set of useful [`IValueConverter`]s for working with objects.
8pub struct ObjectConverters;
9
10/// An untyped value that may be null.
11type Object = Option<BoxedValue>;
12
13/// The value equality of two non-null untyped values.
14fn equals(a: &BoxedValue, b: &Object) -> bool {
15    ValueTypes::identity_equals(Some(a), b.as_ref())
16}
17
18impl ObjectConverters {
19    /// A value converter that returns true if the input object is a null
20    /// reference.
21    pub fn is_null() -> Rc<dyn IValueConverter> {
22        crate::ferro_thread_local! {
23            static IS_NULL: Rc<dyn IValueConverter> =
24                Rc::new(FuncValueConverter::<Object, bool>::new(|x| x.is_none()));
25        }
26        IS_NULL.with(Rc::clone)
27    }
28
29    /// A value converter that returns true if the input object is not null.
30    pub fn is_not_null() -> Rc<dyn IValueConverter> {
31        crate::ferro_thread_local! {
32            static IS_NOT_NULL: Rc<dyn IValueConverter> =
33                Rc::new(FuncValueConverter::<Object, bool>::new(|x| x.is_some()));
34        }
35        IS_NOT_NULL.with(Rc::clone)
36    }
37
38    /// A value converter that returns true if the input object is equal to a
39    /// parameter object.
40    pub fn equal() -> Rc<dyn IValueConverter> {
41        crate::ferro_thread_local! {
42            static EQUAL: Rc<dyn IValueConverter> =
43                Rc::new(FuncValueConverterWithParameter::<Object, Object, bool>::new(|a, b| match &a {
44                    Some(a) => equals(a, &b),
45                    None => b.is_none(),
46                }));
47        }
48        EQUAL.with(Rc::clone)
49    }
50
51    /// A value converter that returns true if the input object is not equal
52    /// to a parameter object.
53    pub fn not_equal() -> Rc<dyn IValueConverter> {
54        crate::ferro_thread_local! {
55            static NOT_EQUAL: Rc<dyn IValueConverter> =
56                Rc::new(FuncValueConverterWithParameter::<Object, Object, bool>::new(|a, b| match &a {
57                    Some(a) => !equals(a, &b),
58                    None => b.is_some(),
59                }));
60        }
61        NOT_EQUAL.with(Rc::clone)
62    }
63
64    /// A multi-value converter that returns true if all inputs are null.
65    pub fn are_all_null() -> Rc<dyn IMultiValueConverter> {
66        crate::ferro_thread_local! {
67            static ARE_ALL_NULL: Rc<dyn IMultiValueConverter> =
68                Rc::new(FuncMultiValueConverter::<Object, bool>::new(|x| x.iter().all(|item| item.is_none())));
69        }
70        ARE_ALL_NULL.with(Rc::clone)
71    }
72
73    /// A multi-value converter that returns true if at least one input is
74    /// null.
75    pub fn are_any_null() -> Rc<dyn IMultiValueConverter> {
76        crate::ferro_thread_local! {
77            static ARE_ANY_NULL: Rc<dyn IMultiValueConverter> =
78                Rc::new(FuncMultiValueConverter::<Object, bool>::new(|x| x.iter().any(|item| item.is_none())));
79        }
80        ARE_ANY_NULL.with(Rc::clone)
81    }
82
83    /// A multi-value converter that returns true if all inputs are equal to
84    /// each other. Null values are not considered equal to anything.
85    pub fn are_all_equal() -> Rc<dyn IMultiValueConverter> {
86        crate::ferro_thread_local! {
87            static ARE_ALL_EQUAL: Rc<dyn IMultiValueConverter> =
88                Rc::new(FuncMultiValueConverter::<Object, bool>::new(ObjectConverters::equality_function));
89        }
90        ARE_ALL_EQUAL.with(Rc::clone)
91    }
92
93    /// Helper function for the [`are_all_equal`](Self::are_all_equal)
94    /// converter. Returns true if all inputs are equal to each other.
95    fn equality_function(values: &[Object]) -> bool {
96        let mut values = values.iter();
97
98        // Empty collection is considered equal.
99        let Some(first) = values.next() else {
100            return true;
101        };
102
103        // Null values are not considered equal to anything.
104        if first.is_none() {
105            return false;
106        }
107
108        for item in values {
109            match item {
110                Some(item) if equals(item, first) => {}
111                _ => return false,
112            }
113        }
114
115        true
116    }
117}
118
119#[cfg(test)]
120mod tests {
121    use super::*;
122    use crate::data::core::ValueType;
123    use crate::data::BindingError;
124    use crate::FerroObject;
125
126    fn as_bool(result: Result<Option<BoxedValue>, BindingError>) -> bool {
127        let value = result.expect("no error").expect("not null");
128        *value.downcast_ref::<bool>().expect("a bool")
129    }
130
131    /// A new object with reference identity.
132    fn new_object() -> BoxedValue {
133        Rc::new(FerroObject::new())
134    }
135
136    // ObjectConvertersTests
137
138    fn text(value: Option<&str>) -> Object {
139        value.map(|v| Rc::new(v.to_string()) as BoxedValue)
140    }
141
142    const VALUE: Option<&str> = Some("value");
143
144    #[test]
145    fn object_converters_tests_are_all_null_works() {
146        let cases = [
147            (None, None, None, true),
148            (None, None, VALUE, false),
149            (None, VALUE, None, false),
150            (VALUE, None, None, false),
151            (VALUE, VALUE, VALUE, false),
152        ];
153        for (value1, value2, value3, valid) in cases {
154            let converter = ObjectConverters::are_all_null();
155            let result = converter.convert(&[text(value1), text(value2), text(value3)], ValueType::of::<bool>(), None, &crate::utilities::CultureInfo::current_culture());
156            assert_eq!(as_bool(result), valid, "{value1:?} {value2:?} {value3:?}");
157        }
158    }
159
160    #[test]
161    fn object_converters_tests_are_any_null_works() {
162        let cases = [
163            (None, None, None, true),
164            (None, None, VALUE, true),
165            (None, VALUE, None, true),
166            (VALUE, None, None, true),
167            (VALUE, VALUE, VALUE, false),
168        ];
169        for (value1, value2, value3, valid) in cases {
170            let converter = ObjectConverters::are_any_null();
171            let result = converter.convert(&[text(value1), text(value2), text(value3)], ValueType::of::<bool>(), None, &crate::utilities::CultureInfo::current_culture());
172            assert_eq!(as_bool(result), valid, "{value1:?} {value2:?} {value3:?}");
173        }
174    }
175
176    #[test]
177    fn object_converters_tests_are_equal_works() {
178        let cases: [([Object; 3], bool); 6] = [
179            ([text(VALUE), text(VALUE), text(VALUE)], true),
180            ([None, text(VALUE), None], false),
181            ([text(VALUE), None, text(VALUE)], false),
182            ([text(VALUE), text(VALUE), text(Some("value1"))], false),
183            ([text(Some("value1")), text(VALUE), text(Some("value1"))], false),
184            ([text(VALUE), text(VALUE), Some(Rc::new(1i32))], false),
185        ];
186        for (values, valid) in cases {
187            let converter = ObjectConverters::are_all_equal();
188            let result = converter.convert(&values, ValueType::of::<bool>(), None, &crate::utilities::CultureInfo::current_culture());
189            assert_eq!(as_bool(result), valid);
190        }
191    }
192
193    #[test]
194    fn object_converters_tests_are_equal_edge_works() {
195        for (empty, unique, valid) in [(true, true, true), (false, true, false), (false, false, true)] {
196            let values: Vec<Object> = if empty {
197                Vec::new()
198            } else if unique {
199                vec![text(Some("1")), text(Some("2"))]
200            } else {
201                vec![text(Some("1")), text(Some("1"))]
202            };
203
204            let converter = ObjectConverters::are_all_equal();
205            let result = converter.convert(&values, ValueType::of::<bool>(), None, &crate::utilities::CultureInfo::current_culture());
206            assert_eq!(as_bool(result), valid, "empty: {empty}, unique: {unique}");
207        }
208    }
209
210    // ObjectConvertersTests_Equal
211
212    mod equal {
213        use super::*;
214
215        #[test]
216        fn returns_true_if_value_and_parameter_are_null() {
217            let result = ObjectConverters::equal().convert(None, ValueType::object(), None, &crate::utilities::CultureInfo::invariant_culture());
218
219            assert!(as_bool(result));
220        }
221
222        #[test]
223        fn returns_false_if_value_is_null_and_parameter_is_not_null() {
224            let result = ObjectConverters::equal().convert(None, ValueType::object(), Some(&new_object()), &crate::utilities::CultureInfo::invariant_culture());
225
226            assert!(!as_bool(result));
227        }
228
229        #[test]
230        fn returns_false_if_value_and_parameter_are_different_objects() {
231            let result = ObjectConverters::equal().convert(Some(&new_object()), ValueType::object(), Some(&new_object()), &crate::utilities::CultureInfo::invariant_culture());
232
233            assert!(!as_bool(result));
234        }
235
236        #[test]
237        fn returns_true_if_value_and_parameter_are_same_object() {
238            let target = new_object();
239            let result = ObjectConverters::equal().convert(Some(&target), ValueType::object(), Some(&target), &crate::utilities::CultureInfo::invariant_culture());
240
241            assert!(as_bool(result));
242        }
243
244        // Specific to this port.
245
246        #[test]
247        fn returns_false_if_value_is_not_null_and_parameter_is_null() {
248            let result = ObjectConverters::equal().convert(Some(&new_object()), ValueType::object(), None, &crate::utilities::CultureInfo::invariant_culture());
249
250            assert!(!as_bool(result));
251        }
252
253        #[test]
254        fn compares_values_by_value() {
255            let a: BoxedValue = Rc::new(5i32);
256            let b: BoxedValue = Rc::new(5i32);
257            let c: BoxedValue = Rc::new(5i64);
258
259            assert!(as_bool(ObjectConverters::equal().convert(Some(&a), ValueType::object(), Some(&b), &crate::utilities::CultureInfo::invariant_culture())));
260            assert!(!as_bool(ObjectConverters::equal().convert(Some(&a), ValueType::object(), Some(&c), &crate::utilities::CultureInfo::invariant_culture())));
261        }
262
263        #[test]
264        fn same_handle_in_different_boxes_is_equal() {
265            let object = FerroObject::new();
266            let a: BoxedValue = Rc::new(object.clone());
267            let b: BoxedValue = Rc::new(object);
268
269            assert!(as_bool(ObjectConverters::equal().convert(Some(&a), ValueType::object(), Some(&b), &crate::utilities::CultureInfo::invariant_culture())));
270        }
271    }
272
273    // ObjectConvertersTests_IsNull
274
275    mod is_null {
276        use super::*;
277
278        #[test]
279        fn returns_true_if_value_is_null() {
280            let result = ObjectConverters::is_null().convert(None, ValueType::object(), None, &crate::utilities::CultureInfo::invariant_culture());
281
282            assert!(as_bool(result));
283        }
284
285        #[test]
286        fn returns_false_if_value_is_not_null() {
287            let result = ObjectConverters::is_null().convert(Some(&new_object()), ValueType::object(), None, &crate::utilities::CultureInfo::invariant_culture());
288
289            assert!(!as_bool(result));
290        }
291
292        // Specific to this port.
293
294        #[test]
295        fn returns_true_if_value_is_an_empty_nullable() {
296            let value: BoxedValue = Rc::new(Option::<i32>::None);
297            let result = ObjectConverters::is_null().convert(Some(&value), ValueType::object(), None, &crate::utilities::CultureInfo::invariant_culture());
298
299            assert!(as_bool(result));
300        }
301    }
302
303    // ObjectConvertersTests_IsNotNull
304
305    mod is_not_null {
306        use super::*;
307
308        #[test]
309        fn returns_true_if_value_is_not_null() {
310            let result = ObjectConverters::is_not_null().convert(Some(&new_object()), ValueType::object(), None, &crate::utilities::CultureInfo::invariant_culture());
311
312            assert!(as_bool(result));
313        }
314
315        #[test]
316        fn returns_false_if_value_is_null() {
317            let result = ObjectConverters::is_not_null().convert(None, ValueType::object(), None, &crate::utilities::CultureInfo::invariant_culture());
318
319            assert!(!as_bool(result));
320        }
321    }
322
323    // ObjectConvertersTests_NotEqual
324
325    mod not_equal {
326        use super::*;
327
328        #[test]
329        fn returns_false_if_value_and_parameter_are_null() {
330            let result = ObjectConverters::not_equal().convert(None, ValueType::object(), None, &crate::utilities::CultureInfo::invariant_culture());
331
332            assert!(!as_bool(result));
333        }
334
335        #[test]
336        fn returns_true_if_value_is_null_and_parameter_is_not_null() {
337            let result = ObjectConverters::not_equal().convert(None, ValueType::object(), Some(&new_object()), &crate::utilities::CultureInfo::invariant_culture());
338
339            assert!(as_bool(result));
340        }
341
342        #[test]
343        fn returns_true_if_value_and_parameter_are_different_objects() {
344            let result =
345                ObjectConverters::not_equal().convert(Some(&new_object()), ValueType::object(), Some(&new_object()), &crate::utilities::CultureInfo::invariant_culture());
346
347            assert!(as_bool(result));
348        }
349
350        #[test]
351        fn returns_false_if_value_and_parameter_are_same_object() {
352            let target = new_object();
353            let result = ObjectConverters::not_equal().convert(Some(&target), ValueType::object(), Some(&target), &crate::utilities::CultureInfo::invariant_culture());
354
355            assert!(!as_bool(result));
356        }
357    }
358}