Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add guard clauses for SmartEnum #533

Merged
merged 13 commits into from
Sep 11, 2024
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
35 changes: 35 additions & 0 deletions .github/workflows/publish-guardclauses.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
name: publish SmartEnum.GuardClauses to nuget
on:
workflow_dispatch:
push:
branches:
- main # Your default release branch
paths:
- 'src/SmartEnum.GuardClauses/**'
jobs:
publish:
name: list SmartEnum.GuardClauses on nuget.org
runs-on: windows-latest
steps:
- uses: actions/checkout@v2

# Required for a specific dotnet version that doesn't come with ubuntu-latest / windows-latest
# Visit bit.ly/2synnZl to see the list of SDKs that are pre-installed with ubuntu-latest / windows-latest
- name: Setup .NET Core
uses: actions/setup-dotnet@v3
with:
dotnet-version: |
6.0.x
7.0.x
8.0.x

# Publish
- name: publish on version change
uses: alirezanet/publish-nuget@v3.0.0
with:
PROJECT_FILE_PATH: src/SmartEnum.GuardClauses/SmartEnum.GuardClauses.csproj # Relative to repository root
VERSION_FILE_PATH: Directory.Build.props # Filepath with version info, relative to repository root. Defaults to project file
VERSION_REGEX: <Version>(.*)<\/Version> # Regex pattern to extract version info in a capturing group
TAG_COMMIT: true # Flag to enable / disable git tagging
TAG_FORMAT: SmartEnumGuardClauses-v* # Format of the git tag, [*] gets replaced with version
NUGET_KEY: ${{secrets.NUGET_API_KEY}} # nuget.org API key
6 changes: 3 additions & 3 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
<ManagePackageVersionsCentrally>true</ManagePackageVersionsCentrally>
</PropertyGroup>
<ItemGroup>
<PackageVersion Include="Ardalis.GuardClauses" Version="4.5.0" />
<PackageVersion Include="AutoFixture" Version="4.18.1" />
<PackageVersion Include="BenchmarkDotNet" Version="0.13.10" />
<PackageVersion Include="Constant" Version="2.0.4" />
Expand All @@ -22,14 +23,13 @@
<PackageVersion Include="Newtonsoft.Json" Version="13.0.2" />
<PackageVersion Include="protobuf-net" Version="3.2.26" />
<PackageVersion Include="System.ComponentModel.Annotations" Version="5.0.0" />
<PackageVersion Include="System.Text.Json" Version="8.0.2" />
<PackageVersion Include="System.Text.Json" Version="8.0.4" />
<PackageVersion Include="Utf8Json" Version="1.3.7" />
<PackageVersion Include="xunit" Version="2.7.0" />
<PackageVersion Include="xunit.runner.visualstudio" Version="2.4.5" />
</ItemGroup>

