Skip to content

Commit 2e67fcd

Browse files
authored
Feature Add Validation to ReactiveProperty (#3777)
<!-- Please be sure to read the [Contribute](https://github.com/reactiveui/reactiveui#contribute) section of the README --> **What kind of change does this PR introduce?** <!-- Bug fix, feature, docs update, ... --> Feature for #3771 **What is the current behavior?** <!-- You can also link to an open issue here. --> ReactiveProperty has basic functionality **What is the new behavior?** <!-- If this is a feature change --> ReactiveProperty now supports Validation through INotifyDataErrorInfo AddValidation and AddValidationError methods added to attach the ReactiveProperty to the Validation mechanism CheckValidation and Refresh exist to re-evaluate the Validation ```c# MyReactiveProperty = new ReactiveProperty<string>() .AddValidation(() => MyReactiveProperty) .AddValidationError(s => string.IsNullOrWhiteSpace(s) ? "required" : null); ``` **What might this PR break?** None expected. **Please check if the PR fulfills these requirements** - [x] Tests for the changes have been added (for bug fixes / features) - [ ] Docs have been added / updated (for bug fixes / features) **Other information**:
1 parent 095a9c9 commit 2e67fcd

16 files changed

+1186
-29
lines changed

src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.DotNet6_0.verified.txt

+25-3
Original file line numberDiff line numberDiff line change
@@ -349,9 +349,12 @@ namespace ReactiveUI
349349
string? PropertyName { get; }
350350
TSender Sender { get; }
351351
}
352-
public interface IReactiveProperty<T> : System.IDisposable, System.IObservable<T?>, System.Reactive.Disposables.ICancelable
352+
public interface IReactiveProperty<T> : System.ComponentModel.INotifyDataErrorInfo, System.ComponentModel.INotifyPropertyChanged, System.IDisposable, System.IObservable<T?>, System.Reactive.Disposables.ICancelable
353353
{
354+
System.IObservable<System.Collections.IEnumerable?> ObserveErrorChanged { get; }
355+
System.IObservable<bool> ObserveHasErrors { get; }
354356
T Value { get; set; }
357+
void Refresh();
355358
}
356359
public interface IRoutableViewModel : ReactiveUI.IReactiveObject, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging
357360
{
@@ -753,18 +756,37 @@ namespace ReactiveUI
753756
public ReactivePropertyChangingEventArgs(TSender sender, string? propertyName) { }
754757
public TSender Sender { get; }
755758
}
759+
public static class ReactivePropertyMixins
760+
{
761+
public static ReactiveUI.ReactiveProperty<T> AddValidation<T>(this ReactiveUI.ReactiveProperty<T> self, System.Linq.Expressions.Expression<System.Func<ReactiveUI.ReactiveProperty<T>?>> selfSelector) { }
762+
public static System.IObservable<string?> ObserveValidationErrors<T>(this ReactiveUI.ReactiveProperty<T> self) { }
763+
}
756764
[System.Runtime.Serialization.DataContract]
757-
public class ReactiveProperty<T> : ReactiveUI.ReactiveObject, ReactiveUI.IReactiveProperty<T>, System.IDisposable, System.IObservable<T?>, System.Reactive.Disposables.ICancelable
765+
public class ReactiveProperty<T> : ReactiveUI.ReactiveObject, ReactiveUI.IReactiveProperty<T>, System.ComponentModel.INotifyDataErrorInfo, System.ComponentModel.INotifyPropertyChanged, System.IDisposable, System.IObservable<T?>, System.Reactive.Disposables.ICancelable
758766
{
759767
public ReactiveProperty() { }
760768
public ReactiveProperty(T? initialValue) { }
761-
public ReactiveProperty(T? initialValue, System.Reactive.Concurrency.IScheduler? scheduler) { }
769+
public ReactiveProperty(T? initialValue, bool skipCurrentValueOnSubscribe, bool allowDuplicateValues) { }
770+
public ReactiveProperty(T? initialValue, System.Reactive.Concurrency.IScheduler? scheduler, bool skipCurrentValueOnSubscribe, bool allowDuplicateValues) { }
771+
public bool HasErrors { get; }
762772
public bool IsDisposed { get; }
773+
public System.IObservable<System.Collections.IEnumerable?> ObserveErrorChanged { get; }
774+
public System.IObservable<bool> ObserveHasErrors { get; }
763775
[System.Runtime.Serialization.DataMember]
764776
[System.Text.Json.Serialization.JsonInclude]
765777
public T Value { get; set; }
778+
public event System.EventHandler<System.ComponentModel.DataErrorsChangedEventArgs>? ErrorsChanged;
779+
public ReactiveUI.ReactiveProperty<T> AddValidationError(System.Func<System.IObservable<T?>, System.IObservable<System.Collections.IEnumerable?>> validator, bool ignoreInitialError = false) { }
780+
public ReactiveUI.ReactiveProperty<T> AddValidationError(System.Func<System.IObservable<T?>, System.IObservable<string?>> validator, bool ignoreInitialError = false) { }
781+
public ReactiveUI.ReactiveProperty<T> AddValidationError(System.Func<T?, System.Collections.IEnumerable?> validator, bool ignoreInitialError = false) { }
782+
public ReactiveUI.ReactiveProperty<T> AddValidationError(System.Func<T?, System.Threading.Tasks.Task<System.Collections.IEnumerable?>> validator, bool ignoreInitialError = false) { }
783+
public ReactiveUI.ReactiveProperty<T> AddValidationError(System.Func<T?, System.Threading.Tasks.Task<string?>> validator, bool ignoreInitialError = false) { }
784+
public ReactiveUI.ReactiveProperty<T> AddValidationError(System.Func<T?, string?> validator, bool ignoreInitialError = false) { }
785+
public void CheckValidation() { }
766786
public void Dispose() { }
767787
protected virtual void Dispose(bool disposing) { }
788+
public System.Collections.IEnumerable? GetErrors(string? propertyName) { }
789+
public void Refresh() { }
768790
public System.IDisposable Subscribe(System.IObserver<T?> observer) { }
769791
}
770792
[System.Runtime.Serialization.DataContract]

src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.DotNet7_0.verified.txt

+25-3
Original file line numberDiff line numberDiff line change
@@ -349,9 +349,12 @@ namespace ReactiveUI
349349
string? PropertyName { get; }
350350
TSender Sender { get; }
351351
}
352-
public interface IReactiveProperty<T> : System.IDisposable, System.IObservable<T?>, System.Reactive.Disposables.ICancelable
352+
public interface IReactiveProperty<T> : System.ComponentModel.INotifyDataErrorInfo, System.ComponentModel.INotifyPropertyChanged, System.IDisposable, System.IObservable<T?>, System.Reactive.Disposables.ICancelable
353353
{
354+
System.IObservable<System.Collections.IEnumerable?> ObserveErrorChanged { get; }
355+
System.IObservable<bool> ObserveHasErrors { get; }
354356
T Value { get; set; }
357+
void Refresh();
355358
}
356359
public interface IRoutableViewModel : ReactiveUI.IReactiveObject, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging
357360
{
@@ -753,18 +756,37 @@ namespace ReactiveUI
753756
public ReactivePropertyChangingEventArgs(TSender sender, string? propertyName) { }
754757
public TSender Sender { get; }
755758
}
759+
public static class ReactivePropertyMixins
760+
{
761+
public static ReactiveUI.ReactiveProperty<T> AddValidation<T>(this ReactiveUI.ReactiveProperty<T> self, System.Linq.Expressions.Expression<System.Func<ReactiveUI.ReactiveProperty<T>?>> selfSelector) { }
762+
public static System.IObservable<string?> ObserveValidationErrors<T>(this ReactiveUI.ReactiveProperty<T> self) { }
763+
}
756764
[System.Runtime.Serialization.DataContract]
757-
public class ReactiveProperty<T> : ReactiveUI.ReactiveObject, ReactiveUI.IReactiveProperty<T>, System.IDisposable, System.IObservable<T?>, System.Reactive.Disposables.ICancelable
765+
public class ReactiveProperty<T> : ReactiveUI.ReactiveObject, ReactiveUI.IReactiveProperty<T>, System.ComponentModel.INotifyDataErrorInfo, System.ComponentModel.INotifyPropertyChanged, System.IDisposable, System.IObservable<T?>, System.Reactive.Disposables.ICancelable
758766
{
759767
public ReactiveProperty() { }
760768
public ReactiveProperty(T? initialValue) { }
761-
public ReactiveProperty(T? initialValue, System.Reactive.Concurrency.IScheduler? scheduler) { }
769+
public ReactiveProperty(T? initialValue, bool skipCurrentValueOnSubscribe, bool allowDuplicateValues) { }
770+
public ReactiveProperty(T? initialValue, System.Reactive.Concurrency.IScheduler? scheduler, bool skipCurrentValueOnSubscribe, bool allowDuplicateValues) { }
771+
public bool HasErrors { get; }
762772
public bool IsDisposed { get; }
773+
public System.IObservable<System.Collections.IEnumerable?> ObserveErrorChanged { get; }
774+
public System.IObservable<bool> ObserveHasErrors { get; }
763775
[System.Runtime.Serialization.DataMember]
764776
[System.Text.Json.Serialization.JsonInclude]
765777
public T Value { get; set; }
778+
public event System.EventHandler<System.ComponentModel.DataErrorsChangedEventArgs>? ErrorsChanged;
779+
public ReactiveUI.ReactiveProperty<T> AddValidationError(System.Func<System.IObservable<T?>, System.IObservable<System.Collections.IEnumerable?>> validator, bool ignoreInitialError = false) { }
780+
public ReactiveUI.ReactiveProperty<T> AddValidationError(System.Func<System.IObservable<T?>, System.IObservable<string?>> validator, bool ignoreInitialError = false) { }
781+
public ReactiveUI.ReactiveProperty<T> AddValidationError(System.Func<T?, System.Collections.IEnumerable?> validator, bool ignoreInitialError = false) { }
782+
public ReactiveUI.ReactiveProperty<T> AddValidationError(System.Func<T?, System.Threading.Tasks.Task<System.Collections.IEnumerable?>> validator, bool ignoreInitialError = false) { }
783+
public ReactiveUI.ReactiveProperty<T> AddValidationError(System.Func<T?, System.Threading.Tasks.Task<string?>> validator, bool ignoreInitialError = false) { }
784+
public ReactiveUI.ReactiveProperty<T> AddValidationError(System.Func<T?, string?> validator, bool ignoreInitialError = false) { }
785+
public void CheckValidation() { }
766786
public void Dispose() { }
767787
protected virtual void Dispose(bool disposing) { }
788+
public System.Collections.IEnumerable? GetErrors(string? propertyName) { }
789+
public void Refresh() { }
768790
public System.IDisposable Subscribe(System.IObserver<T?> observer) { }
769791
}
770792
[System.Runtime.Serialization.DataContract]

