Skip to content

Commit

Permalink
feat: Created library and added initial implementation (#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
samtrion authored Jun 11, 2024
1 parent 94e3071 commit 59a68de
Show file tree
Hide file tree
Showing 28 changed files with 1,328 additions and 8 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
name: Build & Tests
uses: dailydevops/pipelines/.github/workflows/[email protected]
with:
disablePublish: true
enableSonarQube: true
dotnet-logging: ${{ inputs.dotnet-logging }}
dotnet-version: ${{ vars.NE_DOTNET_TARGETFRAMEWORKS }}
solution: ./FluentValue.sln
Expand Down
14 changes: 10 additions & 4 deletions Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,17 @@

<PropertyGroup Label="Package settings">
<Title>$(MSBuildProjectName)</Title>
<Description></Description>
<RepositoryUrl></RepositoryUrl>
<PackageProjectUrl></PackageProjectUrl>
<PackageTags></PackageTags>
<Description>The fluent value validation library provides a set of fluent interfaces to validate values.</Description>
<RepositoryUrl>https://github.com/dailydevops/fluentvalue</RepositoryUrl>
<PackageProjectUrl>https://github.com/dailydevops/fluentvalue.git</PackageProjectUrl>
<PackageReleaseNotes>$(RepositoryUrl)/releases</PackageReleaseNotes>
<PackageTags>fluent;value;validation;</PackageTags>
<CopyrightYearStart>2024</CopyrightYearStart>
</PropertyGroup>

<PropertyGroup>
<NetEvolve_ProjectTargetFrameworks>net6.0;net8.0</NetEvolve_ProjectTargetFrameworks>
<NetEvolve_TestTargetFrameworks>net6.0;net7.0;net8.0</NetEvolve_TestTargetFrameworks>
</PropertyGroup>

</Project>
11 changes: 10 additions & 1 deletion Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,16 @@
<GlobalPackageReference Include="Microsoft.CodeAnalysis.NetAnalyzers" Version="8.0.0" />
<GlobalPackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" />
<GlobalPackageReference Include="Microsoft.VisualStudio.Threading.Analyzers" Version="17.10.48" />
<GlobalPackageReference Include="SonarAnalyzer.CSharp" Version="9.25.1.91650" Condition=" '$(BuildingInsideVisualStudio)' == 'true' " />
<GlobalPackageReference Include="SonarAnalyzer.CSharp" Version="9.26.0.92422" Condition=" '$(BuildingInsideVisualStudio)' == 'true' " />
</ItemGroup>
<ItemGroup>
<PackageVersion Include="coverlet.collector" Version="6.0.2" />
<PackageVersion Include="coverlet.msbuild" Version="6.0.2" />
<PackageVersion Include="Microsoft.NET.Test.Sdk" Version="17.10.0" />
<PackageVersion Include="NetEvolve.Arguments" Version="1.2.12" />
<PackageVersion Include="NetEvolve.Extensions.XUnit" Version="2.2.12" />
<PackageVersion Include="xunit" Version="2.8.1" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.8.1" />
</ItemGroup>

</Project>
49 changes: 49 additions & 0 deletions FluentValue.sln
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,61 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.0.31903.59
MinimumVisualStudioVersion = 10.0.40219.1
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{BB2B11D1-1E37-4333-9E07-E6709C1796F5}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetEvolve.FluentValue", "src\NetEvolve.FluentValue\NetEvolve.FluentValue.csproj", "{7B62BC70-8970-4D45-A86C-CB6C7E64A5D9}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{AE6A68C3-E24A-45BC-B7A6-4D527AF3BF99}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetEvolve.FluentValue.Tests.Unit", "tests\NetEvolve.FluentValue.Tests.Unit\NetEvolve.FluentValue.Tests.Unit.csproj", "{93D85E57-7EB9-4CDD-836E-301A58BF3312}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "NetEvolve.FluentValue.Tests.Architecture", "tests\NetEvolve.FluentValue.Tests.Architecture\NetEvolve.FluentValue.Tests.Architecture.csproj", "{6C599754-9C81-494C-9136-98259711B6C8}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{2FF9F0BF-A46C-4C74-BD17-25B38E8D85EE}"
ProjectSection(SolutionItems) = preProject
.commitlintrc = .commitlintrc
.editorconfig = .editorconfig
.filenesting.json = .filenesting.json
.gitattributes = .gitattributes
.gitignore = .gitignore
.gitmodules = .gitmodules
Directory.Build.props = Directory.Build.props
Directory.Build.targets = Directory.Build.targets
Directory.Packages.props = Directory.Packages.props
Directory.Solution.props = Directory.Solution.props
GitVersion.yml = GitVersion.yml
LICENSE = LICENSE
new-project.ps1 = new-project.ps1
nuget.config = nuget.config
README.md = README.md
update-solution.ps1 = update-solution.ps1
EndProjectSection
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{7B62BC70-8970-4D45-A86C-CB6C7E64A5D9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{7B62BC70-8970-4D45-A86C-CB6C7E64A5D9}.Debug|Any CPU.Build.0 = Debug|Any CPU
{7B62BC70-8970-4D45-A86C-CB6C7E64A5D9}.Release|Any CPU.ActiveCfg = Release|Any CPU
{7B62BC70-8970-4D45-A86C-CB6C7E64A5D9}.Release|Any CPU.Build.0 = Release|Any CPU
{93D85E57-7EB9-4CDD-836E-301A58BF3312}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{93D85E57-7EB9-4CDD-836E-301A58BF3312}.Debug|Any CPU.Build.0 = Debug|Any CPU
{93D85E57-7EB9-4CDD-836E-301A58BF3312}.Release|Any CPU.ActiveCfg = Release|Any CPU
{93D85E57-7EB9-4CDD-836E-301A58BF3312}.Release|Any CPU.Build.0 = Release|Any CPU
{6C599754-9C81-494C-9136-98259711B6C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6C599754-9C81-494C-9136-98259711B6C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6C599754-9C81-494C-9136-98259711B6C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{6C599754-9C81-494C-9136-98259711B6C8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{7B62BC70-8970-4D45-A86C-CB6C7E64A5D9} = {BB2B11D1-1E37-4333-9E07-E6709C1796F5}
{93D85E57-7EB9-4CDD-836E-301A58BF3312} = {AE6A68C3-E24A-45BC-B7A6-4D527AF3BF99}
{6C599754-9C81-494C-9136-98259711B6C8} = {AE6A68C3-E24A-45BC-B7A6-4D527AF3BF99}
EndGlobalSection
EndGlobal
11 changes: 9 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
# template-dotnet
.NET template for repositories
# NetEvolve.FluentValue

This is a simple library that allows you to validate the given value against a set of constraints. It is designed to be used in a fluent way, so you can chain multiple constraints together. Most of the constraints have multipe execution paths, so that based on the value, delivers the most fitting result.

## Installation

```bash
dotnet add package NetEvolve.FluentValue
```
42 changes: 42 additions & 0 deletions src/NetEvolve.FluentValue/Constraints/ConstraintBase.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
namespace NetEvolve.FluentValue.Constraints;

using System;
using System.Text;
using NetEvolve.FluentValue;

internal abstract class ConstraintBase : IConstraint
{
[ThreadStatic]
private static StringBuilder? _builder;

private const int DefaultCapacity = 1024;

public abstract bool IsSatisfiedBy(object? value);

public abstract void SetDescription(StringBuilder builder);

public override string ToString()
{
var builder = _builder ?? new StringBuilder(capacity: DefaultCapacity);
#pragma warning disable S2696 // Instance members should not write to "static" fields
_builder = null;
#pragma warning restore S2696 // Instance members should not write to "static" fields
try
{
_ = builder.Append('"').Append("{Value}");

SetDescription(builder);

return builder.Append('.').Append('"').ToString();
}
finally
{
_ = builder.Clear();
if (builder.Capacity > DefaultCapacity)
{
builder.Capacity = DefaultCapacity;
}
_builder = builder;
}
}
}
43 changes: 43 additions & 0 deletions src/NetEvolve.FluentValue/Constraints/ContainsConstraint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
namespace NetEvolve.FluentValue.Constraints;

using System;
using System.Collections;
using System.Linq;
using System.Text;

internal sealed class ContainsConstraint : ConstraintBase
{
private readonly object? _compareValue;
private readonly StringComparison? _comparison;

public ContainsConstraint(char compareValue, StringComparison comparison)
{
_compareValue = compareValue;
_comparison = comparison;
}

public ContainsConstraint(string compareValue, StringComparison comparison)
{
_compareValue = compareValue;
_comparison = comparison;
}

public ContainsConstraint(object? compareValue) => _compareValue = compareValue;

public override bool IsSatisfiedBy(object? value) =>
value switch
{
null => false,
string stringValue when _compareValue is string compareValue
=> stringValue.Contains(compareValue, _comparison ?? default),
string stringValue when _compareValue is char compareValue
=> stringValue.Contains(compareValue, _comparison ?? default),
IDictionary dictionary => dictionary.Contains(_compareValue!),
IList list => list.Contains(_compareValue!),
IEnumerable enumerable => enumerable.Cast<object?>().Contains(_compareValue),
_ => throw new NotSupportedException($"Invalid type `{value!.GetType().FullName}`.")
};

public override void SetDescription(StringBuilder builder) =>
builder.Append(" contains `").Append(_compareValue).Append('`');
}
26 changes: 26 additions & 0 deletions src/NetEvolve.FluentValue/Constraints/DefaultConstraint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
namespace NetEvolve.FluentValue.Constraints;

using System;
using System.Text;

internal sealed class DefaultConstraint : ConstraintBase
{
public override bool IsSatisfiedBy(object? value) =>
value?.GetType() switch
{
{ IsValueType: true } valueType => GetDefault(valueType).Equals(value),
_ => false
};

public override void SetDescription(StringBuilder builder) => builder.Append(" is <default>");

private static object GetDefault(Type value)
{
var underlying = Nullable.GetUnderlyingType(value);
if (underlying is not null)
{
return Activator.CreateInstance(underlying)!;
}
return Activator.CreateInstance(value)!;
}
}
20 changes: 20 additions & 0 deletions src/NetEvolve.FluentValue/Constraints/EmptyConstraint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
namespace NetEvolve.FluentValue.Constraints;

using System;
using System.Collections;
using System.Text;

internal sealed class EmptyConstraint : ConstraintBase
{
public override bool IsSatisfiedBy(object? value) =>
value switch
{
string stringValue => stringValue.Length == 0,
Guid guidValue => guidValue == Guid.Empty,
ICollection collection => collection.Count == 0,
IEnumerable enumerable => !enumerable.GetEnumerator().MoveNext(),
_ => false
};

public override void SetDescription(StringBuilder builder) => builder.Append(" is <empty>");
}
30 changes: 30 additions & 0 deletions src/NetEvolve.FluentValue/Constraints/EndsWithConstraint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
namespace NetEvolve.FluentValue.Constraints;

using System;
using System.Text;

internal sealed class EndsWithConstraint : ConstraintBase
{
private readonly object? _compareValue;
private readonly StringComparison? _comparison;

public EndsWithConstraint(char compareValue) => _compareValue = compareValue;

public EndsWithConstraint(string compareValue, StringComparison comparison)
{
_compareValue = compareValue;
_comparison = comparison;
}

public override bool IsSatisfiedBy(object? value) =>
value switch
{
string stringValue when _compareValue is string compare
=> stringValue.EndsWith(compare, _comparison ?? default),
string stringValue when _compareValue is char compare => stringValue.EndsWith(compare),
_ => false
};

public override void SetDescription(StringBuilder builder) =>
builder.Append(" ends with `").Append(_compareValue).Append('`');
}
31 changes: 31 additions & 0 deletions src/NetEvolve.FluentValue/Constraints/EqualToConstraint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
namespace NetEvolve.FluentValue.Constraints;

using System;
using System.Text;

internal sealed class EqualToConstraint : ConstraintBase
{
private readonly object? _compareValue;
private readonly StringComparison? _comparison;

public EqualToConstraint(object? compareValue) => _compareValue = compareValue;

public EqualToConstraint(string compareValue, StringComparison comparison)
{
_compareValue = compareValue;
_comparison = comparison;
}

public override bool IsSatisfiedBy(object? value) =>
value switch
{
string stringValue when _compareValue is string compareValue
=> stringValue.Equals(compareValue, _comparison ?? default),
string stringValue when _compareValue is IConvertible convertible
=> stringValue.Equals(convertible.ToString(), _comparison ?? default),
_ => false
};

public override void SetDescription(StringBuilder builder) =>
builder.Append(" is equal to `").Append(_compareValue).Append('`');
}
28 changes: 28 additions & 0 deletions src/NetEvolve.FluentValue/Constraints/MatchesConstraint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
namespace NetEvolve.FluentValue.Constraints;

using System;
using System.Text;
using System.Text.RegularExpressions;

internal sealed class MatchesConstraint : ConstraintBase
{
private readonly string _pattern;
private readonly Regex _regex;

public MatchesConstraint(string pattern, RegexOptions? options)
{
_pattern = pattern;
_regex = new Regex(pattern, options ?? default);

Check warning on line 15 in src/NetEvolve.FluentValue/Constraints/MatchesConstraint.cs

View workflow job for this annotation

GitHub Actions / Build & Tests / Tests / Testing .NET solution

Pass a timeout to limit the execution time. (https://rules.sonarsource.com/csharp/RSPEC-6444)

Check warning on line 15 in src/NetEvolve.FluentValue/Constraints/MatchesConstraint.cs

View workflow job for this annotation

GitHub Actions / Build & Tests / Tests / Testing .NET solution

Pass a timeout to limit the execution time. (https://rules.sonarsource.com/csharp/RSPEC-6444)

Check warning on line 15 in src/NetEvolve.FluentValue/Constraints/MatchesConstraint.cs

View workflow job for this annotation

GitHub Actions / Build & Tests / Tests / Testing .NET solution

Pass a timeout to limit the execution time. (https://rules.sonarsource.com/csharp/RSPEC-6444)

Check warning on line 15 in src/NetEvolve.FluentValue/Constraints/MatchesConstraint.cs

View workflow job for this annotation

GitHub Actions / Build & Tests / Tests / Testing .NET solution

Pass a timeout to limit the execution time. (https://rules.sonarsource.com/csharp/RSPEC-6444)
}

public override bool IsSatisfiedBy(object? value) =>
value switch
{
string stringValue => _regex.IsMatch(stringValue),
IConvertible convertible => _regex.IsMatch(convertible.ToString()!),
_ => false
};

public override void SetDescription(StringBuilder builder) =>
builder.Append(" matches `").Append(_pattern).Append('`');
}
10 changes: 10 additions & 0 deletions src/NetEvolve.FluentValue/Constraints/NullConstraint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
namespace NetEvolve.FluentValue.Constraints;

using System.Text;

internal sealed class NullConstraint : ConstraintBase
{
public override bool IsSatisfiedBy(object? value) => value is null;

public override void SetDescription(StringBuilder builder) => builder.Append(" is <null>");
}
31 changes: 31 additions & 0 deletions src/NetEvolve.FluentValue/Constraints/ParenthesisConstraint.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
namespace NetEvolve.FluentValue.Constraints;

using System.Text;
using NetEvolve.Arguments;
using NetEvolve.FluentValue;

internal sealed class ParenthesisConstraint : ConstraintBase
{
private readonly ConstraintBase _constraint;

public ParenthesisConstraint(IConstraint constraint)
{
Argument.ThrowIfNull(constraint);

_constraint = (ConstraintBase)constraint;
}

public override bool IsSatisfiedBy(object? value)
{
Argument.ThrowIfNull(_constraint);

return _constraint.IsSatisfiedBy(value);
}

public override void SetDescription(StringBuilder builder)
{
_ = builder.Append(" (");
_constraint.SetDescription(builder);
_ = builder.Replace("( ", "(").Append(')');
}
}
Loading

0 comments on commit 59a68de

Please sign in to comment.