<ItemGroup>
<GlobalPackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />
<GlobalPackageReference Include="SonarAnalyzer.CSharp" Version="9.17.0.82934" PrivateAssets="All" />
<GlobalPackageReference Include="SonarAnalyzer.CSharp" Version="9.17.0.82934" PrivateAssets="All" />
</ItemGroup>
</Project>
14 changes: 14 additions & 0 deletions SmartEnum.sln
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,10 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SmartEnum.Dapper.UnitTests"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SmartEnum.Dapper.IntegrationTests", "test\SmartEnum.Dapper.IntegrationTests\SmartEnum.Dapper.IntegrationTests.csproj", "{ACCA93E9-EE80-490C-81A3-824086E4EA2F}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SmartEnum.GuardClauses", "src\SmartEnum.GuardClauses\SmartEnum.GuardClauses.csproj", "{A720F348-2176-4A47-ADC5-CC2664FDA516}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "SmartEnum.GuardClauses.UnitTests", "test\SmartEnum.GuardClauses.UnitTests\SmartEnum.GuardClauses.UnitTests.csproj", "{B7B944B3-E9DC-4CFC-BADC-11EC2F226AA2}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -162,6 +166,14 @@ Global
{ACCA93E9-EE80-490C-81A3-824086E4EA2F}.Debug|Any CPU.Build.0 = Debug|Any CPU
{ACCA93E9-EE80-490C-81A3-824086E4EA2F}.Release|Any CPU.ActiveCfg = Release|Any CPU
{ACCA93E9-EE80-490C-81A3-824086E4EA2F}.Release|Any CPU.Build.0 = Release|Any CPU
{A720F348-2176-4A47-ADC5-CC2664FDA516}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A720F348-2176-4A47-ADC5-CC2664FDA516}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A720F348-2176-4A47-ADC5-CC2664FDA516}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A720F348-2176-4A47-ADC5-CC2664FDA516}.Release|Any CPU.Build.0 = Release|Any CPU
{B7B944B3-E9DC-4CFC-BADC-11EC2F226AA2}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{B7B944B3-E9DC-4CFC-BADC-11EC2F226AA2}.Debug|Any CPU.Build.0 = Debug|Any CPU
{B7B944B3-E9DC-4CFC-BADC-11EC2F226AA2}.Release|Any CPU.ActiveCfg = Release|Any CPU
{B7B944B3-E9DC-4CFC-BADC-11EC2F226AA2}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand Down Expand Up @@ -190,6 +202,8 @@ Global
{7E08FCFA-2318-4D36-BAB5-8AFA41F00CC3} = {FA199ECB-5F29-442A-AAC6-91DBCB7A5A04}
{ADBD5097-87A4-492B-9399-6A4CCC53CD5A} = {79268877-BBEF-4DE2-B8D9-697F21933159}
{ACCA93E9-EE80-490C-81A3-824086E4EA2F} = {EF5634F4-4667-4481-934C-D1CFA042AD0B}
{A720F348-2176-4A47-ADC5-CC2664FDA516} = {FA199ECB-5F29-442A-AAC6-91DBCB7A5A04}
{B7B944B3-E9DC-4CFC-BADC-11EC2F226AA2} = {79268877-BBEF-4DE2-B8D9-697F21933159}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {46896DE3-41B8-442F-A6FB-6AC9F11CCBCE}
Expand Down
66 changes: 66 additions & 0 deletions src/SmartEnum.GuardClauses/GuardAgainstSmartEnumOutOfRange.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
using Ardalis.GuardClauses;
using System;

