Skip to main content

ferroui_base/data/core/plugins/
indei_validation_plugin.rs

1use super::{AccessorListener, DataValidationBase, IDataValidationPlugin, IPropertyAccessor};
2use crate::data::core::{ValueType, WeakValue};
3use crate::data::model::ModelTypes;
4use crate::data::{
5    AggregateException, BindingError, BindingErrorType, BindingNotification, BindingPriority,
6    DataValidationException,
7};
8use crate::reactive::IDisposable;
9use crate::BoxedValue;
10use std::cell::Cell;
11use std::rc::{Rc, Weak};
12
13/// Validates properties on model objects that implement
14/// [`INotifyDataErrorInfo`](crate::data::model::INotifyDataErrorInfo).
15pub struct IndeiValidationPlugin;
16
17impl IDataValidationPlugin for IndeiValidationPlugin {
18    fn match_(&self, reference: &WeakValue, _member_name: &str) -> bool {
19        instance(reference).is_some_and(|t| ModelTypes::find(&*t).is_some_and(|m| m.as_notify_data_error_info(&*t).is_some()))
20    }
21
22    fn start(
23        &self,
24        reference: &WeakValue,
25        property_name: &str,
26        inner: Rc<dyn IPropertyAccessor>,
27    ) -> Rc<dyn IPropertyAccessor> {
28        Rc::new_cyclic(|this| Validator {
29            this: this.clone(),
30            validation: DataValidationBase::new(inner),
31            reference: reference.clone(),
32            name: property_name.into(),
33            token: Cell::new(None),
34        })
35    }
36}
37
38/// The object the reference of a binding refers to, as the object whose contracts are
39/// looked up: the object form of a reference type held as its handle (a view model a
40/// binding read from a property typed with the handle), the value itself otherwise.
41fn instance(reference: &WeakValue) -> Option<BoxedValue> {
42    reference.upgrade().map(|target| super::markup_members::instance_of(&target))
43}
44
45struct Validator {
46    this: Weak<Validator>,
47    validation: DataValidationBase,
48    reference: WeakValue,
49    name: Box<str>,
50    token: Cell<Option<u64>>,
51}
52
53impl Validator {
54    fn create_binding_notification(&self, value: Option<BoxedValue>) -> BoxedValue {
55        if let Some(target) = instance(&self.reference) {
56            if let Some(model) = ModelTypes::find(&*target) {
57                if let Some(indei) = model.as_notify_data_error_info(&*target) {
58                    let errors = indei.get_errors(Some(&self.name));
59                    if !errors.is_empty() {
60                        return Rc::new(BindingNotification::with_error_and_fallback(
61                            generate_exception(errors),
62                            BindingErrorType::DataValidationError,
63                            value,
64                        ));
65                    }
66                }
67            }
68        }
69        Rc::new(BindingNotification::new(value))
70    }
71
72    fn remove_handler(&self) {
73        if let Some(token) = self.token.take() {
74            if let Some(target) = instance(&self.reference) {
75                if let Some(model) = ModelTypes::find(&*target) {
76                    if let Some(indei) = model.as_notify_data_error_info(&*target) {
77                        indei.errors_changed().remove(token);
78                    }
79                }
80            }
81        }
82    }
83}
84
85fn generate_exception(mut errors: Vec<BoxedValue>) -> BindingError {
86    if errors.len() == 1 {
87        BindingError::new(DataValidationException::new(errors.pop()))
88    } else {
89        BindingError::new(AggregateException::new(
90            errors.into_iter().map(|e| DataValidationException::new(Some(e))).collect(),
91        ))
92    }
93}
94
95impl IDisposable for Validator {
96    fn dispose(&self) {
97        if self.validation.base.is_subscribed() {
98            self.unsubscribe();
99        }
100    }
101}
102
103impl IPropertyAccessor for Validator {
104    fn property_type(&self) -> Option<ValueType> {
105        self.validation.inner.property_type()
106    }
107
108    fn value(&self) -> Option<BoxedValue> {
109        self.validation.inner.value()
110    }
111
112    fn set_value(&self, value: Option<&BoxedValue>, priority: BindingPriority) -> Result<bool, BindingError> {
113        self.validation.inner.set_value(value, priority)
114    }
115
116    fn subscribe(&self, listener: AccessorListener) {
117        self.validation.base.set_listener(listener);
118        if let Some(target) = instance(&self.reference) {
119            if let Some(model) = ModelTypes::find(&*target) {
120                if let Some(indei) = model.as_notify_data_error_info(&*target) {
121                    let weak = self.this.clone();
122                    let token = indei.errors_changed().add(Rc::new(move |name: &str| {
123                        if let Some(this) = weak.upgrade() {
124                            if name.is_empty() || name == &*this.name {
125                                let value = this.value();
126                                let notification = this.create_binding_notification(value);
127                                this.validation.base.publish_value(Some(notification));
128                            }
129                        }
130                    }));
131                    self.token.set(Some(token));
132                }
133            }
134        }
135        let weak = self.this.clone();
136        self.validation.subscribe_core(Rc::new(move |value| {
137            if let Some(this) = weak.upgrade() {
138                let notification = this.create_binding_notification(value);
139                this.validation.base.publish_value(Some(notification));
140            }
141        }));
142    }
143
144    fn unsubscribe(&self) {
145        self.remove_handler();
146        self.validation.unsubscribe_core();
147        self.validation.base.clear_listener();
148    }
149}
150
151impl Drop for Validator {
152    fn drop(&mut self) {
153        self.remove_handler();
154    }
155}