night-moon-studio / Leo

A library based on NCC Natasha to quickly and easily read/write instance's fields or properties.
https://natasha.dotnetcore.xyz
MIT License
81 stars 18 forks source link

Memo for integrating the validator in LeoVisitor #20

Open alexinea opened 3 years ago

alexinea commented 3 years ago

In some application solutions, we need to check and verify data, and filter out illegal or unreasonable data. Therefore, it is necessary to integrate a validator in LeoVisitor so that it can be verified directly during the setup phase.

Since the requirement of the validator is not a general requirement, adding validation to the core library DictBase will pay extra. Therefore, it is an ideal choice to add an optional verification context in LeoVisitor to implement data verification.

I will record this part of the notes here in the form of a memo.

Set verification policy at creation time

To set the validation rules when creating LeoVisitor, we can pass in a strategy object (which implements the ILeoValidationStrategy interface) to the LeoVisitorFactory factory method:

var visitor = LeoVisitorFactory.Create(Type, Instance, ValidationStrategy);

// or
var visitor = LeoVisitorFactory.Create<T>(Instance, ValidationStrategy);

// or
var visitor = LeoVisitorFactory.Create<T, TStrategy>(Instance);

For LeoVisitor created with an initial data set (IDictionary implementation), the verification strategy can also be passed in when creating:

var d = new Dictionary<string, object>();
var visitor = LeoVisitorFactory.Create(Type, ValidationStrategy, d);

// or
var visitor = LeoVisitorFactory.Create<T>(ValidationStrategy, d);

// or
var visitor = LeoVisitorFactory.Create<T, TStrategy>(d);

Set or override the verification strategy in LeoVisitor

An API should be provided so that verification rules can be set or updated on LeoVisitor:

var validationEntry = visitor.Validation;

validationEntry.SetStrategy(StrategyInstance, OverwriteOrUpdateFlag);  // OverwriteOrUpdateFlag default is overwrite

// or
validationEntry.SetStrategy<TStrategy>(OverwriteOrUpdateFlag);
var validationEntry = visitor.Validation;

validationEntry.Member(Name, ValueRule);

// or
validationEntry.Member<TVal>(Expression<Func<T, TVal>> expression, ValueRule);  //for IValitionEntry<T>
validationEntry.Member<T, TVal>(Expression<Func<T, TVal>> expression, ValueRule);  //for IValitionEntry

// or
validationEntry.Member<TVal, TRule>(Expression<Func<T, TVal>> expression);  //for IValitionEntry<T>
validationEntry.Member<T, TVal,TRule>(Expression<Func<T, TVal>> expression);  //for IValitionEntry

// or (Not recommended)
validationEntry.Member<TVal>(Expression<Func<T, TVal>> expression).Required(); //fluent api for IValitionEntry<T>
validationEntry.Member<T, TVal>(Expression<Func<T, TVal>> expression).Required(); //fluent api for IValitionEntry

When to verify data

When LeoVisitor is created and a strategy is given, the value in LeoVisitor should not be checked immediately. I think it should be checked when the results are obtained to avoid time-consuming creation.

We may need to provide a parameter to indicate whether the policy is strict or non-strict. When using strict rules, each of our SetValue operations will check the value; when using non-strict rules, we will perform a check only when we get the verification result.

This applies to both instance setters and value setters.

Support for other verification components

Do we need to consider supporting other verification components? For example, tell users how to extend LeoVisitor's verification capabilities to integrate FluentValidation?

In addition, do we need to support the DataAnnotation that comes with .NET?

How to extend the support for user-designed verification components? This requires us to discuss together.

Throw an exception, or silently collect errors?

For LeoVisitor, I think the silent mode is more suitable. Throwing an exception directly loses the original intention of verifying data. We should be able to obtain a set of error messages so that we can return and inform the user.

Of course, for similar low-code platforms, they should be able to verify data at the front end. Then the LeoVisitor verifier at the back end can be regarded as a two-stage verification. So throwing exceptions directly is not in practice. Of course, we can provide a pair of APIs, similar to Verify and VerifyAndThrow.

At last

I think LeoVisitor's validator should not be designed to be too complicated and too many functions. Even, whether we should use Fluent APIs is a matter of thinking. Fluent APIs can make the code difficult to read in this case.

alexinea commented 3 years ago

Leo's own Validation will not include "business specific" validators, nor will it include complex validators. (In the design of Leo Validation, we call the validator "ValueToken").

We will provide a way for users to integrate third-party verification solutions in Leo Validation. For example, integrate FluentValidation or the DataAnnotation that comes with .NET into Leo Validation. But we will not directly integrate, nor will we provide integrated extension packages. But we will tell you how to integrate, and the method will be included in the "samples" folder.


The author of NMS.Leo.Typed, @alexinea, will provide a complete scheme of validation design in cosmos-loops/Cosmos.Validation.

By then, users can either integrate cosmos-loops/Cosmos.Validation into Leo Validation as a third-party solution, or integrate Leo Validation into cosmos-loops/Cosmos.Validation.

alexinea commented 3 years ago

All Available Validation Tokens

Token Name Status Generic Version Alias With Message Features
Null Avaliable ✔
NotNull Avaliable ✔
Empty Avaliable ✔
NotEmpty Avaliable ✔ Required
Equal Avaliable ✔
NotEqual Avaliable ✔
LessThan Avaliable ✔
LessThanOrEqual Avaliable ✔
GreaterThan Avaliable ✔
GreaterThanOrEqual Avaliable ✔
Length Avaliable ✔
MaxLength Avaliable ✔
MinLength Avaliable ✔ AtLeast
Range Avaliable ✔
RangeWithOpenInterval Avaliable ✔
RangeWithCloseInterval Avaliable ✔
Func Avaliable ✔ Must
Predicate Avaliable ✔
RegularExpression Avaliable ✔
Enum Avaliable ✔
StringEnum Avaliable ✔
Type Avaliable ✔
Types Avaliable ✔
ScalePrecision Avaliable ✔
In Avaliable ✔
NotIn Avaliable ✔
Any Avaliable ✔ NotAll
All Avaliable ✔ NotAny
Items for Null and NotNull WIP 🚧 ✔ ❕
Items for Empty and NotEmpty Intend ✒ ✔ ❕
Items for Equal and NotEqual Intend ✒ ✔ ❕
Items for LT, LToE, GT and GToE Intend ✒ ✔ ❕
Items for Length , MaxLen and MinLen Intend ✒ ✔ ❕
Items for Range, RangeO and RangeC Intend ✒ ✔ ❕
Items for Type and Types Intend ✒ ✔ ❕
Items for In and NotIn Intend ✒ ✔ ❕
Items for All and Any Intend ✒ ✔ ❕

Range Token will contain one parameter: RangeOptions, the value is:

The Mutually Exclusive of Tokens

The Memo for Mutually Exclusive Flags

Flag Value Value Tokens
90111 Null, MaxAndMinLength
90112 Null, MaxLength
90113 Null, MinLength
90114 Null, NotNull
90115 Empty, MaxAndMinLength
90116 Empty, MaxLength
90117 Empty, MinLength
90118 NotEmpty, Empty
90119 MaxLength, MaxAndMinLength
90120 MinLength, MaxAndMinLength

Non Mutually Exclusive Tokens

alexinea commented 3 years ago

Add two other tokens that are equal to ValueToken:

IterationToken can be used if and only if TVal is an iterable type under a generic interface.