src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.DotNet8_0.verified.txt

+25-3
Original file line numberDiff line numberDiff line change
@@ -349,9 +349,12 @@ namespace ReactiveUI
349349
string? PropertyName { get; }
350350
TSender Sender { get; }
351351
}
352-
public interface IReactiveProperty<T> : System.IDisposable, System.IObservable<T?>, System.Reactive.Disposables.ICancelable
352+
public interface IReactiveProperty<T> : System.ComponentModel.INotifyDataErrorInfo, System.ComponentModel.INotifyPropertyChanged, System.IDisposable, System.IObservable<T?>, System.Reactive.Disposables.ICancelable
353353
{
354+
System.IObservable<System.Collections.IEnumerable?> ObserveErrorChanged { get; }
355+
System.IObservable<bool> ObserveHasErrors { get; }
354356
T Value { get; set; }
357+
void Refresh();
355358
}
356359
public interface IRoutableViewModel : ReactiveUI.IReactiveObject, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging
357360
{
@@ -753,18 +756,37 @@ namespace ReactiveUI
753756
public ReactivePropertyChangingEventArgs(TSender sender, string? propertyName) { }
754757
public TSender Sender { get; }
755758
}
759+
public static class ReactivePropertyMixins
760+
{
761+
public static ReactiveUI.ReactiveProperty<T> AddValidation<T>(this ReactiveUI.ReactiveProperty<T> self, System.Linq.Expressions.Expression<System.Func<ReactiveUI.ReactiveProperty<T>?>> selfSelector) { }
762+
public static System.IObservable<string?> ObserveValidationErrors<T>(this ReactiveUI.ReactiveProperty<T> self) { }
763+
}
756764
[System.Runtime.Serialization.DataContract]
757-
public class ReactiveProperty<T> : ReactiveUI.ReactiveObject, ReactiveUI.IReactiveProperty<T>, System.IDisposable, System.IObservable<T?>, System.Reactive.Disposables.ICancelable
765+
public class ReactiveProperty<T> : ReactiveUI.ReactiveObject, ReactiveUI.IReactiveProperty<T>, System.ComponentModel.INotifyDataErrorInfo, System.ComponentModel.INotifyPropertyChanged, System.IDisposable, System.IObservable<T?>, System.Reactive.Disposables.ICancelable
758766
{
759767
public ReactiveProperty() { }
760768
public ReactiveProperty(T? initialValue) { }
761-
public ReactiveProperty(T? initialValue, System.Reactive.Concurrency.IScheduler? scheduler) { }
769+
public ReactiveProperty(T? initialValue, bool skipCurrentValueOnSubscribe, bool allowDuplicateValues) { }
770+
public ReactiveProperty(T? initialValue, System.Reactive.Concurrency.IScheduler? scheduler, bool skipCurrentValueOnSubscribe, bool allowDuplicateValues) { }
771+
public bool HasErrors { get; }
762772
public bool IsDisposed { get; }
773+
public System.IObservable<System.Collections.IEnumerable?> ObserveErrorChanged { get; }
774+
public System.IObservable<bool> ObserveHasErrors { get; }
763775
[System.Runtime.Serialization.DataMember]
764776
[System.Text.Json.Serialization.JsonInclude]
765777
public T Value { get; set; }
778+
public event System.EventHandler<System.ComponentModel.DataErrorsChangedEventArgs>? ErrorsChanged;
779+
public ReactiveUI.ReactiveProperty<T> AddValidationError(System.Func<System.IObservable<T?>, System.IObservable<System.Collections.IEnumerable?>> validator, bool ignoreInitialError = false) { }
780+
public ReactiveUI.ReactiveProperty<T> AddValidationError(System.Func<System.IObservable<T?>, System.IObservable<string?>> validator, bool ignoreInitialError = false) { }
781+
public ReactiveUI.ReactiveProperty<T> AddValidationError(System.Func<T?, System.Collections.IEnumerable?> validator, bool ignoreInitialError = false) { }
782+
public ReactiveUI.ReactiveProperty<T> AddValidationError(System.Func<T?, System.Threading.Tasks.Task<System.Collections.IEnumerable?>> validator, bool ignoreInitialError = false) { }
783+
public ReactiveUI.ReactiveProperty<T> AddValidationError(System.Func<T?, System.Threading.Tasks.Task<string?>> validator, bool ignoreInitialError = false) { }
784+
public ReactiveUI.ReactiveProperty<T> AddValidationError(System.Func<T?, string?> validator, bool ignoreInitialError = false) { }
785+
public void CheckValidation() { }
766786
public void Dispose() { }
767787
protected virtual void Dispose(bool disposing) { }
788+
public System.Collections.IEnumerable? GetErrors(string? propertyName) { }
789+
public void Refresh() { }
768790
public System.IDisposable Subscribe(System.IObserver<T?> observer) { }
769791
}
770792
[System.Runtime.Serialization.DataContract]

