Skip to content

Commit

Permalink
Create TokenSchedule.FluentValidation package (#9)
Browse files Browse the repository at this point in the history
* - initial create `TokenSchedule.FluentValidation` project

* - fix validators
- use `TokenSchedule.FluentValidation` in `TokenSchedule`

* - add `.props`

* - fix exist tests
- write validator tests

* - use `When`

* - update `README.md` of `TokenSchedule.FluentValidation` project

* - setting README files

* - update main readme
  • Loading branch information
ArdenHide authored Dec 25, 2024
1 parent c9e78a2 commit 80de9fd
Show file tree
Hide file tree
Showing 17 changed files with 455 additions and 56 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ A simple C# library for managing token distribution schedules.
## Features

- Define token distribution schedules with start and end times and ratios.
- Validate schedules to ensure correct distribution.
- Validate schedules to ensure correct distribution using [TokenSchedule.FluentValidation](https://github.com/The-Poolz/TokenSchedule/tree/master/src/TokenSchedule.FluentValidation/README.md).
- Retrieve the Token Generation Event (TGE) and subsequent distribution events.

## Usage
Expand Down
16 changes: 15 additions & 1 deletion TokenSchedule.sln
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{E1976F27-81A
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{DDB5240C-78C9-406B-9B5E-5595577F2086}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TokenSchedule.Tests", "tests\TokenSchedule.Tests\TokenSchedule.Tests.csproj", "{D1F8A56B-EAD1-4567-954E-D916DEAFA937}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TokenSchedule.Tests", "tests\TokenSchedule.Tests\TokenSchedule.Tests.csproj", "{D1F8A56B-EAD1-4567-954E-D916DEAFA937}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TokenSchedule.FluentValidation", "src\TokenSchedule.FluentValidation\TokenSchedule.FluentValidation.csproj", "{EE0274D2-00D1-4FD9-93D8-BE90BD5E6168}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TokenSchedule.FluentValidation.Tests", "tests\TokenSchedule.FluentValidation.Tests\TokenSchedule.FluentValidation.Tests.csproj", "{C6E981C2-D6AF-4844-B054-AAC35FFC1427}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Expand All @@ -25,13 +29,23 @@ Global
{D1F8A56B-EAD1-4567-954E-D916DEAFA937}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D1F8A56B-EAD1-4567-954E-D916DEAFA937}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D1F8A56B-EAD1-4567-954E-D916DEAFA937}.Release|Any CPU.Build.0 = Release|Any CPU
{EE0274D2-00D1-4FD9-93D8-BE90BD5E6168}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{EE0274D2-00D1-4FD9-93D8-BE90BD5E6168}.Debug|Any CPU.Build.0 = Debug|Any CPU
{EE0274D2-00D1-4FD9-93D8-BE90BD5E6168}.Release|Any CPU.ActiveCfg = Release|Any CPU
{EE0274D2-00D1-4FD9-93D8-BE90BD5E6168}.Release|Any CPU.Build.0 = Release|Any CPU
{C6E981C2-D6AF-4844-B054-AAC35FFC1427}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{C6E981C2-D6AF-4844-B054-AAC35FFC1427}.Debug|Any CPU.Build.0 = Debug|Any CPU
{C6E981C2-D6AF-4844-B054-AAC35FFC1427}.Release|Any CPU.ActiveCfg = Release|Any CPU
{C6E981C2-D6AF-4844-B054-AAC35FFC1427}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{68749023-CE2D-46AF-9427-4676BF5F9D49} = {E1976F27-81A3-4D53-ABAD-B59C5082E5E2}
{D1F8A56B-EAD1-4567-954E-D916DEAFA937} = {DDB5240C-78C9-406B-9B5E-5595577F2086}
{EE0274D2-00D1-4FD9-93D8-BE90BD5E6168} = {E1976F27-81A3-4D53-ABAD-B59C5082E5E2}
{C6E981C2-D6AF-4844-B054-AAC35FFC1427} = {DDB5240C-78C9-406B-9B5E-5595577F2086}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {6BC9FC1F-C841-4550-9C10-A193F14DE296}
Expand Down
21 changes: 21 additions & 0 deletions buildConf/.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<Project>
<PropertyGroup>
<Nullable>enable</Nullable>
<Version>1.1.0</Version>
<Authors>ArdenHide, Lomet</Authors>
<Company>The-Poolz</Company>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/The-Poolz/TokenSchedule</RepositoryUrl>
<PackageReleaseNotes>https://github.com/The-Poolz/TokenSchedule/releases/tag/v1.1.0</PackageReleaseNotes>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<IncludeSymbols>True</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>

<ItemGroup>
<None Include="..\..\LICENSE">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
using System;

namespace TokenSchedule.FluentValidation.Models
{
public interface IValidatedScheduleItem
{
public decimal Ratio { get; }
public DateTime StartDate { get; }
public DateTime? FinishDate { get; }
}
}
100 changes: 100 additions & 0 deletions src/TokenSchedule.FluentValidation/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
# TokenSchedule.FluentValidation

**TokenSchedule.FluentValidation** is a small library that provides validators for schedule items used in token distribution.
It is built on top of [FluentValidation](https://github.com/FluentValidation/FluentValidation) to handle validation dependencies separately.

## Features

- **`ScheduleItemValidator`**: Validates a single schedule item (`IValidatedScheduleItem`):
- Ensures `Ratio` is positive (`> 0`).
- Validates that `FinishDate` is either not set or later than `StartDate`.
- **`ScheduleValidator`**: Validates an entire schedule (`IEnumerable<IValidatedScheduleItem>`):
- Schedule cannot be null or empty.
- The sum of all `Ratio` values must be `1.0`.
- The first item in the schedule must have the earliest `StartDate` (treated as the Token Generation Event).
- Each individual item is validated by the `ScheduleItemValidator`.

## Installation

You can install this package via the .NET CLI:

```powershell
dotnet add package TokenSchedule.FluentValidation
```

Or via the NuGet Package Manager console:

```powershell
Install-Package TokenSchedule.FluentValidation
```

## Getting Started

Below is a simple example showing how to use `TokenSchedule.FluentValidation`:

```csharp
using System;
using System.Collections.Generic;
using FluentValidation;
using TokenSchedule.FluentValidation;
using TokenSchedule.FluentValidation.Models;

public class MyScheduleItem : IValidatedScheduleItem
{
public decimal Ratio { get; set; }
public DateTime StartDate { get; set; }
public DateTime? FinishDate { get; set; }
}

class Program
{
static void Main()
{
// Create some schedule items
var scheduleItems = new List<IValidatedScheduleItem>
{
new MyScheduleItem
{
Ratio = 0.5m,
StartDate = new DateTime(2024, 1, 1),
FinishDate = new DateTime(2024, 6, 1)
},
new MyScheduleItem
{
Ratio = 0.5m,
StartDate = new DateTime(2024, 6, 2),
FinishDate = new DateTime(2024, 12, 31)
}
};

// Create an instance of the ScheduleValidator
var validator = new ScheduleValidator();

// Validate the schedule
var result = validator.Validate(scheduleItems);

if (result.IsValid)
{
Console.WriteLine("Schedule is valid!");
}
else
{
Console.WriteLine("Schedule is invalid. Errors:");
foreach (var error in result.Errors)
{
Console.WriteLine($"- {error.ErrorMessage}");
}
}
}
}
```

### Explanation
1. **Define your schedule items** by implementing the `IValidatedScheduleItem` interface.
2. **Create a list of schedule items** that you want to validate.
3. **Use the `ScheduleValidator`** to validate the entire collection.
4. **Handle the validation results**, e.g. by displaying errors or by throwing exceptions.

## License

This project is licensed under the MIT License - see the LICENSE file for details.
20 changes: 20 additions & 0 deletions src/TokenSchedule.FluentValidation/ScheduleItemValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
using FluentValidation;
using TokenSchedule.FluentValidation.Models;

namespace TokenSchedule.FluentValidation
{
public class ScheduleItemValidator : AbstractValidator<IValidatedScheduleItem>
{
public ScheduleItemValidator()
{
RuleFor(item => item)
.Cascade(CascadeMode.Stop)
.NotNull()
.Must(item => item.Ratio > 0)
.WithMessage("Ratio must be positive.")
.Must(item => item.StartDate < item.FinishDate!.Value)
.When(item => item.FinishDate.HasValue)
.WithMessage("End time must be greater than start time.");
}
}
}
24 changes: 24 additions & 0 deletions src/TokenSchedule.FluentValidation/ScheduleValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
using System.Linq;
using FluentValidation;
using System.Collections.Generic;
using TokenSchedule.FluentValidation.Models;

namespace TokenSchedule.FluentValidation
{
public class ScheduleValidator : AbstractValidator<IEnumerable<IValidatedScheduleItem>>
{
public ScheduleValidator()
{
RuleFor(schedule => schedule.ToArray())
.Cascade(CascadeMode.Stop)
.NotNull()
.NotEmpty()
.WithMessage("Schedule must contain 1 or more elements.")
.Must(schedule => schedule.Sum(item => item.Ratio) == 1.0m)
.WithMessage("The sum of the ratios must be 1.")
.Must(schedule => schedule[0].StartDate == schedule.Min(x => x.StartDate))
.WithMessage("The first element must be the TGE (Token Generation Event).")
.ForEach(item => item.SetValidator(new ScheduleItemValidator()));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="..\..\buildConf\.props" />
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Nullable>enable</Nullable>
<PackageId>TokenSchedule.FluentValidation</PackageId>
<Product>TokenSchedule.FluentValidation</Product>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="FluentValidation" Version="11.11.0" />
</ItemGroup>

<ItemGroup>
<None Update="README.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>

</Project>
16 changes: 6 additions & 10 deletions src/TokenSchedule/ScheduleItem.cs
Original file line number Diff line number Diff line change
@@ -1,27 +1,23 @@
using System;
using FluentValidation;
using TokenSchedule.FluentValidation;
using TokenSchedule.FluentValidation.Models;

namespace TokenSchedule
{
public class ScheduleItem
public class ScheduleItem : IValidatedScheduleItem
{
public decimal Ratio { get; }
public DateTime StartDate { get; }
public DateTime? FinishDate { get; }

public ScheduleItem(decimal ratio, DateTime startDate, DateTime? finishDate = null)
{
if (finishDate.HasValue && startDate >= finishDate.Value)
{
throw new ArgumentException("End time must be greater than start time.", nameof(startDate));
}
if (ratio <= 0)
{
throw new ArgumentException("Ratio must be positive.", nameof(ratio));
}

Ratio = ratio;
StartDate = startDate;
FinishDate = finishDate;

new ScheduleItemValidator().ValidateAndThrow(this);
}
}
}
22 changes: 3 additions & 19 deletions src/TokenSchedule/ScheduleManager.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
using System;
using System.Linq;
using FluentValidation;
using System.Collections.Generic;
using TokenSchedule.FluentValidation;

namespace TokenSchedule
{
Expand All @@ -17,25 +19,7 @@ public ScheduleManager(IEnumerable<ScheduleItem> schedule) : this(schedule.ToArr

public ScheduleManager(params ScheduleItem[] schedule)
{
if (schedule == null)
{
throw new ArgumentNullException(nameof(schedule));
}

if (!schedule.Any())
{
throw new ArgumentException("Schedule must contain 1 or more elements.", nameof(schedule));
}

if (schedule.Sum(x => x.Ratio) != 1)
{
throw new ArgumentException("The sum of the ratios must be 1.", nameof(schedule));
}

if (schedule[0].StartDate != schedule.Min(x => x.StartDate))
{
throw new ArgumentException("The first element must be the TGE (Token Generation Event).", nameof(schedule));
}
new ScheduleValidator().ValidateAndThrow(schedule);

Schedule = schedule;
TGE = schedule[0];
Expand Down
20 changes: 5 additions & 15 deletions src/TokenSchedule/TokenSchedule.csproj
Original file line number Diff line number Diff line change
@@ -1,30 +1,20 @@
<Project Sdk="Microsoft.NET.Sdk">

<Import Project="..\..\buildConf\.props" />
<PropertyGroup>
<TargetFramework>netstandard2.1</TargetFramework>
<Nullable>enable</Nullable>
<PackageId>TokenSchedule</PackageId>
<Version>1.0.1</Version>
<Authors>Lomet, ArdenHide</Authors>
<Company>The-Poolz</Company>
<Product>TokenSchedule</Product>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryType>git</RepositoryType>
<RepositoryUrl>https://github.com/The-Poolz/TokenSchedule</RepositoryUrl>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<IncludeSymbols>True</IncludeSymbols>
<SymbolPackageFormat>snupkg</SymbolPackageFormat>
</PropertyGroup>

<ItemGroup>
<None Include="..\..\LICENSE">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
<None Include="..\..\README.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>

<ItemGroup>
<ProjectReference Include="..\TokenSchedule.FluentValidation\TokenSchedule.FluentValidation.csproj" />
</ItemGroup>

</Project>
Loading

0 comments on commit 80de9fd

Please sign in to comment.