Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,17 @@
## 0.12.2

- Move the instance field `TypeValidatorBase._shouldBeComparedToNull` to a static readonly field (renamed to `_canBeNull`) to cache the reflection result per `TypeValidatorBase<T>` type and eliminate redundant per-instance evaluations.
- Remove the eagerly allocated `NotNullValidationMessageProvider` during `ExpressValidatorBuilder` configuration, and instantiate `NotNullValidationMessageProvider<object, T>` only when the value is null.
Deprecate `NotNullValidationMessageProvider.GetMessage(ValidationContext<T>)`.
- Update to FluentValidation 12.1.0.
- Rename private `TypeValidatorBase.HasOnlyNullOrEmptyValidators` to `HasNonEmptyValidators` (inverting the boolean logic) and remove the negation of this property in the `ShouldValidate` method.
- DRY refactor of null validation in `TypeValidatorBase<T>`.
- Add tests for null-tolerance validation in `QuickValidator`.
- Add test for validating a primitive type using `ExpressValidatorBuilder`.
- Add a unit test that verifies `ExpressValidator` does not throw when members are null and no null-related validators are used.
- Edit README.md and NuGet.md.


## 0.12.0

- Support .NET 8.0 and FluentValidation 12.0.0.
Expand Down
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,15 @@ ExpressValidator is a library that provides the ability to validate objects usin
- Supports adding a property or field for validation.
- Verifies that a property expression is a property and a field expression is a field, and throws `ArgumentException` if it is not.
- Supports adding a `Func` that provides a value for validation.
- Provides quick and easy validation using the `QuickValidator`.
- Provides quick and easy validation using `QuickValidator`, with built-in tolerance for `null` values.
- Supports asynchronous validation.
- Targets .NET Standard 2.0+

## 📜 Documentation