namespace Ardalis.SmartEnum.GuardClauses
{
/// <summary>
/// Provides guard clauses to ensure input values are valid instances of a specified SmartEnum.
/// </summary>
public static class GuardAgainstSmartEnumOutOfRange
{
/// <summary>
/// Throws a<see cref="SmartEnumNotFoundException" /> or a custom<see cref="Exception" />
/// if <paramref name="input"/> is not a valid <see cref="SmartEnum{TEnum}"/> value.
/// </summary>
/// <typeparam name="TEnum">The type of the smart enum.</typeparam>
/// <param name="guardClause">The guard clause interface.</param>
/// <param name="input">The value to check against the smart enum values.</param>
/// <param name="message">Optional. Custom error message to pass to <see cref="SmartEnumNotFoundException"/>.</param>
/// <param name="exceptionCreator">Optional. A function that creates a custom exception.</param>
/// <returns>The valid <see cref="SmartEnum{TEnum}"/> value <paramref name="input" />.</returns>
/// <exception cref="SmartEnumNotFoundException">Thrown when <paramref name="input" />
/// is not a valid enum value, and no custom exception is provided.</exception>
/// <exception cref="Exception">Thrown when a custom exception is provided by <paramref name="exceptionCreator" />.</exception>
public static TEnum SmartEnumOutOfRange<TEnum>(
this IGuardClause guardClause,
int input,
string message = null,
Func<Exception> exceptionCreator = null)
where TEnum : SmartEnum<TEnum>
{
return guardClause.SmartEnumOutOfRange<TEnum, int>(input, message, exceptionCreator);
}

/// <summary>
/// Throws a <see cref="SmartEnumNotFoundException"/> or a custom <see cref="Exception"/>
/// if <paramref name="input"/> is not a valid <see cref="SmartEnum{TEnum, TValue}"/> value.
/// </summary>
/// <typeparam name="TEnum">The type of the smart enum.</typeparam>
/// <typeparam name="TValue">The type of the value that the smart enum uses.</typeparam>
/// <param name="guardClause">The guard clause interface.</param>
/// <param name="input">The value to check against the smart enum values.</param>
/// <param name="message">Optional. Custom error message to pass to <see cref="SmartEnumNotFoundException"/>.</param>
/// <param name="exceptionCreator">Optional. A function that creates a custom exception.</param>
/// <returns>The valid enum value <typeparamref name="TEnum"/> corresponding to <paramref name="input"/>.</returns>
/// <exception cref="SmartEnumNotFoundException">Thrown when <paramref name="input"/>
/// is not a valid enum value and no custom exception is provided.</exception>
/// <exception cref="Exception">Thrown when a custom exception
/// is provided by <paramref name="exceptionCreator"/>.</exception>
public static TEnum SmartEnumOutOfRange<TEnum, TValue>(
this IGuardClause guardClause,
TValue input,
string message = null,
Func<Exception> exceptionCreator = null)
where TEnum : SmartEnum<TEnum, TValue>
where TValue : IEquatable<TValue>, IComparable<TValue>
{
if (SmartEnum<TEnum, TValue>.TryFromValue(input, out TEnum result))
{
return result;
}

var exceptionMessage = message ?? $"The value '{input}' is not a valid {typeof(TEnum).Name}.";
throw exceptionCreator?.Invoke() ?? new SmartEnumNotFoundException(exceptionMessage);
}
}
}
119 changes: 119 additions & 0 deletions src/SmartEnum.GuardClauses/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Ardalis.SmartEnum.GuardClauses

Ardalis.SmartEnum.GuardClauses is a NuGet package that provides guard clauses to ensure input values are valid instances of a specified SmartEnum. It helps you to validate that a given value corresponds to a valid SmartEnum value and throws appropriate exceptions if it is not.

## Installation

To install the Ardalis.SmartEnum.GuardClauses package, run the following command in the NuGet Package Manager Console:

```bash
Install-Package Ardalis.SmartEnum.GuardClauses
```

Alternatively, you can install it via the .NET CLI:

```bash
dotnet add package Ardalis.SmartEnum.GuardClauses
```

## Usage

### SmartEnumOutOfRange Method

The primary method provided by this package is `SmartEnumOutOfRange`, which can be used to validate if an input value is a valid `SmartEnum`.

### Example Usage

Here's an example of how to use the `SmartEnumOutOfRange` method:

```csharp
using Ardalis.GuardClauses;
using Ardalis.SmartEnum;
using Ardalis.SmartEnum.GuardClauses;
using System;

public class Status : SmartEnum<Status>
{
public static readonly Status Draft = new Status(nameof(Draft), 1);
public static readonly Status Published = new Status(nameof(Published), 2);
public static readonly Status Archived = new Status(nameof(Archived), 3);

private Status(string name, int value) : base(name, value) { }
}

public class Example
{
public void ValidateStatus(int statusValue)
{
// This will throw a SmartEnumNotFoundException if the statusValue is not a valid Status
Status status = Guard.Against.SmartEnumOutOfRange<Status>(statusValue);
Console.WriteLine($"Validated status: {status.Name}");
}
}
```

In this example, the `ValidateStatus` method checks if the `statusValue` is a valid `Status` SmartEnum. If the value is invalid, a `SmartEnumNotFoundException` is thrown.

### Custom Exception Handling

You can also pass a custom exception creator function to the `SmartEnumOutOfRange` method:

