Skip to main content

ferroui_base/data/
binding_notification.rs

1use crate::data::core::ValueTypes;
2use crate::data::{BindingError, BindingOperations, BindingValueType};
3use crate::{BoxedValue, DoNothingType, FerroProperty, UnsetValueType};
4use std::cell::{Cell, RefCell};
5use std::fmt;
6use std::rc::Rc;
7
8/// Defines the types of binding errors for a [`BindingNotification`].
9#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
10#[repr(i32)]
11pub enum BindingErrorType {
12    /// There was no error.
13    None = 0,
14    /// There was a binding error.
15    Error = 1,
16    /// There was a data validation error.
17    DataValidationError = 2,
18}
19
20impl BindingErrorType {
21    /// The binding value type that represents this error type.
22    pub fn to_binding_value_type(self) -> BindingValueType {
23        match self {
24            BindingErrorType::Error => BindingValueType::BINDING_ERROR,
25            BindingErrorType::DataValidationError => BindingValueType::DATA_VALIDATION_ERROR,
26            BindingErrorType::None => BindingValueType::VALUE,
27        }
28    }
29}
30
31/// Several binding errors that occurred on the same binding notification.
32#[derive(Clone, Debug)]
33pub struct AggregateError {
34    inner_errors: Vec<BindingError>,
35}
36
37impl AggregateError {
38    /// Creates an error that aggregates `inner_errors`.
39    pub fn new(inner_errors: Vec<BindingError>) -> Self {
40        Self { inner_errors }
41    }
42
43    /// The aggregated errors.
44    pub fn inner_errors(&self) -> &[BindingError] {
45        &self.inner_errors
46    }
47}
48
49impl fmt::Display for AggregateError {
50    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
51        f.write_str("One or more errors occurred.")?;
52        for error in &self.inner_errors {
53            write!(f, " ({error})")?;
54        }
55        Ok(())
56    }
57}
58
59impl std::error::Error for AggregateError {}
60
61/// Represents a binding notification that can be a valid binding value, or a
62/// binding or data validation error.
63///
64/// This type is very similar to [`BindingValue`](crate::data::BindingValue),
65/// but where `BindingValue` is used by typed bindings, this type is used to
66/// hold binding and data validation errors in untyped bindings.
67///
68/// A notification has reference semantics: it is shared as
69/// `Rc<BindingNotification>` (which converts to a [`BoxedValue`] holding the
70/// notification) and is modified in place.
71pub struct BindingNotification {
72    value: RefCell<Option<BoxedValue>>,
73    error: RefCell<Option<BindingError>>,
74    error_type: Cell<BindingErrorType>,
75}
76
77impl BindingNotification {
78    /// A binding notification representing the null value.
79    pub fn null() -> Rc<BindingNotification> {
80        crate::ferro_thread_local! {
81            static NULL: Rc<BindingNotification> = Rc::new(BindingNotification::new(None));
82        }
83        NULL.with(Rc::clone)
84    }
85
86    /// A binding notification representing the unset value marker.
87    pub fn unset_value() -> Rc<BindingNotification> {
88        crate::ferro_thread_local! {
89            static UNSET_VALUE: Rc<BindingNotification> =
90                Rc::new(BindingNotification::new(Some(FerroProperty::unset_value())));
91        }
92        UNSET_VALUE.with(Rc::clone)
93    }
94
95    /// Creates a binding notification for a binding value.
96    pub fn new(value: Option<BoxedValue>) -> Self {
97        Self { value: RefCell::new(value), error: RefCell::new(None), error_type: Cell::new(BindingErrorType::None) }
98    }
99
100    /// Creates a binding notification for a binding error.
101    ///
102    /// Panics if `error_type` is [`BindingErrorType::None`].
103    pub fn with_error(error: BindingError, error_type: BindingErrorType) -> Self {
104        Self::with_error_and_fallback(error, error_type, Some(FerroProperty::unset_value()))
105    }
106
107    /// Creates a binding notification for a binding error with a fallback
108    /// value.
109    ///
110    /// Panics if `error_type` is [`BindingErrorType::None`].
111    pub fn with_error_and_fallback(
112        error: BindingError,
113        error_type: BindingErrorType,
114        fallback_value: Option<BoxedValue>,
115    ) -> Self {
116        assert!(error_type != BindingErrorType::None, "'error_type' may not be None");
117        Self {
118            value: RefCell::new(fallback_value),
119            error: RefCell::new(Some(error)),
120            error_type: Cell::new(error_type),
121        }
122    }
123
124    /// Gets the value that should be passed to the target when
125    /// [`has_value`](Self::has_value) is true.
126    ///
127    /// If this is read when `has_value` is false then it returns the unset
128    /// value marker.
129    pub fn value(&self) -> Option<BoxedValue> {
130        self.value.borrow().clone()
131    }
132
133    /// Gets a value indicating whether [`value`](Self::value) should be
134    /// pushed to the target.
135    pub fn has_value(&self) -> bool {
136        !BindingOperations::is_unset(self.value.borrow().as_ref())
137    }
138
139    /// Gets the error that occurred on the source, if any.
140    pub fn error(&self) -> Option<BindingError> {
141        self.error.borrow().clone()
142    }
143
144    /// Sets the error that occurred on the source.
145    pub fn set_error(&self, error: Option<BindingError>) {
146        *self.error.borrow_mut() = error;
147    }
148
149    /// Gets the type of error that [`error`](Self::error) represents, if any.
150    pub fn error_type(&self) -> BindingErrorType {
151        self.error_type.get()
152    }
153
154    /// Sets the type of error that [`error`](Self::error) represents.
155    pub fn set_error_type(&self, error_type: BindingErrorType) {
156        self.error_type.set(error_type);
157    }
158
159    /// Gets a value from an untyped value that may be a binding notification.
160    ///
161    /// If `o` is a binding notification then returns the binding
162    /// notification's [`value`](Self::value). If not, returns the value
163    /// unchanged.
164    pub fn extract_value(o: Option<&BoxedValue>) -> Option<BoxedValue> {
165        match o.and_then(|o| o.downcast_ref::<BindingNotification>()) {
166            Some(notification) => notification.value(),
167            None => o.cloned(),
168        }
169    }
170
171    /// Updates the value of an untyped value that may be a binding
172    /// notification.
173    ///
174    /// If `o` is a binding notification then sets its value to `value` and
175    /// returns the notification; otherwise returns `value`. If `value` is a
176    /// binding notification then its value is extracted first.
177    pub fn update_value(o: Option<&BoxedValue>, value: Option<&BoxedValue>) -> Option<BoxedValue> {
178        if let Some(o) = o {
179            if let Some(notification) = o.downcast_ref::<BindingNotification>() {
180                notification.set_value(Self::extract_value(value));
181                return Some(o.clone());
182            }
183        }
184        value.cloned()
185    }
186
187    /// Gets an error from an untyped value that may be a binding
188    /// notification.
189    ///
190    /// If `o` is a binding notification then returns the binding
191    /// notification's [`error`](Self::error) (boxed as a [`BindingError`]).
192    /// If not, returns the value unchanged.
193    pub fn extract_error(o: Option<&BoxedValue>) -> Option<BoxedValue> {
194        match o.and_then(|o| o.downcast_ref::<BindingNotification>()) {
195            Some(notification) => notification.error().map(|error| Rc::new(error) as BoxedValue),
196            None => o.cloned(),
197        }
198    }
199
200    /// Converts the notification to a typed binding value (the notification
201    /// part of the reference's untyped-to-typed binding value conversion).
202    ///
203    /// The notification's value must hold exactly `T`; a value that cannot be
204    /// converted produces a binding error, aggregated with the notification's
205    /// own error if it has one. `target_type` names `T` in that error.
206    pub(crate) fn to_binding_value<T: Clone + 'static>(&self, target_type: &str) -> crate::data::BindingValue<T> {
207        use crate::data::BindingValue;
208
209        // Only the downcast and the construction of the result depend on
210        // `T`; the rest is shared by every instantiation.
211        let (error, error_type, value) = match self.binding_value_parts() {
212            Ok(parts) => parts,
213            Err((error, BindingErrorType::DataValidationError)) => return BindingValue::data_validation_error(error),
214            Err((error, _)) => return BindingValue::binding_error(error),
215        };
216
217        let typed = value.as_ref().and_then(|value| value.downcast_ref::<T>().cloned());
218
219        match typed {
220            Some(typed) => match (error_type, error) {
221                (BindingErrorType::Error, Some(error)) => BindingValue::binding_error_with_fallback(error, Some(typed)),
222                (BindingErrorType::DataValidationError, Some(error)) => {
223                    BindingValue::data_validation_error_with_fallback(error, Some(typed))
224                }
225                _ => BindingValue::new(typed),
226            },
227            None => BindingValue::binding_error(Self::conversion_error(error, value.is_some(), target_type)),
228        }
229    }
230
231    /// The error, the error type and the value of the notification, or the
232    /// error a notification without a value stands for.
233    #[allow(clippy::type_complexity)]
234    fn binding_value_parts(
235        &self,
236    ) -> Result<(Option<BindingError>, BindingErrorType, Option<BoxedValue>), (BindingError, BindingErrorType)> {
237        let error = self.error();
238        let error_type = self.error_type();
239
240        // As in the reference, a notification without an error always counts
241        // as carrying a value, even when its value is the unset marker.
242        if !self.has_value() && error_type != BindingErrorType::None {
243            let error = error.unwrap_or_else(|| BindingError::message("Binding notification has no error."));
244            return Err((error, error_type));
245        }
246
247        Ok((error, error_type, self.value()))
248    }
249
250    /// The error of a notification whose value is not of the type asked
251    /// for, aggregated with the error of the notification if it has one.
252    fn conversion_error(error: Option<BindingError>, has_value: bool, target_type: &str) -> BindingError {
253        let e = BindingError::message(format!(
254            "Unable to convert object '{}' to type '{target_type}'.",
255            if has_value { "(object)" } else { "(null)" }
256        ));
257        match error {
258            Some(error) => BindingError::new(AggregateError::new(vec![error, e])),
259            None => e,
260        }
261    }
262
263    /// Adds an error to the binding notification.
264    ///
265    /// Panics if `error_type` is [`BindingErrorType::None`].
266    pub fn add_error(&self, e: BindingError, error_type: BindingErrorType) {
267        assert!(error_type != BindingErrorType::None, "BindingErrorType may not be None");
268
269        let current = self.error.borrow_mut().take();
270        let error = match current {
271            Some(current) => BindingError::new(AggregateError::new(vec![current, e])),
272            None => e,
273        };
274        *self.error.borrow_mut() = Some(error);
275
276        if error_type == BindingErrorType::Error || self.error_type.get() == BindingErrorType::Error {
277            self.error_type.set(BindingErrorType::Error);
278        }
279    }
280
281    /// Removes the [`value`](Self::value) and makes
282    /// [`has_value`](Self::has_value) return false.
283    pub fn clear_value(&self) {
284        *self.value.borrow_mut() = Some(FerroProperty::unset_value());
285    }
286
287    /// Sets the [`value`](Self::value).
288    pub fn set_value(&self, value: Option<BoxedValue>) {
289        *self.value.borrow_mut() = value;
290    }
291
292    /// Errors are equal if they are the same error, or have the same kind and
293    /// message.
294    fn error_equals(a: Option<&BindingError>, b: Option<&BindingError>) -> bool {
295        match (a, b) {
296            (None, None) => true,
297            (Some(a), Some(b)) => a == b || (format!("{a:?}") == format!("{b:?}") && a.to_string() == b.to_string()),
298            _ => false,
299        }
300    }
301}
302
303/// The text of an untyped value: empty for null.
304fn value_text(value: Option<&BoxedValue>) -> String {
305    let Some(value) = value else {
306        return String::new();
307    };
308    if let Some(marker) = value.downcast_ref::<UnsetValueType>() {
309        return marker.to_string();
310    }
311    if let Some(marker) = value.downcast_ref::<DoNothingType>() {
312        return marker.to_string();
313    }
314    if let Some(notification) = value.downcast_ref::<BindingNotification>() {
315        return notification.to_string();
316    }
317    ValueTypes::to_display_string(Some(value))
318}
319
320impl PartialEq for BindingNotification {
321    /// Compares two binding notifications for equality.
322    fn eq(&self, other: &Self) -> bool {
323        if std::ptr::eq(self, other) {
324            return true;
325        }
326
327        let a_value = self.value();
328        let b_value = other.value();
329        let a_has_value = self.has_value();
330        a_has_value == other.has_value()
331            && self.error_type() == other.error_type()
332            && (!a_has_value || ValueTypes::identity_equals(a_value.as_ref(), b_value.as_ref()))
333            && (self.error_type() == BindingErrorType::None
334                || Self::error_equals(self.error().as_ref(), other.error().as_ref()))
335    }
336}
337
338impl fmt::Display for BindingNotification {
339    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
340        let value = self.value();
341        let value = value_text(value.as_ref());
342        let error = self.error().map(|e| e.to_string()).unwrap_or_default();
343        match self.error_type() {
344            BindingErrorType::None => write!(f, "{{Value: {value}}}"),
345            error_type if self.has_value() => write!(f, "{{{error_type:?}: {error}, Fallback: {value}}}"),
346            error_type => write!(f, "{{{error_type:?}: {error}}}"),
347        }
348    }
349}
350
351impl fmt::Debug for BindingNotification {
352    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
353        fmt::Display::fmt(self, f)
354    }
355}
356
357#[cfg(test)]
358mod tests {
359    //! Upstream has no dedicated tests for this type; these cover the port.
360
361    use super::*;
362
363    fn boxed<T: PartialEq + 'static>(value: T) -> Option<BoxedValue> {
364        Some(Rc::new(value))
365    }
366
367    fn error(message: &str) -> BindingError {
368        BindingError::message(message)
369    }
370
371    #[test]
372    fn null_and_unset_value_statics() {
373        assert!(BindingNotification::null().has_value());
374        assert!(BindingNotification::null().value().is_none());
375        assert_eq!(BindingNotification::null().error_type(), BindingErrorType::None);
376        assert!(Rc::ptr_eq(&BindingNotification::null(), &BindingNotification::null()));
377
378        assert!(!BindingNotification::unset_value().has_value());
379        assert!(BindingOperations::is_unset(BindingNotification::unset_value().value().as_ref()));
380        assert!(Rc::ptr_eq(&BindingNotification::unset_value(), &BindingNotification::unset_value()));
381    }
382
383    #[test]
384    fn value_notification_has_value_and_no_error() {
385        let target = BindingNotification::new(boxed(42i32));
386
387        assert!(target.has_value());
388        assert_eq!(target.value().and_then(|v| v.downcast_ref::<i32>().copied()), Some(42));
389        assert!(target.error().is_none());
390        assert_eq!(target.error_type(), BindingErrorType::None);
391    }
392
393    #[test]
394    fn error_notification_has_no_value() {
395        let e = error("foo");
396        let target = BindingNotification::with_error(e.clone(), BindingErrorType::Error);
397
398        assert!(!target.has_value());
399        assert!(BindingOperations::is_unset(target.value().as_ref()));
400        assert_eq!(target.error(), Some(e));
401        assert_eq!(target.error_type(), BindingErrorType::Error);
402    }
403
404    #[test]
405    fn error_notification_with_fallback_has_value() {
406        let target =
407            BindingNotification::with_error_and_fallback(error("foo"), BindingErrorType::DataValidationError, boxed(1i32));
408
409        assert!(target.has_value());
410        assert_eq!(target.error_type(), BindingErrorType::DataValidationError);
411
412        let null_fallback =
413            BindingNotification::with_error_and_fallback(error("foo"), BindingErrorType::Error, None);
414        assert!(null_fallback.has_value());
415        assert!(null_fallback.value().is_none());
416    }
417
418    #[test]
419    #[should_panic(expected = "may not be None")]
420    fn error_notification_rejects_error_type_none() {
421        let _ = BindingNotification::with_error(error("foo"), BindingErrorType::None);
422    }
423
424    #[test]
425    #[should_panic(expected = "may not be None")]
426    fn add_error_rejects_error_type_none() {
427        BindingNotification::new(None).add_error(error("foo"), BindingErrorType::None);
428    }
429
430    #[test]
431    fn equality_compares_values() {
432        assert_eq!(BindingNotification::new(boxed(1i32)), BindingNotification::new(boxed(1i32)));
433        assert_ne!(BindingNotification::new(boxed(1i32)), BindingNotification::new(boxed(2i32)));
434        assert_ne!(BindingNotification::new(boxed(1i32)), BindingNotification::new(boxed(1i64)));
435        assert_ne!(BindingNotification::new(boxed(1i32)), BindingNotification::new(None));
436        assert_eq!(BindingNotification::new(None), BindingNotification::new(None));
437        assert_eq!(*BindingNotification::null(), BindingNotification::new(None));
438        assert_ne!(*BindingNotification::null(), *BindingNotification::unset_value());
439        assert_eq!(*BindingNotification::unset_value(), BindingNotification::new(Some(FerroProperty::unset_value())));
440    }
441
442    #[test]
443    fn equality_compares_errors_by_kind_and_message() {
444        let a = BindingNotification::with_error(error("foo"), BindingErrorType::Error);
445        let b = BindingNotification::with_error(error("foo"), BindingErrorType::Error);
446        let other_message = BindingNotification::with_error(error("bar"), BindingErrorType::Error);
447        let other_type = BindingNotification::with_error(error("foo"), BindingErrorType::DataValidationError);
448        let other_kind =
449            BindingNotification::with_error(BindingError::new(AggregateError::new(Vec::new())), BindingErrorType::Error);
450        let with_fallback =
451            BindingNotification::with_error_and_fallback(error("foo"), BindingErrorType::Error, boxed(1i32));
452
453        assert_eq!(a, b);
454        assert_ne!(a, other_message);
455        assert_ne!(a, other_type);
456        assert_ne!(a, other_kind);
457        assert_ne!(a, with_fallback);
458    }
459
460    #[test]
461    fn equality_ignores_error_when_error_type_is_none() {
462        let a = BindingNotification::new(boxed(1i32));
463        let b = BindingNotification::new(boxed(1i32));
464        b.set_error(Some(error("ignored")));
465
466        assert_eq!(a, b);
467    }
468
469    #[test]
470    fn can_be_carried_and_compared_as_untyped_value() {
471        let a: BoxedValue = Rc::new(BindingNotification::new(boxed(1i32)));
472        let b: BoxedValue = Rc::new(BindingNotification::new(boxed(1i32)));
473        let c: BoxedValue = Rc::new(1i32);
474
475        assert!(ValueTypes::identity_equals(Some(&a), Some(&b)));
476        assert!(!ValueTypes::identity_equals(Some(&a), Some(&c)));
477    }
478
479    #[test]
480    fn extract_value_returns_value_of_notification_or_the_value_itself() {
481        let notification: BoxedValue = Rc::new(BindingNotification::new(boxed(5i32)));
482        let plain: BoxedValue = Rc::new(6i32);
483
484        let extracted = BindingNotification::extract_value(Some(&notification));
485        assert_eq!(extracted.and_then(|v| v.downcast_ref::<i32>().copied()), Some(5));
486        let extracted = BindingNotification::extract_value(Some(&plain));
487        assert!(extracted.is_some_and(|v| Rc::ptr_eq(&v, &plain)));
488        assert!(BindingNotification::extract_value(None).is_none());
489
490        let null: BoxedValue = BindingNotification::null();
491        assert!(BindingNotification::extract_value(Some(&null)).is_none());
492    }
493
494    #[test]
495    fn extract_error_returns_error_of_notification_or_the_value_itself() {
496        let e = error("foo");
497        let notification: BoxedValue = Rc::new(BindingNotification::with_error(e.clone(), BindingErrorType::Error));
498        let plain: BoxedValue = Rc::new(6i32);
499
500        let extracted = BindingNotification::extract_error(Some(&notification));
501        assert_eq!(extracted.and_then(|v| v.downcast_ref::<BindingError>().cloned()), Some(e));
502        let extracted = BindingNotification::extract_error(Some(&plain));
503        assert!(extracted.is_some_and(|v| Rc::ptr_eq(&v, &plain)));
504        assert!(BindingNotification::extract_error(None).is_none());
505
506        let no_error: BoxedValue = Rc::new(BindingNotification::new(boxed(1i32)));
507        assert!(BindingNotification::extract_error(Some(&no_error)).is_none());
508    }
509
510    #[test]
511    fn update_value_updates_notification_in_place() {
512        let notification: BoxedValue = Rc::new(BindingNotification::with_error(error("foo"), BindingErrorType::Error));
513        let new_value: BoxedValue = Rc::new(BindingNotification::new(boxed(7i32)));
514
515        let result = BindingNotification::update_value(Some(&notification), Some(&new_value)).expect("a value");
516
517        assert!(Rc::ptr_eq(&result, &notification));
518        let updated = notification.downcast_ref::<BindingNotification>().expect("a notification");
519        assert!(updated.has_value());
520        assert_eq!(updated.value().and_then(|v| v.downcast_ref::<i32>().copied()), Some(7));
521        assert_eq!(updated.error_type(), BindingErrorType::Error);
522    }
523
524    #[test]
525    fn update_value_returns_value_when_not_a_notification() {
526        let plain: BoxedValue = Rc::new(6i32);
527        let value: BoxedValue = Rc::new(7i32);
528
529        let result = BindingNotification::update_value(Some(&plain), Some(&value)).expect("a value");
530        assert!(Rc::ptr_eq(&result, &value));
531        let result = BindingNotification::update_value(None, Some(&value)).expect("a value");
532        assert!(Rc::ptr_eq(&result, &value));
533    }
534
535    #[test]
536    fn add_error_sets_error() {
537        let target = BindingNotification::new(boxed(1i32));
538        let e = error("foo");
539
540        target.add_error(e.clone(), BindingErrorType::Error);
541
542        assert_eq!(target.error(), Some(e));
543        assert_eq!(target.error_type(), BindingErrorType::Error);
544        assert!(target.has_value());
545    }
546
547    #[test]
548    fn add_error_aggregates_errors() {
549        let target = BindingNotification::with_error(error("first"), BindingErrorType::DataValidationError);
550
551        target.add_error(error("second"), BindingErrorType::Error);
552
553        assert_eq!(target.error_type(), BindingErrorType::Error);
554        let e = target.error().expect("an error");
555        assert_eq!(e.to_string(), "One or more errors occurred. (first) (second)");
556        let aggregate = e.inner().downcast_ref::<AggregateError>().expect("an aggregate error");
557        assert_eq!(aggregate.inner_errors().len(), 2);
558    }
559
560    #[test]
561    fn add_error_keeps_error_type_unless_either_is_error() {
562        let target = BindingNotification::with_error(error("first"), BindingErrorType::Error);
563        target.add_error(error("second"), BindingErrorType::DataValidationError);
564        assert_eq!(target.error_type(), BindingErrorType::Error);
565
566        let target = BindingNotification::with_error(error("first"), BindingErrorType::DataValidationError);
567        target.add_error(error("second"), BindingErrorType::DataValidationError);
568        assert_eq!(target.error_type(), BindingErrorType::DataValidationError);
569
570        // As upstream: a data validation error added to a notification
571        // without an error leaves the error type unchanged.
572        let target = BindingNotification::new(boxed(1i32));
573        target.add_error(error("first"), BindingErrorType::DataValidationError);
574        assert_eq!(target.error_type(), BindingErrorType::None);
575        assert!(target.error().is_some());
576    }
577
578    #[test]
579    fn clear_value_and_set_value() {
580        let target = BindingNotification::new(boxed(1i32));
581
582        target.clear_value();
583        assert!(!target.has_value());
584
585        target.set_value(None);
586        assert!(target.has_value());
587        assert!(target.value().is_none());
588    }
589
590    #[test]
591    fn to_string_formats_value_and_error() {
592        assert_eq!(BindingNotification::new(boxed(42i32)).to_string(), "{Value: 42}");
593        assert_eq!(BindingNotification::new(None).to_string(), "{Value: }");
594        assert_eq!(BindingNotification::unset_value().to_string(), "{Value: (unset)}");
595        assert_eq!(
596            BindingNotification::with_error(error("foo"), BindingErrorType::Error).to_string(),
597            "{Error: foo}"
598        );
599        assert_eq!(
600            BindingNotification::with_error_and_fallback(
601                error("foo"),
602                BindingErrorType::DataValidationError,
603                boxed(String::from("fallback"))
604            )
605            .to_string(),
606            "{DataValidationError: foo, Fallback: fallback}"
607        );
608    }
609
610    #[test]
611    fn error_type_converts_to_binding_value_type() {
612        assert_eq!(BindingErrorType::Error.to_binding_value_type(), BindingValueType::BINDING_ERROR);
613        assert_eq!(
614            BindingErrorType::DataValidationError.to_binding_value_type(),
615            BindingValueType::DATA_VALIDATION_ERROR
616        );
617        assert_eq!(BindingErrorType::None.to_binding_value_type(), BindingValueType::VALUE);
618    }
619}