Skip to main content

ferroui_base/data/converters/
string_converters.rs

1use super::{FuncValueConverter, IValueConverter};
2use std::rc::Rc;
3
4/// Provides a set of useful [`IValueConverter`]s for working with string
5/// values.
6pub struct StringConverters;
7
8impl StringConverters {
9    /// A value converter that returns true if the input string is null or an
10    /// empty string.
11    pub fn is_null_or_empty() -> Rc<dyn IValueConverter> {
12        crate::ferro_thread_local! {
13            static IS_NULL_OR_EMPTY: Rc<dyn IValueConverter> =
14                Rc::new(FuncValueConverter::<Option<String>, bool>::new(|x| x.is_none_or(|x| x.is_empty())));
15        }
16        IS_NULL_OR_EMPTY.with(Rc::clone)
17    }
18
19    /// A value converter that returns true if the input string is not null
20    /// or empty.
21    pub fn is_not_null_or_empty() -> Rc<dyn IValueConverter> {
22        crate::ferro_thread_local! {
23            static IS_NOT_NULL_OR_EMPTY: Rc<dyn IValueConverter> =
24                Rc::new(FuncValueConverter::<Option<String>, bool>::new(|x| !x.is_none_or(|x| x.is_empty())));
25        }
26        IS_NOT_NULL_OR_EMPTY.with(Rc::clone)
27    }
28}
29
30#[cfg(test)]
31mod tests {
32    use super::*;
33    use crate::data::core::ValueType;
34    use crate::data::{BindingError, BindingOperations};
35    use crate::BoxedValue;
36
37    fn boxed(input: Option<&str>) -> Option<BoxedValue> {
38        input.map(|s| Rc::new(s.to_string()) as BoxedValue)
39    }
40
41    fn as_bool(result: Result<Option<BoxedValue>, BindingError>) -> bool {
42        let value = result.expect("no error").expect("not null");
43        *value.downcast_ref::<bool>().expect("a bool")
44    }
45
46    #[test]
47    fn string_converters_is_null_or_empty_works() {
48        for (input, expected) in [(Some("hello"), false), (Some(""), true), (None, true)] {
49            let converter = StringConverters::is_null_or_empty();
50            let result = converter.convert(boxed(input).as_ref(), ValueType::of::<bool>(), None, &crate::utilities::CultureInfo::current_culture());
51            assert_eq!(as_bool(result), expected, "{input:?}");
52        }
53    }
54
55    #[test]
56    fn string_converters_is_not_null_or_empty_works() {
57        for (input, expected) in [(Some("hello"), true), (Some(""), false), (None, false)] {
58            let converter = StringConverters::is_not_null_or_empty();
59            let result = converter.convert(boxed(input).as_ref(), ValueType::of::<bool>(), None, &crate::utilities::CultureInfo::current_culture());
60            assert_eq!(as_bool(result), expected, "{input:?}");
61        }
62    }
63
64    // Specific to this port.
65
66    #[test]
67    fn returns_unset_for_input_that_is_not_a_string() {
68        let converter = StringConverters::is_null_or_empty();
69        let input: BoxedValue = Rc::new(5i32);
70        let result = converter.convert(Some(&input), ValueType::of::<bool>(), None, &crate::utilities::CultureInfo::current_culture());
71        assert!(BindingOperations::is_unset(result.expect("no error").as_ref()));
72    }
73
74    #[test]
75    fn accepts_string_literals_and_nullable_strings() {
76        let converter = StringConverters::is_null_or_empty();
77        let literal: BoxedValue = Rc::new("hello");
78        let nullable: BoxedValue = Rc::new(Option::<String>::None);
79        assert!(!as_bool(converter.convert(Some(&literal), ValueType::of::<bool>(), None, &crate::utilities::CultureInfo::current_culture())));
80        assert!(as_bool(converter.convert(Some(&nullable), ValueType::of::<bool>(), None, &crate::utilities::CultureInfo::current_culture())));
81    }
82}