```csharp
public void ValidateStatusWithCustomException(int statusValue)
{
try
{
Status status = Guard.Against.SmartEnumOutOfRange<Status>(statusValue, exceptionCreator: () => new ArgumentException("Invalid status value provided."));
Console.WriteLine($"Validated status: {status.Name}");
}
catch (Exception ex)
{
Console.WriteLine($"Exception caught: {ex.Message}");
}
}
```

In this example, if the `statusValue` is not valid, a custom `ArgumentException` will be thrown instead of the default `SmartEnumNotFoundException`.

### Supporting Different Value Types

The package also supports SmartEnums with different value types, such as `string`, `Guid`, etc.:

```csharp
public class Color : SmartEnum<Color, string>
{
public static readonly Color Red = new Color(nameof(Red), "FF0000");
public static readonly Color Green = new Color(nameof(Green), "00FF00");
public static readonly Color Blue = new Color(nameof(Blue), "0000FF");

private Color(string name, string value) : base(name, value) { }
}

public void ValidateColor(string colorValue)
{
Color color = Guard.Against.SmartEnumOutOfRange<Color, string>(colorValue);
Console.WriteLine($"Validated color: {color.Name}");
}
```

In this case, the `SmartEnumOutOfRange` method checks if `colorValue` corresponds to a valid `Color` SmartEnum.

## Additional Information

For more details on the SmartEnum package and its usage, check out the official [SmartEnum repository](https://github.com/ardalis/SmartEnum/).

## License

This project is licensed under the MIT License - see the [LICENSE](https://github.com/ardalis/SmartEnum/blob/main/LICENSE) file for details.

## Contributing

Contributions are welcome! Please see the [CONTRIBUTING](https://github.com/ardalis/SmartEnum/blob/main/CONTRIBUTING.md) guide for details.

## Acknowledgements

Special thanks to [Ardalis](https://github.com/ardalis) for creating the SmartEnum package and to all contributors for their ongoing efforts.

---

This README provides an overview of how to use the Ardalis.SmartEnum.GuardClauses package. Make sure to check out the [SmartEnum repository](https://github.com/ardalis/SmartEnum/) for further examples and documentation.
32 changes: 32 additions & 0 deletions src/SmartEnum.GuardClauses/SmartEnum.GuardClauses.csproj
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<PackageId>Ardalis.SmartEnum.GuardClauses</PackageId>
<Title>Ardalis.SmartEnum.GuardClauses</Title>
<Description>Guard clauses for Ardalis.SmartEnum.</Description>
<Summary>Guard clauses for Ardalis.SmartEnum.</Summary>
<PackageTags>enum;smartenum;ardalis;guard;guardclauses</PackageTags>
<PackageReleaseNotes>Added support for guard clauses</PackageReleaseNotes>
<PackageIcon>icon.png</PackageIcon>
<AssemblyName>Ardalis.SmartEnum.GuardClauses</AssemblyName>
<RootNamespace>Ardalis.SmartEnum.GuardClauses</RootNamespace>
<PackageReadmeFile>README.md</PackageReadmeFile>
</PropertyGroup>

<PropertyGroup Condition=" '$(Configuration)' == 'Release' ">
<DocumentationFile>bin\$(Configuration)\$(TargetFramework)\$(AssemblyName).xml</DocumentationFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Ardalis.GuardClauses" />
</ItemGroup>
<ItemGroup>
<None Include="README.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
<None Include="$(SolutionDir)img\icon.png" Pack="true" Visible="false" PackagePath="" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\SmartEnum\SmartEnum.csproj" />
</ItemGroup>
</Project>
2 changes: 1 addition & 1 deletion src/SmartEnum/SmartFlagEnum.cs
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ public static IEnumerable<TEnum> FromValue(TValue value)
/// <returns></returns>
public static TEnum DeserializeValue(TValue value)
{
//todo we should not be calling get options for each deserialization. Perhaps move it to a lazy field _enumOptions.
// we should not be calling get options for each deserialization. Perhaps move it to a lazy field _enumOptions.
var enumList = GetAllOptions();

var returnValue = enumList.FirstOrDefault(x => x.Value.Equals(value));
Expand Down
Loading