Skip to main content

ferroui_base/data/
binding_value.rs

1use crate::data::core::{ValueType, ValueTypes};
2use crate::data::{AggregateError, BindingErrorType, BindingNotification, BindingOperations};
3use crate::{BoxedValue, DoNothingType, FerroProperty, PropertyValue, UnsetValueType};
4use std::any::Any;
5use std::fmt;
6use std::rc::Rc;
7
8/// An error produced by a binding or by data validation.
9#[derive(Clone)]
10pub struct BindingError(Rc<dyn std::error::Error>);
11
12impl BindingError {
13    pub fn new(error: impl std::error::Error + 'static) -> Self {
14        Self(Rc::new(error))
15    }
16
17    pub fn message(message: impl Into<String>) -> Self {
18        #[derive(Debug)]
19        struct Message(String);
20        impl fmt::Display for Message {
21            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
22                f.write_str(&self.0)
23            }
24        }
25        impl std::error::Error for Message {}
26        Self(Rc::new(Message(message.into())))
27    }
28
29    pub fn inner(&self) -> &(dyn std::error::Error + 'static) {
30        &*self.0
31    }
32}
33
34impl fmt::Debug for BindingError {
35    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36        fmt::Debug::fmt(&self.0, f)
37    }
38}
39
40impl fmt::Display for BindingError {
41    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
42        fmt::Display::fmt(&self.0, f)
43    }
44}
45
46impl PartialEq for BindingError {
47    fn eq(&self, other: &Self) -> bool {
48        Rc::ptr_eq(&self.0, &other.0)
49    }
50}
51
52bitflags::bitflags! {
53    /// Describes the state of a [`BindingValue`]: a kind (the bits of
54    /// [`TYPE_MASK`](Self::TYPE_MASK)) combined with the
55    /// [`HAS_VALUE`](Self::HAS_VALUE) and [`HAS_ERROR`](Self::HAS_ERROR)
56    /// flags.
57    #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
58    pub struct BindingValueType: i32 {
59        /// An unset value: the target property will revert to its unbound
60        /// state until a new binding value is produced.
61        const UNSET_VALUE = 0;
62        /// Do nothing: the binding value will be ignored.
63        const DO_NOTHING = 1;
64        /// A simple value.
65        const VALUE = 2 | Self::HAS_VALUE.bits();
66        /// A binding error, such as a missing source property.
67        const BINDING_ERROR = 3 | Self::HAS_ERROR.bits();
68        /// A data validation error.
69        const DATA_VALIDATION_ERROR = 4 | Self::HAS_ERROR.bits();
70        /// A binding error with a fallback value.
71        const BINDING_ERROR_WITH_FALLBACK = Self::BINDING_ERROR.bits() | Self::HAS_VALUE.bits();
72        /// A data validation error with a fallback value.
73        const DATA_VALIDATION_ERROR_WITH_FALLBACK = Self::DATA_VALIDATION_ERROR.bits() | Self::HAS_VALUE.bits();
74        /// The bits that hold the kind of the value.
75        const TYPE_MASK = 0x00ff;
76        /// The binding value has a value.
77        const HAS_VALUE = 0x0100;
78        /// The binding value has an error.
79        const HAS_ERROR = 0x0200;
80    }
81}
82
83/// A value passed into a binding: a value, an "unset"/"do nothing" marker, or
84/// an error with an optional fallback value.
85#[derive(Clone, Debug, PartialEq)]
86pub struct BindingValue<T> {
87    type_: BindingValueType,
88    value: Option<T>,
89    error: Option<BindingError>,
90}
91
92impl<T> BindingValue<T> {
93    /// Creates a binding value holding a simple value.
94    #[inline]
95    pub fn new(value: T) -> Self {
96        Self { type_: BindingValueType::VALUE, value: Some(value), error: None }
97    }
98
99    /// A binding value representing the unset state.
100    #[inline]
101    pub fn unset() -> Self {
102        Self { type_: BindingValueType::UNSET_VALUE, value: None, error: None }
103    }
104
105    /// A binding value that is ignored by its target.
106    #[inline]
107    pub fn do_nothing() -> Self {
108        Self { type_: BindingValueType::DO_NOTHING, value: None, error: None }
109    }
110
111    /// A binding error without a fallback value.
112    pub fn binding_error(error: BindingError) -> Self {
113        Self { type_: BindingValueType::BINDING_ERROR, value: None, error: Some(error) }
114    }
115
116    /// A binding error with an optional fallback value.
117    pub fn binding_error_with_fallback(error: BindingError, fallback: Option<T>) -> Self {
118        let type_ = if fallback.is_some() {
119            BindingValueType::BINDING_ERROR_WITH_FALLBACK
120        } else {
121            BindingValueType::BINDING_ERROR
122        };
123        Self { type_, value: fallback, error: Some(error) }
124    }
125
126    /// A data validation error without a fallback value.
127    pub fn data_validation_error(error: BindingError) -> Self {
128        Self { type_: BindingValueType::DATA_VALIDATION_ERROR, value: None, error: Some(error) }
129    }
130
131    /// A data validation error with an optional fallback value.
132    pub fn data_validation_error_with_fallback(error: BindingError, fallback: Option<T>) -> Self {
133        let type_ = if fallback.is_some() {
134            BindingValueType::DATA_VALIDATION_ERROR_WITH_FALLBACK
135        } else {
136            BindingValueType::DATA_VALIDATION_ERROR
137        };
138        Self { type_, value: fallback, error: Some(error) }
139    }
140
141    /// Whether the binding value carries a value (or fallback value).
142    #[inline]
143    pub fn has_value(&self) -> bool {
144        self.value.is_some()
145    }
146
147    /// Whether the binding value carries an error.
148    #[inline]
149    pub fn has_error(&self) -> bool {
150        self.error.is_some()
151    }
152
153    #[inline]
154    pub fn value_type(&self) -> BindingValueType {
155        self.type_
156    }
157
158    /// The value. Panics if there is none, see [`has_value`](Self::has_value).
159    #[inline]
160    pub fn value(&self) -> &T {
161        self.value.as_ref().expect("BindingValue has no value")
162    }
163
164    #[inline]
165    pub fn error(&self) -> Option<&BindingError> {
166        self.error.as_ref()
167    }
168
169    /// Converts into the contained value, if any.
170    #[inline]
171    pub fn into_option(self) -> Option<T> {
172        self.value
173    }
174
175    /// Returns the value, or `default` if there is none.
176    #[inline]
177    pub fn get_value_or_default(self, default: T) -> T {
178        self.value.unwrap_or(default)
179    }
180
181    /// Returns a binding value with the same error state and a new value. For
182    /// error states this turns them into their "with fallback" variants.
183    pub fn with_value(self, value: T) -> Self {
184        let type_ = match self.type_ {
185            BindingValueType::DO_NOTHING => panic!("Cannot add value to DoNothing binding value."),
186            BindingValueType::BINDING_ERROR => BindingValueType::BINDING_ERROR_WITH_FALLBACK,
187            BindingValueType::DATA_VALIDATION_ERROR => {
188                BindingValueType::DATA_VALIDATION_ERROR_WITH_FALLBACK
189            }
190            BindingValueType::UNSET_VALUE => BindingValueType::VALUE,
191            other => other,
192        };
193        Self { type_, value: Some(value), error: self.error }
194    }
195
196    /// Maps the contained value, keeping the state.
197    pub fn map<U>(self, f: impl FnOnce(T) -> U) -> BindingValue<U> {
198        BindingValue { type_: self.type_, value: self.value.map(f), error: self.error }
199    }
200}
201
202impl<T: Clone> BindingValue<T> {
203    /// Converts the binding value to an optional value: the value (or fallback value), if
204    /// there is one.
205    pub fn to_optional(&self) -> Option<T> {
206        self.value.clone()
207    }
208
209    /// Gets the value of the binding value if present, otherwise the default value: none.
210    pub fn get_value_or_default_option(&self) -> Option<T> {
211        self.value.clone()
212    }
213}
214
215impl<T: Clone + 'static> BindingValue<T> {
216    /// Gets the value of the binding value if it is present and of type `TResult`, otherwise
217    /// the default value: none.
218    pub fn get_value_or_default_as<TResult: Clone + 'static>(&self) -> Option<TResult> {
219        self.value.as_ref().and_then(|value| (value as &dyn Any).downcast_ref::<TResult>().cloned())
220    }
221
222    /// Gets the value of the binding value if it is present and of type `TResult`; none if it
223    /// is present and of another type; `default_value` if there is no value.
224    pub fn get_value_or_default_as_or<TResult: Clone + 'static>(&self, default_value: TResult) -> Option<TResult> {
225        match &self.value {
226            Some(value) => (value as &dyn Any).downcast_ref::<TResult>().cloned(),
227            None => Some(default_value),
228        }
229    }
230}
231
232impl<T: PropertyValue> BindingValue<T> {
233    /// A value as an untyped value: null for the null of a nullable type, the contained value
234    /// for a value of one (the boxing of the managed original).
235    fn box_value(value: &T) -> Option<BoxedValue> {
236        ValueTypes::normalize(Rc::new(value.clone()))
237    }
238
239    /// Converts the value to untyped representation, using the unset and do-nothing markers
240    /// and [`BindingNotification`] where appropriate.
241    ///
242    /// # Panics
243    /// Panics if the type of the binding value is none of the kinds [`BindingValueType`]
244    /// declares.
245    pub fn to_untyped(&self) -> Option<BoxedValue> {
246        let error = || self.error.clone().expect("a binding value with an error state has an error");
247        if self.type_ == BindingValueType::UNSET_VALUE {
248            Some(FerroProperty::unset_value())
249        } else if self.type_ == BindingValueType::DO_NOTHING {
250            Some(BindingOperations::do_nothing())
251        } else if self.type_ == BindingValueType::VALUE {
252            Self::box_value(self.value())
253        } else if self.type_ == BindingValueType::BINDING_ERROR {
254            Some(Rc::new(BindingNotification::with_error(error(), BindingErrorType::Error)))
255        } else if self.type_ == BindingValueType::BINDING_ERROR_WITH_FALLBACK {
256            Some(Rc::new(BindingNotification::with_error_and_fallback(
257                error(),
258                BindingErrorType::Error,
259                Self::box_value(self.value()),
260            )))
261        } else if self.type_ == BindingValueType::DATA_VALIDATION_ERROR {
262            Some(Rc::new(BindingNotification::with_error(error(), BindingErrorType::DataValidationError)))
263        } else if self.type_ == BindingValueType::DATA_VALIDATION_ERROR_WITH_FALLBACK {
264            Some(Rc::new(BindingNotification::with_error_and_fallback(
265                error(),
266                BindingErrorType::DataValidationError,
267                Self::box_value(self.value()),
268            )))
269        } else {
270            panic!("Invalid BindingValueType.")
271        }
272    }
273
274    /// Creates a binding value from an untyped value: the unset or do-nothing marker, a
275    /// [`BindingNotification`], or a value that converts implicitly to `T`.
276    pub fn from_untyped(value: Option<&BoxedValue>) -> Self {
277        Self::from_untyped_with_type(value, ValueType::of::<T>())
278    }
279
280    /// Creates a binding value from an untyped value, converting the value to `target_type`.
281    ///
282    /// A value that does not convert to `target_type`, or whose conversion is not a `T`,
283    /// gives a binding error. Null converts only to a target type that has a null value of
284    /// its own (a nullable form): `T` itself is never null here.
285    // Deviation (DEVIATIONS.md, Bindings): upstream converts null to a `T` that is a reference
286    // type; here null converts only to a nullable form with a registered null value.
287    pub fn from_untyped_with_type(value: Option<&BoxedValue>, target_type: ValueType) -> Self {
288        if value.is_some_and(|value| value.is::<UnsetValueType>()) {
289            return Self::unset();
290        } else if value.is_some_and(|value| value.is::<DoNothingType>()) {
291            return Self::do_nothing();
292        }
293
294        let mut type_ = BindingValueType::VALUE;
295        let mut v: Option<T> = None;
296        let mut error: Option<BindingError> = None;
297        let mut errors: Option<Vec<BindingError>> = None;
298        let mut value: Option<BoxedValue> = value.cloned();
299
300        let notification = value.as_ref().and_then(|value| {
301            value.downcast_ref::<BindingNotification>().map(|n| (n.error(), n.error_type(), n.has_value(), n.value()))
302        });
303        if let Some((notification_error, error_type, has_value, notification_value)) = notification {
304            error = notification_error;
305            type_ = match error_type {
306                BindingErrorType::Error => BindingValueType::BINDING_ERROR,
307                BindingErrorType::DataValidationError => BindingValueType::DATA_VALIDATION_ERROR,
308                BindingErrorType::None => BindingValueType::VALUE,
309            };
310
311            if has_value {
312                type_ |= BindingValueType::HAS_VALUE;
313            }
314            value = notification_value;
315        }
316
317        if type_.contains(BindingValueType::HAS_VALUE) {
318            let typed = ValueTypes::try_convert(value.as_ref(), target_type)
319                .flatten()
320                .and_then(|typed| typed.downcast_ref::<T>().cloned());
321            match typed {
322                Some(typed) => v = Some(typed),
323                None => {
324                    let e = BindingError::message(format!(
325                        "Unable to convert object '{}' of type '{}' to type '{}'.",
326                        ValueTypes::to_display_string(value.as_ref()),
327                        value
328                            .as_ref()
329                            .map(|value| ValueTypes::type_full_name(ValueType::of_value(&**value)))
330                            .unwrap_or_default(),
331                        ValueTypes::type_full_name(target_type)
332                    ));
333
334                    match error.take() {
335                        None => error = Some(e),
336                        Some(first) => errors.get_or_insert_with(|| vec![first]).push(e),
337                    }
338
339                    type_ = BindingValueType::BINDING_ERROR;
340                }
341            }
342        }
343
344        if let Some(errors) = errors {
345            error = Some(BindingError::new(AggregateError::new(errors)));
346        }
347
348        Self { type_, value: v, error }
349    }
350}
351
352/// The text of the binding value: `Error: <message>` for an error, the text of the value, or
353/// `(null)` without a value.
354// Deviation (DEVIATIONS.md, Bindings): without a value upstream prints the default of `T`
355// (`0` for a number); there is no default here, so the text is `(null)`.
356impl<T: fmt::Display> fmt::Display for BindingValue<T> {
357    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
358        match (&self.error, &self.value) {
359            (Some(error), _) => write!(f, "Error: {error}"),
360            (None, Some(value)) => fmt::Display::fmt(value, f),
361            (None, None) => f.write_str("(null)"),
362        }
363    }
364}
365
366impl<T> From<T> for BindingValue<T> {
367    #[inline]
368    fn from(value: T) -> Self {
369        Self::new(value)
370    }
371}
372
373impl<T> From<Option<T>> for BindingValue<T> {
374    /// `None` maps to the unset state.
375    fn from(value: Option<T>) -> Self {
376        match value {
377            Some(v) => Self::new(v),
378            None => Self::unset(),
379        }
380    }
381}
382
383#[cfg(test)]
384mod tests {
385    // Not ports: the upstream project has no tests of this type.
386    use super::*;
387
388    fn boxed<T: PartialEq + 'static>(value: T) -> BoxedValue {
389        Rc::new(value)
390    }
391
392    fn notification(value: &Option<BoxedValue>) -> &BindingNotification {
393        value.as_ref().and_then(|value| value.downcast_ref::<BindingNotification>()).expect("a binding notification")
394    }
395
396    #[test]
397    fn to_untyped_gives_the_value_or_the_marker_of_the_state() {
398        let value = BindingValue::new(5).to_untyped();
399        assert_eq!(Some(&5), value.as_ref().and_then(|value| value.downcast_ref::<i32>()));
400        assert!(BindingValue::<i32>::unset().to_untyped().is_some_and(|value| value.is::<UnsetValueType>()));
401        assert!(BindingValue::<i32>::do_nothing().to_untyped().is_some_and(|value| value.is::<DoNothingType>()));
402    }
403
404    #[test]
405    fn to_untyped_gives_a_notification_for_an_error() {
406        let untyped = BindingValue::<i32>::binding_error(BindingError::message("broken")).to_untyped();
407        let n = notification(&untyped);
408        assert_eq!(BindingErrorType::Error, n.error_type());
409        assert!(!n.has_value());
410        assert_eq!(Some(String::from("broken")), n.error().map(|error| error.to_string()));
411
412        let untyped =
413            BindingValue::data_validation_error_with_fallback(BindingError::message("invalid"), Some(7)).to_untyped();
414        let n = notification(&untyped);
415        assert_eq!(BindingErrorType::DataValidationError, n.error_type());
416        assert!(n.has_value());
417        assert_eq!(Some(7), n.value().and_then(|value| value.downcast_ref::<i32>().copied()));
418    }
419
420    #[test]
421    fn from_untyped_reads_values_and_markers() {
422        assert_eq!(BindingValue::new(5), BindingValue::<i32>::from_untyped(Some(&boxed(5))));
423        assert_eq!(BindingValue::<i32>::unset(), BindingValue::from_untyped(Some(&FerroProperty::unset_value())));
424        assert_eq!(BindingValue::<i32>::do_nothing(), BindingValue::from_untyped(Some(&BindingOperations::do_nothing())));
425    }
426
427    #[test]
428    fn from_untyped_reads_a_notification() {
429        let error = BindingError::message("invalid");
430        let n: BoxedValue = Rc::new(BindingNotification::with_error_and_fallback(
431            error.clone(),
432            BindingErrorType::DataValidationError,
433            Some(boxed(7)),
434        ));
435        let value = BindingValue::<i32>::from_untyped(Some(&n));
436        assert_eq!(BindingValueType::DATA_VALIDATION_ERROR_WITH_FALLBACK, value.value_type());
437        assert_eq!(7, *value.value());
438        assert_eq!(Some(&error), value.error());
439
440        let n: BoxedValue = Rc::new(BindingNotification::with_error(error.clone(), BindingErrorType::Error));
441        let value = BindingValue::<i32>::from_untyped(Some(&n));
442        assert_eq!(BindingValueType::BINDING_ERROR, value.value_type());
443        assert!(!value.has_value());
444        assert_eq!(Some(&error), value.error());
445    }
446
447    #[test]
448    fn from_untyped_gives_a_binding_error_for_a_value_of_another_type() {
449        #[derive(Clone, PartialEq)]
450        struct Unrelated;
451
452        let value = BindingValue::<i32>::from_untyped(Some(&boxed(Unrelated)));
453        assert_eq!(BindingValueType::BINDING_ERROR, value.value_type());
454        assert!(!value.has_value());
455        let message = value.error().map(|error| error.to_string()).unwrap_or_default();
456        assert!(message.starts_with("Unable to convert object '"), "{message}");
457
458        // The error of a notification is kept next to the conversion error.
459        let n: BoxedValue = Rc::new(BindingNotification::with_error_and_fallback(
460            BindingError::message("invalid"),
461            BindingErrorType::DataValidationError,
462            Some(boxed(Unrelated)),
463        ));
464        let value = BindingValue::<i32>::from_untyped(Some(&n));
465        assert_eq!(BindingValueType::BINDING_ERROR, value.value_type());
466        let count = value
467            .error()
468            .and_then(|error| error.inner().downcast_ref::<AggregateError>())
469            .map(|error| error.inner_errors().len());
470        assert_eq!(Some(2), count);
471    }
472
473    #[test]
474    fn to_string_formats_the_error_the_value_or_null() {
475        assert_eq!("5", BindingValue::new(5).to_string());
476        assert_eq!("(null)", BindingValue::<i32>::unset().to_string());
477        assert_eq!("Error: broken", BindingValue::<i32>::binding_error(BindingError::message("broken")).to_string());
478        assert_eq!(
479            "Error: broken",
480            BindingValue::binding_error_with_fallback(BindingError::message("broken"), Some(1)).to_string()
481        );
482    }
483
484    #[test]
485    fn the_value_or_a_default_can_be_read() {
486        assert_eq!(Some(5), BindingValue::new(5).to_optional());
487        assert_eq!(None, BindingValue::<i32>::unset().to_optional());
488        assert_eq!(Some(5), BindingValue::new(5).get_value_or_default_option());
489        assert_eq!(None, BindingValue::<i32>::do_nothing().get_value_or_default_option());
490        assert_eq!(9, BindingValue::<i32>::unset().get_value_or_default(9));
491
492        assert_eq!(Some(5), BindingValue::new(5).get_value_or_default_as::<i32>());
493        assert_eq!(None, BindingValue::new(5).get_value_or_default_as::<String>());
494        assert_eq!(None, BindingValue::<i32>::unset().get_value_or_default_as::<i32>());
495        assert_eq!(Some(5), BindingValue::new(5).get_value_or_default_as_or(9));
496        assert_eq!(None, BindingValue::new(5).get_value_or_default_as_or(String::from("x")));
497        assert_eq!(Some(9), BindingValue::<i32>::unset().get_value_or_default_as_or(9));
498    }
499}