ferroui_base/data/converters/
string_format_multi_value_converter.rs1use super::composite_format::format_values_with;
2use super::IMultiValueConverter;
3use crate::data::core::ValueType;
4use crate::data::BindingError;
5use crate::utilities::CultureInfo;
6use crate::BoxedValue;
7use std::rc::Rc;
8
9pub struct StringFormatMultiValueConverter {
12 format: String,
13 inner: Option<Rc<dyn IMultiValueConverter>>,
14}
15
16impl StringFormatMultiValueConverter {
17 pub fn new(format: impl Into<String>, inner: Option<Rc<dyn IMultiValueConverter>>) -> Self {
22 Self { format: format.into(), inner }
23 }
24
25 pub fn inner(&self) -> Option<&Rc<dyn IMultiValueConverter>> {
28 self.inner.as_ref()
29 }
30
31 pub fn format(&self) -> &str {
33 &self.format
34 }
35}
36
37impl IMultiValueConverter for StringFormatMultiValueConverter {
38 fn convert(
39 &self,
40 values: &[Option<BoxedValue>],
41 target_type: ValueType,
42 parameter: Option<&BoxedValue>,
43 culture: &CultureInfo,
44 ) -> Result<Option<BoxedValue>, BindingError> {
45 let result = match &self.inner {
46 None => format_values_with(&self.format, values, culture),
47 Some(inner) => {
48 format_values_with(&self.format, &[inner.convert(values, target_type, parameter, culture)?], culture)
49 }
50 };
51 match result {
52 Ok(text) => Ok(Some(Rc::new(text))),
53 Err(error) => Err(BindingError::new(error)),
54 }
55 }
56}
57
58#[cfg(test)]
59mod tests {
60 use super::*;
64 use crate::data::converters::composite_format::FormatError;
65 use crate::data::converters::FuncMultiValueConverter;
66
67 fn boxed<T: PartialEq + 'static>(value: T) -> Option<BoxedValue> {
68 Some(Rc::new(value))
69 }
70
71 fn convert(target: &StringFormatMultiValueConverter, values: &[Option<BoxedValue>]) -> String {
72 let result = target.convert(values, ValueType::of::<String>(), None, &crate::utilities::CultureInfo::invariant_culture()).expect("no error").expect("not null");
73 result.downcast_ref::<String>().cloned().expect("a string")
74 }
75
76 #[test]
77 fn exposes_format_and_inner() {
78 let inner: Rc<dyn IMultiValueConverter> = Rc::new(FuncMultiValueConverter::<i32, i32>::new(|x| x[0]));
79 let target = StringFormatMultiValueConverter::new("{0}", Some(inner.clone()));
80
81 assert_eq!(target.format(), "{0}");
82 assert!(target.inner().is_some_and(|i| Rc::ptr_eq(i, &inner)));
83 assert!(StringFormatMultiValueConverter::new("{0}", None).inner().is_none());
84 }
85
86 #[test]
87 fn formats_values_with_composite_format() {
88 let target = StringFormatMultiValueConverter::new("{0}+{1}={2:F1}", None);
89
90 assert_eq!(convert(&target, &[boxed(1i32), boxed(String::from("b")), boxed(2.5f64)]), "1+b=2.5");
91 }
92
93 #[test]
96 fn null_value_formats_as_empty_text() {
97 let target = StringFormatMultiValueConverter::new("Converted: {0}", None);
98
99 assert_eq!(convert(&target, &[boxed(String::from("Foo"))]), "Converted: Foo");
100 assert_eq!(convert(&target, &[None]), "Converted: ");
101 }
102
103 #[test]
104 fn format_without_braces_is_literal_text() {
105 let target = StringFormatMultiValueConverter::new("0.00", None);
106
107 assert_eq!(convert(&target, &[boxed(1.5f64)]), "0.00");
108 }
109
110 #[test]
111 fn result_of_inner_converter_is_the_only_argument() {
112 let inner: Rc<dyn IMultiValueConverter> =
113 Rc::new(FuncMultiValueConverter::<i32, i32>::new(|x| x.iter().sum()));
114 let target = StringFormatMultiValueConverter::new("Sum: {0:D3}", Some(inner.clone()));
115
116 assert_eq!(convert(&target, &[boxed(1i32), boxed(2i32), boxed(3i32)]), "Sum: 006");
117
118 let target = StringFormatMultiValueConverter::new("{1}", Some(inner));
119 let error = target.convert(&[boxed(1i32), boxed(2i32)], ValueType::of::<String>(), None, &crate::utilities::CultureInfo::invariant_culture()).expect_err("an error");
120 assert_eq!(error.inner().downcast_ref::<FormatError>(), Some(&FormatError::IndexOutOfRange));
121 }
122
123 #[test]
124 fn missing_value_is_an_error() {
125 let target = StringFormatMultiValueConverter::new("{0} {1}", None);
126
127 let error = target.convert(&[boxed(1i32)], ValueType::of::<String>(), None, &crate::utilities::CultureInfo::invariant_culture()).expect_err("an error");
128
129 assert_eq!(error.inner().downcast_ref::<FormatError>(), Some(&FormatError::IndexOutOfRange));
130 }
131}