For details, please check the [API documentation](https://www.tmfexplorer.com/ExpressValidator/api/ExpressValidator.html).
> See the [API documentation](https://www.tmfexplorer.com/ExpressValidator/api/ExpressValidator.html) for reference.
>
> Learn more on [DeepWiki](https://deepwiki.com/kolan72/ExpressValidator/2-core-library-%28expressvalidator%29).


## 🚀 Quick Start
Expand Down Expand Up @@ -181,7 +183,8 @@ var result = QuickValidator.Validate(
.ChildRules((v) => v.RuleFor(o => o.PercentValue1).InclusiveBetween(0, 100)),
nameof(obj));
```
The `QuickValidator` also provides a `ValidateAsync` method for asynchronous validation.
The `QuickValidator` also provides a `ValidateAsync` method for asynchronous validation.
It is also tolerant of `null` values, i.e., it avoids exceptions when the input is null.

## 🧩 Nuances Of Using The Library

Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
## 0.3.12

- Support .NET 8.0 and FluentValidation 12.0.0.
- Update Microsoft nuget packages.
- Update ExpressValidator NuGet package to v0.12.0.
- Update NUnit NuGet package to v4.4.0.
- Retarget ExpressValidator.Extensions.DependencyInjection.Sample to .NET 8.0.


## 0.3.9

- Update ExpressValidator nuget package.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>0.3.9</Version>
<Version>0.3.12</Version>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<Authors>Andrey Kolesnichenko</Authors>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
Expand All @@ -15,7 +15,7 @@
<PackageTags>FluentValidation Validation DependencyInjection</PackageTags>
<Description>The ExpressValidator.Extensions.DependencyInjection package extends ExpressValidator to provide integration with Microsoft Dependency Injection.</Description>
<Copyright>Copyright 2024 Andrey Kolesnichenko</Copyright>
<AssemblyVersion>0.3.9.0</AssemblyVersion>
<AssemblyVersion>0.3.12.0</AssemblyVersion>
</PropertyGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'netstandard2.0'">
Expand Down
4 changes: 4 additions & 0 deletions src/ExpressValidator.Extensions.DependencyInjection/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@
- Additionally, the `IExpressValidatorBuilder<ObjToValidate, ValidationParametersOptions>` interface can be configured and registered to update the validator parameters when the `ValidationParametersOptions` change.
- Ability to dynamically update the validator parameters from options bound to the configuration section without restarting the application by configuring the `IExpressValidatorWithReload<ObjToValidate>` interface.

## 📜 Documentation

Explore the API documentation and in-depth details on [DeepWiki](https://deepwiki.com/kolan72/ExpressValidator/3-dependency-injection-extension).

## 🚀 Usage

```csharp
Expand Down
6 changes: 3 additions & 3 deletions src/ExpressValidator/ExpressValidator.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
<PropertyGroup>
<TargetFrameworks>netstandard2.0;net8.0</TargetFrameworks>
<GeneratePackageOnBuild>true</GeneratePackageOnBuild>
<Version>0.12.0</Version>
<Version>0.12.2</Version>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<Authors>Andrey Kolesnichenko</Authors>
<Description>ExpressValidator is a library that provides the ability to validate objects using the FluentValidation library, but without object inheritance from `AbstractValidator`.</Description>
Expand All @@ -15,7 +15,7 @@
<PackageIcon>ExpressValidator.png</PackageIcon>
<PackageReadmeFile>NuGet.md</PackageReadmeFile>
<PackageIconUrl />
<AssemblyVersion>0.12.0.0</AssemblyVersion>
<AssemblyVersion>0.12.2.0</AssemblyVersion>
<FileVersion>0.0.0.0</FileVersion>
</PropertyGroup>

Expand All @@ -24,7 +24,7 @@
</ItemGroup>

<ItemGroup Condition="'$(TargetFramework)' == 'net8.0'">
<PackageReference Include="FluentValidation" Version="12.0.0" />
<PackageReference Include="FluentValidation" Version="12.1.0" />
</ItemGroup>

<ItemGroup>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using FluentValidation;
using FluentValidation.Validators;
using System;

namespace ExpressValidator
{
Expand All @@ -11,9 +12,23 @@ public NotNullValidationMessageProvider(string propName)
_propName = propName;
}

public string GetMessage() => GetDefaultMessageTemplate(null);

#pragma warning disable S1133 // Deprecated code should be removed
[Obsolete("This method is obsolete")]
#pragma warning restore S1133 // Deprecated code should be removed
public string GetMessage(ValidationContext<T> context)
{
return context.MessageFormatter.AppendPropertyName(_propName).BuildMessage(GetDefaultMessageTemplate(null));
}
}

internal static class NullFallbackMessageProvider
{
public static string GetMessage<T>(string propName, ValidationContext<T> context)
{
var validator = new NotNullValidationMessageProvider<T>(propName);
return context.MessageFormatter.AppendPropertyName(propName).BuildMessage(validator.GetMessage());
}
}
}
25 changes: 11 additions & 14 deletions src/ExpressValidator/TypeValidators/TypeValidatorBase.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,11 @@ namespace ExpressValidator
internal abstract class TypeValidatorBase<T> : AbstractValidator<T>
{
protected IRuleBuilderOptions<T, T> _ruleBuilderInitial;
private NotNullValidationMessageProvider<T> _nullMessageProvider;

private IValidationRule<T> _rule;
private string _propName;

private readonly bool _shouldBeComparedToNull;

protected TypeValidatorBase()
{
_shouldBeComparedToNull = !typeof(T).IsValueType || (Nullable.GetUnderlyingType(typeof(T)) != null);
}
private static readonly bool _canBeNull = !typeof(T).IsValueType || (Nullable.GetUnderlyingType(typeof(T)) != null);

protected override void OnRuleAdded(IValidationRule<T> rule)
{
Expand All @@ -36,9 +30,9 @@ protected override void OnRuleAdded(IValidationRule<T> rule)
/// <returns></returns>
protected override bool PreValidate(ValidationContext<T> context, ValidationResult result)
{
if (_shouldBeComparedToNull && EqualityComparer<T>.Default.Equals(context.InstanceToValidate, default))
if (IsValueNull(context.InstanceToValidate))
{
result.Errors.Add(new ValidationFailure(_propName, _nullMessageProvider.GetMessage(context)));
result.Errors.Add(new ValidationFailure(_propName, NullFallbackMessageProvider.GetMessage(_propName, context)));
return false;
}
return true;
Expand All @@ -55,9 +49,7 @@ public void SetValidation(Action<IRuleBuilderOptions<T, T>> action, string propN
_ruleBuilderInitial = _ruleBuilderInitial.OverridePropertyName(_propName);
}

_nullMessageProvider = new NotNullValidationMessageProvider<T>(_propName);

HasOnlyNullOrEmptyValidators = AllValidatorsAreNullOrEmpty();
HasNonEmptyValidators = !AllValidatorsAreNullOrEmpty();
}

public async Task<(bool IsValid, List<ValidationFailure> Failures)> ValidateExAsync(T value, CancellationToken token = default)
Expand Down Expand Up @@ -94,9 +86,14 @@ public void SetValidation(Action<IRuleBuilderOptions<T, T>> action, string propN

internal abstract bool? IsAsync { get; }

protected bool ShouldValidate(T value) =>!_shouldBeComparedToNull || !EqualityComparer<T>.Default.Equals(value, default) || !HasOnlyNullOrEmptyValidators;
protected bool ShouldValidate(T value) => !IsValueNull(value) || HasNonEmptyValidators;

private static bool IsValueNull(T value)
{
return _canBeNull && EqualityComparer<T>.Default.Equals(value, default);
}

private bool HasOnlyNullOrEmptyValidators { get; set; }
private bool HasNonEmptyValidators { get; set; }

private bool AllValidatorsAreNullOrEmpty()
{
Expand Down
5 changes: 3 additions & 2 deletions src/ExpressValidator/docs/NuGet.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ ExpressValidator is a library that provides the ability to validate objects usin
- Supports adding a property or field for validation.
- Verifies that a property expression is a property and a field expression is a field, and throws `ArgumentException` if it is not.
- Supports adding a `Func` that provides a value for validation.
- Provides quick and easy validation using the `QuickValidator`.
- Provides quick and easy validation using `QuickValidator`, with built-in tolerance for `null` values.
- Supports asynchronous validation.
- Targets .NET Standard 2.0+

Expand Down Expand Up @@ -144,7 +144,8 @@ var result = QuickValidator.Validate(
.ChildRules((v) => v.RuleFor(o => o.PercentValue1).InclusiveBetween(0, 100)),
nameof(obj));
```
The `QuickValidator` also provides a `ValidateAsync` method for asynchronous validation.
The `QuickValidator` also provides a `ValidateAsync` method for asynchronous validation.
It is also tolerant of `null` values, i.e., it avoids exceptions when the input is null.

## Nuances Of Using The Library

Expand Down
43 changes: 42 additions & 1 deletion tests/ExpressValidator.Tests/ExpressValidatorTests.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using FluentValidation;
using ExpressValidator.Extensions;
using FluentValidation;
using NUnit.Framework;
using NUnit.Framework.Legacy;
using System;
Expand Down Expand Up @@ -53,6 +54,26 @@ public void Should_Not_Invoke_SuccessValidationHandler_When_IsNotValid()
Assert.That(result.IsValid, Is.False);
}

[Test]
public void Should_NotThrow_When_MembersAreNull()
{
var result = new ExpressValidatorBuilder<ObjWithTwoPublicProps>()
.AddProperty(o => o.S)
.WithValidation(o => o.MaximumLength(1))
.AddField(o => o._sField)
.WithValidation(o => o.MinimumLength(1))
.Build()
.Validate(new ObjWithTwoPublicProps());

var em1 = NullFallbackMessageProvider.GetMessage("S", new ValidationContext<string>(null));
var em2 = NullFallbackMessageProvider.GetMessage("_sField", new ValidationContext<string>(null));

Assert.That(result.IsValid, Is.False);
Assert.That(result.Errors.Count, Is.EqualTo(2));
Assert.That(result.Errors[0].ErrorMessage, Is.EqualTo(em1));
Assert.That(result.Errors[1].ErrorMessage, Is.EqualTo(em2));
}

[Test]
public void Should_Work_When_IsValid_ForSubObjWithSimpleConditionForComplexProperty()
{
Expand Down Expand Up @@ -349,5 +370,25 @@ public void Should_AddFunc_Preserve_Property_Name(SetPropertyNameType setPropert
Assert.That(result.Errors.FirstOrDefault().ErrorMessage, Does.Contain("TestName"));
}
}

[Test]
[TestCase(true)]
[TestCase(false)]
public void Should_Validate_Primitive(bool valid)
{
int value;

if(valid)
value = 3;
else
value = 1;

var result = new ExpressValidatorBuilder<int>()
.AddFunc(i => i, "value")
.WithValidation(o => o.GreaterThan(2))
.BuildAndValidate(value);

Assert.That(result.IsValid, Is.EqualTo(valid));
}
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
using FluentValidation;
using NUnit.Framework;
using System;

namespace ExpressValidator.Tests
{
public class NotNullValidationMessageProviderTests
{
#pragma warning disable S1133 // Deprecated code should be removed
[Obsolete("This test is obsolete")]
#pragma warning restore S1133 // Deprecated code should be removed
[Test]
public void Should_GetMessage_Returns_CorrectMessage_For_Null_Instance()
{
Expand All @@ -13,5 +17,13 @@ public void Should_GetMessage_Returns_CorrectMessage_For_Null_Instance()
var res = notNullMsgProvider.GetMessage(new ValidationContext<string>(null));
Assert.That(res.Contains(propName), Is.True);
}

[Test]
public void Should_NullFallbackMessageProvider_Returns_CorrectMessage_For_Null_Instance()
{
const string propName = "TestPropName";
var res = NullFallbackMessageProvider.GetMessage(propName, new ValidationContext<string>(null));
Assert.That(res.Contains(propName), Is.True);
}
}
}
Loading