src/ReactiveUI.Tests/API/ApiApprovalTests.ReactiveUI.Net4_7.verified.txt

+25-3
Original file line numberDiff line numberDiff line change
@@ -347,9 +347,12 @@ namespace ReactiveUI
347347
string? PropertyName { get; }
348348
TSender Sender { get; }
349349
}
350-
public interface IReactiveProperty<T> : System.IDisposable, System.IObservable<T?>, System.Reactive.Disposables.ICancelable
350+
public interface IReactiveProperty<T> : System.ComponentModel.INotifyDataErrorInfo, System.ComponentModel.INotifyPropertyChanged, System.IDisposable, System.IObservable<T?>, System.Reactive.Disposables.ICancelable
351351
{
352+
System.IObservable<System.Collections.IEnumerable?> ObserveErrorChanged { get; }
353+
System.IObservable<bool> ObserveHasErrors { get; }
352354
T Value { get; set; }
355+
void Refresh();
353356
}
354357
public interface IRoutableViewModel : ReactiveUI.IReactiveObject, Splat.IEnableLogger, System.ComponentModel.INotifyPropertyChanged, System.ComponentModel.INotifyPropertyChanging
355358
{
@@ -751,18 +754,37 @@ namespace ReactiveUI
751754
public ReactivePropertyChangingEventArgs(TSender sender, string? propertyName) { }
752755
public TSender Sender { get; }
753756
}
757+
public static class ReactivePropertyMixins
758+
{
759+
public static ReactiveUI.ReactiveProperty<T> AddValidation<T>(this ReactiveUI.ReactiveProperty<T> self, System.Linq.Expressions.Expression<System.Func<ReactiveUI.ReactiveProperty<T>?>> selfSelector) { }
760+
public static System.IObservable<string?> ObserveValidationErrors<T>(this ReactiveUI.ReactiveProperty<T> self) { }
761+
}
754762
[System.Runtime.Serialization.DataContract]
755-
public class ReactiveProperty<T> : ReactiveUI.ReactiveObject, ReactiveUI.IReactiveProperty<T>, System.IDisposable, System.IObservable<T?>, System.Reactive.Disposables.ICancelable
763+
public class ReactiveProperty<T> : ReactiveUI.ReactiveObject, ReactiveUI.IReactiveProperty<T>, System.ComponentModel.INotifyDataErrorInfo, System.ComponentModel.INotifyPropertyChanged, System.IDisposable, System.IObservable<T?>, System.Reactive.Disposables.ICancelable
756764
{
757765
public ReactiveProperty() { }
758766
public ReactiveProperty(T? initialValue) { }
759-
public ReactiveProperty(T? initialValue, System.Reactive.Concurrency.IScheduler? scheduler) { }
767+
public ReactiveProperty(T? initialValue, bool skipCurrentValueOnSubscribe, bool allowDuplicateValues) { }
768+
public ReactiveProperty(T? initialValue, System.Reactive.Concurrency.IScheduler? scheduler, bool skipCurrentValueOnSubscribe, bool allowDuplicateValues) { }
769+
public bool HasErrors { get; }
760770
public bool IsDisposed { get; }
771+
public System.IObservable<System.Collections.IEnumerable?> ObserveErrorChanged { get; }
772+
public System.IObservable<bool> ObserveHasErrors { get; }
761773
[System.Runtime.Serialization.DataMember]
762774
[System.Text.Json.Serialization.JsonInclude]
763775
public T Value { get; set; }
776+
public event System.EventHandler<System.ComponentModel.DataErrorsChangedEventArgs>? ErrorsChanged;
777+
public ReactiveUI.ReactiveProperty<T> AddValidationError(System.Func<System.IObservable<T?>, System.IObservable<System.Collections.IEnumerable?>> validator, bool ignoreInitialError = false) { }
778+
public ReactiveUI.ReactiveProperty<T> AddValidationError(System.Func<System.IObservable<T?>, System.IObservable<string?>> validator, bool ignoreInitialError = false) { }
779+
public ReactiveUI.ReactiveProperty<T> AddValidationError(System.Func<T?, System.Collections.IEnumerable?> validator, bool ignoreInitialError = false) { }
780+
public ReactiveUI.ReactiveProperty<T> AddValidationError(System.Func<T?, System.Threading.Tasks.Task<System.Collections.IEnumerable?>> validator, bool ignoreInitialError = false) { }
781+
public ReactiveUI.ReactiveProperty<T> AddValidationError(System.Func<T?, System.Threading.Tasks.Task<string?>> validator, bool ignoreInitialError = false) { }
782+
public ReactiveUI.ReactiveProperty<T> AddValidationError(System.Func<T?, string?> validator, bool ignoreInitialError = false) { }
783+
public void CheckValidation() { }
764784
public void Dispose() { }
765785
protected virtual void Dispose(bool disposing) { }
786+
public System.Collections.IEnumerable? GetErrors(string? propertyName) { }
787+
public void Refresh() { }
766788
public System.IDisposable Subscribe(System.IObserver<T?> observer) { }
767789
}
768790
[System.Runtime.Serialization.DataContract]

0 commit comments

Comments
 (0)