Skip to content

Commit 901496d

Browse files
Add the PhoneNumber.FluentValidation package.
1 parent 8c6661b commit 901496d

File tree

12 files changed

+302
-1
lines changed

12 files changed

+302
-1
lines changed

PosInformatique.Foundations.slnx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,10 @@
9494
<Project Path="src/PhoneNumbers.EntityFramework/PhoneNumbers.EntityFramework.csproj" Id="3d06c72c-2346-4b36-9373-a58de44d2a76" />
9595
<Project Path="tests/PhoneNumbers.EntityFramework.Tests/PhoneNumbers.EntityFramework.Tests.csproj" Id="65c0d0fb-0427-4f37-aecf-e857e9c4317d" />
9696
</Folder>
97+
<Folder Name="/PhoneNumbers/FluentValidation/">
98+
<Project Path="src/PhoneNumbers.FluentValidation/PhoneNumbers.FluentValidation.csproj" Id="ed3262e3-700b-4c71-a8dc-b3984e786b0f" />
99+
<Project Path="tests/PhoneNumbers.FluentValidation.Tests/PhoneNumbers.FluentValidation.Tests.csproj" Id="773a710f-bea4-4044-ae15-ee09a9786ff0" />
100+
</Folder>
97101
<Folder Name="/PhoneNumbers/Json/">
98102
<Project Path="src/PhoneNumbers.Json/PhoneNumbers.Json.csproj" Id="ba6614aa-3b78-4bc6-a22d-9389bfa6e133" />
99103
<Project Path="tests/PhoneNumbers.Json.Tests/PhoneNumbers.Json.Tests.csproj" Id="65c8dd1e-6f18-4ad6-a261-3bc8dd96d00a" />

