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#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
10#[repr(i32)]
11pub enum BindingErrorType {
12 None = 0,
14 Error = 1,
16 DataValidationError = 2,
18}
19
20impl BindingErrorType {
21 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#[derive(Clone, Debug)]
33pub struct AggregateError {
34 inner_errors: Vec<BindingError>,
35}
36
37impl AggregateError {
38 pub fn new(inner_errors: Vec<BindingError>) -> Self {
40 Self { inner_errors }
41 }
42
43 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
61pub struct BindingNotification {
72 value: RefCell<Option<BoxedValue>>,
73 error: RefCell<Option<BindingError>>,
74 error_type: Cell<BindingErrorType>,
75}
76
77impl BindingNotification {
78 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 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 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 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 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 pub fn value(&self) -> Option<BoxedValue> {
130 self.value.borrow().clone()
131 }
132
133 pub fn has_value(&self) -> bool {
136 !BindingOperations::is_unset(self.value.borrow().as_ref())
137 }
138
139 pub fn error(&self) -> Option<BindingError> {
141 self.error.borrow().clone()
142 }
143
144 pub fn set_error(&self, error: Option<BindingError>) {
146 *self.error.borrow_mut() = error;
147 }
148
149 pub fn error_type(&self) -> BindingErrorType {
151 self.error_type.get()
152 }
153
154 pub fn set_error_type(&self, error_type: BindingErrorType) {
156 self.error_type.set(error_type);
157 }
158
159 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 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 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 pub(crate) fn to_binding_value<T: Clone + 'static>(&self, target_type: &str) -> crate::data::BindingValue<T> {
207 use crate::data::BindingValue;
208
209 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 #[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 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 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 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 pub fn clear_value(&self) {
284 *self.value.borrow_mut() = Some(FerroProperty::unset_value());
285 }
286
287 pub fn set_value(&self, value: Option<BoxedValue>) {
289 *self.value.borrow_mut() = value;
290 }
291
292 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
303fn 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 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 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(¬ification));
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(¬ification));
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(¬ification), Some(&new_value)).expect("a value");
516
517 assert!(Rc::ptr_eq(&result, ¬ification));
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 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}