README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ You can install any package using the .NET CLI or NuGet Package Manager.
4141
|<img src="./src/People/Icon.png" alt="PosInformatique.Foundations.People.Json icon" width="48" height="48" />|[**PosInformatique.Foundations.People.Json**](./src/People.Json/README.md) | `System.Text.Json` converters for `FirstName` and `LastName`, with validation and easy registration via `AddPeopleConverters()`. | [![NuGet](https://img.shields.io/nuget/v/PosInformatique.Foundations.People.Json)](https://www.nuget.org/packages/PosInformatique.Foundations.People.Json) |
4242
|<img src="./src/PhoneNumbers/Icon.png" alt="PosInformatique.Foundations.PhoneNumbers icon" width="48" height="48" />|[**PosInformatique.Foundations.PhoneNumbers**](./src/PhoneNumbers/README.md) | Strongly-typed value object representing a phone number in E.164 format, with parsing (including region-aware local numbers), validation, comparison, and formatting helpers. | [![NuGet](https://img.shields.io/nuget/v/PosInformatique.Foundations.PhoneNumbers)](https://www.nuget.org/packages/PosInformatique.Foundations.PhoneNumbers) |
4343
|<img src="./src/PhoneNumbers/Icon.png" alt="PosInformatique.Foundations.PhoneNumbers.EntityFramework icon" width="48" height="48" />|[**PosInformatique.Foundations.PhoneNumbers.EntityFramework**](./src/PhoneNumbers.EntityFramework/README.md) | Entity Framework Core integration for the `PhoneNumber` value object, mapping it to a SQL `PhoneNumber` column type backed by `VARCHAR(16)` using a dedicated value converter. | [![NuGet](https://img.shields.io/nuget/v/PosInformatique.Foundations.PhoneNumbers.EntityFramework)](https://www.nuget.org/packages/PosInformatique.Foundations.PhoneNumbers.EntityFramework) |
44+
|<img src="./src/PhoneNumbers/Icon.png" alt="PosInformatique.Foundations.PhoneNumbers.FluentValidation icon" width="48" height="48" />|[**PosInformatique.Foundations.PhoneNumbers.FluentValidation**](./src/PhoneNumbers.FluentValidation/README.md) | FluentValidation integration for the `PhoneNumber` value object, providing dedicated validators and rules to ensure E.164 compliant phone numbers. | [![NuGet](https://img.shields.io/nuget/v/PosInformatique.Foundations.PhoneNumbers.FluentValidation)](https://www.nuget.org/packages/PosInformatique.Foundations.PhoneNumbers.FluentValidation) |
4445
|<img src="./src/PhoneNumbers/Icon.png" alt="PosInformatique.Foundations.PhoneNumbers.Json icon" width="48" height="48" />|[**PosInformatique.Foundations.PhoneNumbers.Json**](./src/PhoneNumbers.Json/README.md) | `System.Text.Json` converter for the `PhoneNumber` value object, enabling seamless serialization and deserialization of E.164 compliant phone numbers. | [![NuGet](https://img.shields.io/nuget/v/PosInformatique.Foundations.PhoneNumbers.Json)](https://www.nuget.org/packages/PosInformatique.Foundations.PhoneNumbers.Json) |
4546
|<img src="./src/Text.Templating/Icon.png" alt="PosInformatique.Foundations.Text.Templating icon" width="48" height="48" />|[**PosInformatique.Foundations.Text.Templating**](./src/Text.Templating/README.md) | Abstractions for text templating, including the `TextTemplate<TModel>` base class and `ITextTemplateRenderContext` interface, to be used by concrete templating engine implementations such as Razor-based text templates. | [![NuGet](https://img.shields.io/nuget/v/PosInformatique.Foundations.Text.Templating)](https://www.nuget.org/packages/PosInformatique.Foundations.Text.Templating) |
4647
|<img src="./src/Text.Templating/Icon.png" alt="PosInformatique.Foundations.Text.Templating.Razor icon" width="48" height="48" />|[**PosInformatique.Foundations.Text.Templating.Razor**](./src/Text.Templating.Razor/README.md) | Razor-based text templating using Blazor components, allowing generation of text from Razor views with a strongly-typed Model parameter and full dependency injection integration. | [![NuGet](https://img.shields.io/nuget/v/PosInformatique.Foundations.Text.Templating.Razor)](https://www.nuget.org/packages/PosInformatique.Foundations.Text.Templating.Razor) |

src/PhoneNumbers.EntityFramework/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
### PosInformatique.Foundations.PhoneNumbers.EntityFramework
1+
# PosInformatique.Foundations.PhoneNumbers.EntityFramework
22

33
[![NuGet version](https://img.shields.io/nuget/v/PosInformatique.Foundations.PhoneNumbers.EntityFramework)](https://www.nuget.org/packages/PosInformatique.Foundations.PhoneNumbers.EntityFramework/)
44
[![NuGet downloads](https://img.shields.io/nuget/dt/PosInformatique.Foundations.PhoneNumbers.EntityFramework)](https://www.nuget.org/packages/PosInformatique.Foundations.PhoneNumbers.EntityFramework/)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
1.0.0
2+
- Initial release with the support FluentValidation for the validation of EmailAddress value object.
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
//-----------------------------------------------------------------------
2+
// <copyright file="PhoneNumberValidator.cs" company="P.O.S Informatique">
3+
// Copyright (c) P.O.S Informatique. All rights reserved.
4+
// </copyright>
5+
//-----------------------------------------------------------------------
6+
7+
namespace FluentValidation
8+
{
9+
using FluentValidation.Validators;
10+
using PosInformatique.Foundations.PhoneNumbers;
11+
12+
internal sealed class PhoneNumberValidator<T> : PropertyValidator<T, string>
13+
{
14+
public override string Name
15+
{
16+
get => "PhoneNumberValidator";
17+
}
18+
19+
public override bool IsValid(ValidationContext<T> context, string value)
20+
{
21+
if (value is not null)
22+
{
23+
return PhoneNumber.IsValid(value);
24+
}
25+
26+
return true;
27+
}
28+
29+
protected override string GetDefaultMessageTemplate(string errorCode)
30+
{
31+
return $"'{{PropertyName}}' must be a valid phone number in E.164 format.";
32+
}
33+
}
34+
}
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
<Project Sdk="Microsoft.NET.Sdk">
2+
3+
<PropertyGroup>
4+
<IsPackable>true</IsPackable>
5+
6+
<Description>
7+
FluentValidation integration for the PhoneNumber value object, providing dedicated validators and rules to ensure E.164 compliant phone numbers.
8+
</Description>
9+
<PackageTags>phone;phonenumber;fluentvalidation;validation;e164;dotnet;posinformatique</PackageTags>
10+
<PackageReleaseNotes>
11+
$([System.IO.File]::ReadAllText("$(MSBuildProjectDirectory)/CHANGELOG.md"))
12+
</PackageReleaseNotes>
13+
14+
</PropertyGroup>
15+
16+
<ItemGroup>
17+
<ProjectReference Include="..\PhoneNumbers\PhoneNumbers.csproj" />
18+
</ItemGroup>
19+
20+
<ItemGroup>
21+
<PackageReference Include="FluentValidation" />
22+
</ItemGroup>
23+
24+
<ItemGroup>
25+
<Content Include="CHANGELOG.md" Pack="false" />
26+
<Content Include="..\EmailAddresses\Icon.png" Pack="true" PackagePath="" />
27+
<Content Include="README.md" Pack="true" PackagePath="" />
28+
</ItemGroup>
29+
30+
</Project>
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
//-----------------------------------------------------------------------
2+
// <copyright file="PhoneNumbersValidatorExtensions.cs" company="P.O.S Informatique">
3+
// Copyright (c) P.O.S Informatique. All rights reserved.
4+
// </copyright>
5+
//-----------------------------------------------------------------------
6+
7+
namespace FluentValidation
8+
{
9+
using PosInformatique.Foundations.PhoneNumbers;
10+
11+
/// <summary>
12+
/// Contains extension methods for <c>FluentValidation</c> to validate phone numbers.
13+
/// </summary>
14+
public static class PhoneNumbersValidatorExtensions
15+
{
16+
/// <summary>
17+
/// Defines a validator that checks if a <see cref="string"/> property is a valid phone number
18+
/// (parsable by the <see cref="PhoneNumber"/> class).
19+
/// Validation fails if the value is not a valid phone number.
20+
/// If the <see cref="string"/> value is <see langword="null"/>, validation succeeds.
21+
/// Use the <see cref="DefaultValidatorExtensions.NotNull{T, TProperty}(IRuleBuilder{T, TProperty})"/> validator
22+
/// to disallow <see langword="null"/> values.
23+
/// </summary>
24+
/// <typeparam name="T">The type of the object being validated.</typeparam>
25+
/// <param name="ruleBuilder">The rule builder on which the validator is defined.</param>
26+
/// <returns>The <paramref name="ruleBuilder"/> instance to continue configuring the property validator.</returns>
27+
/// <exception cref="ArgumentNullException">If the specified <paramref name="ruleBuilder"/> argument is <see langword="null"/>.</exception>
28+
public static IRuleBuilderOptions<T, string> MustBePhoneNumber<T>(this IRuleBuilder<T, string> ruleBuilder)
29+
{
30+
ArgumentNullException.ThrowIfNull(ruleBuilder);
31+
32+
return ruleBuilder.SetValidator(new PhoneNumberValidator<T>());
33+
}
34+
}
35+
}
Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# PosInformatique.Foundations.PhoneNumbers.FluentValidation
2+
3+
[![NuGet version](https://img.shields.io/nuget/v/PosInformatique.Foundations.PhoneNumbers.FluentValidation)](https://www.nuget.org/packages/PosInformatique.Foundations.PhoneNumbers.FluentValidation/)
4+
[![NuGet downloads](https://img.shields.io/nuget/dt/PosInformatique.Foundations.PhoneNumbers.FluentValidation)](https://www.nuget.org/packages/PosInformatique.Foundations.PhoneNumbers.FluentValidation/)
5+
6+
## Introduction
7+
8+
This package provides [FluentValidation](https://fluentvalidation.net/) integration for the `PhoneNumber` value object
9+
from [PosInformatique.Foundations.PhoneNumbers](../PhoneNumbers/README.md).
10+
11+
It adds a dedicated validator and extension method to validate that string properties contain valid phone numbers
12+
in **E.164** format, using the same parsing and validation logic as the core `PhoneNumber` type.
13+
14+
## Install
15+
16+
You can install the package from [NuGet](https://www.nuget.org/packages/PosInformatique.Foundations.PhoneNumbers.FluentValidation/):
17+
18+
```powershell
19+
dotnet add package PosInformatique.Foundations.PhoneNumbers.FluentValidation
20+
```
21+
22+
## Features
23+
24+
- FluentValidation integration for phone number validation
25+
- Extension method `MustBePhoneNumber()` for `string` properties
26+
- Validation based on the core `PhoneNumber.IsValid()` logic (E.164 format)
27+
- `null` values are considered valid by default (combine with `NotNull()` / `NotEmpty()` when needed)
28+
- Consistent validation rules across your application
29+
30+
## Use cases
31+
32+
- Validate incoming DTOs or commands that contain phone numbers as strings
33+
- Ensure only valid E.164 phone numbers are accepted at the boundaries of your system
34+
- Reuse the same validation logic used by the `PhoneNumber` value object everywhere
35+
36+
## Examples
37+
38+
### Basic validation with MustBePhoneNumber
39+
40+
```csharp
41+
using FluentValidation;
42+
43+
public sealed class ContactDto
44+
{
45+
public string Name { get; set; } = default!;
46+
public string? Mobile { get; set; }
47+
}
48+
49+
public sealed class ContactDtoValidator : AbstractValidator<ContactDto>
50+
{
51+
public ContactDtoValidator()
52+
{
53+
RuleFor(x => x.Mobile)
54+
.MustBePhoneNumber(); // Validates Mobile as an E.164 phone number (or null)
55+
}
56+
}
57+
```
58+
59+
- If `Mobile` is `null`, the rule passes.
60+
- If `Mobile` is not `null`, it must be a valid phone number in E.164 format, otherwise validation fails with the default message:
61+
- `"'Mobile' must be a valid phone number in E.164 format."`
62+
63+
### Combine with NotNull / NotEmpty
64+
65+
If you want to make the phone number mandatory, combine `MustBePhoneNumber()` with standard FluentValidation rules:
66+
67+
```csharp
68+
public sealed class RequiredContactDtoValidator : AbstractValidator<ContactDto>
69+
{
70+
public RequiredContactDtoValidator()
71+
{
72+
RuleFor(x => x.Mobile)
73+
.NotEmpty()
74+
.MustBePhoneNumber();
75+
}
76+
}
77+
```
78+
79+
This enforces:
80+
81+
- `Mobile` is not `null` or empty.
82+
- `Mobile` must be a valid phone number in E.164 format.
83+
84+
## Links
85+
86+
- [NuGet package: PhoneNumbers (core library)](https://www.nuget.org/packages/PosInformatique.Foundations.PhoneNumbers/)
87+
- [NuGet package: PhoneNumbers.FluentValidation](https://www.nuget.org/packages/PosInformatique.Foundations.PhoneNumbers.FluentValidation/)
88+
- [Source code](https://github.com/PosInformatique/PosInformatique.Foundations)

src/PhoneNumbers/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -156,5 +156,6 @@ Console.WriteLine(formatted); // "+33123456789"
156156

157157
- [NuGet package (core library)](https://www.nuget.org/packages/PosInformatique.Foundations.PhoneNumbers/)
158158
- [NuGet package: PhoneNumbers.EntityFramework](https://www.nuget.org/packages/PosInformatique.Foundations.PhoneNumbers.EntityFramework/)
159+
- [NuGet package: PhoneNumbers.FluentValidation](https://www.nuget.org/packages/PosInformatique.Foundations.PhoneNumbers.FluentValidation/)
159160
- [NuGet package: PhoneNumbers.Json](https://www.nuget.org/packages/PosInformatique.Foundations.PhoneNumbers.Json/)
160161
- [Source code](https://github.com/PosInformatique/PosInformatique.Foundations)
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
//-----------------------------------------------------------------------
2+
// <copyright file="PhoneNumberValidatorTest.cs" company="P.O.S Informatique">
3+
// Copyright (c) P.O.S Informatique. All rights reserved.
4+
// </copyright>
5+
//-----------------------------------------------------------------------
6+
7+
namespace FluentValidation.Tests
8+
{
9+
using FluentValidation.Validators;
10+
using PosInformatique.Foundations.PhoneNumbers;
11+
12+
public class PhoneNumberValidatorTest
13+
{
14+
[Fact]
15+
public void Constructor()
16+
{
17+
var validator = new PhoneNumberValidator<object>();
18+
19+
validator.Name.Should().Be("PhoneNumberValidator");
20+
}
21+
22+
[Fact]
23+
public void GetDefaultMessageTemplate()
24+
{
25+
var validator = new PhoneNumberValidator<object>();
26+
27+
validator.As<IPropertyValidator>().GetDefaultMessageTemplate(default).Should().Be("'{PropertyName}' must be a valid phone number in E.164 format.");
28+
}
29+
30+
[Theory]
31+
[MemberData(nameof(PhoneNumberTestData.ValidPhoneNumbers), MemberType = typeof(PhoneNumberTestData))]
32+
#pragma warning disable SA1313 // Parameter names should begin with lower-case letter
33+
public void IsValid_True(string phoneNumber, string _)
34+
#pragma warning restore SA1313 // Parameter names should begin with lower-case letter
35+
{
36+
var validator = new PhoneNumberValidator<object>();
37+
38+
validator.IsValid(default, phoneNumber).Should().BeTrue();
39+
}
40+
41+
[Fact]
42+
public void IsValid_WithNull()
43+
{
44+
var validator = new PhoneNumberValidator<object>();
45+
46+
validator.IsValid(default!, null!).Should().BeTrue();
47+
}
48+
49+
[Theory]
50+
[MemberData(nameof(PhoneNumberTestData.InvalidPhoneNumbers), MemberType = typeof(PhoneNumberTestData))]
51+
public void IsValid_False(string phoneNumber)
52+
{
53+
var validator = new PhoneNumberValidator<object>();
54+
55+
validator.IsValid(default, phoneNumber).Should().BeFalse();
56+
}
57+
}
58+
}

0 commit comments

Comments
 (0)