From 674d2817c80b877f9226cfad07d99dea54a2df2d Mon Sep 17 00:00:00 2001 From: Tomasz Malinowski Date: Wed, 2 Feb 2022 21:17:43 +0100 Subject: [PATCH] Smart flag enum (#160) * Added SmartFlagEnum Functionality * Added AutoFixture Support for SmarFlagEnum Type * SmartFlagEnum 1.0. Serialization and AutoFixture Utilities and UnitTests Complete. * Return all flags for underlying type MaxValue When FromValue() is called on a SmartFlagEnum with the corresponding MaxValue of the base tyoe as the value agrument, a list of all defined enum values is returned. This is similar to passing negative one. * Pulled In Upstream Changes and Resolved Conflicts, Files Moved To New Folder Structure * Missing nuget packages for integration projects (#62) * current progress * Move test project to solution and create Directory.Build.props files to simplify the configuration process * Set up CI with Azure Pipelines [skip ci] * Remove redundant PackageId from Directory.Build.props * Fix LangVersion property * Fix unit test project missing source project references * Update test project path * Update azure-pipelines.yml for Azure Pipelines * Remove duplicate tasks * Update azure-pipelines.yml for Azure Pipelines * Update azure-pipelines.yml for Azure Pipelines * Update azure-pipelines.yml for Azure Pipelines * Remove Benchmarks from pack process by ading false * Make protram and main method public to avoid error CS5001 on azure pipelines * Update azure-pipelines.yml for Azure Pipelines * Update azure-pipelines.yml for Azure Pipelines * Update azure-pipelines.yml for Azure Pipelines * Update azure-pipelines.yml for Azure Pipelines * Update azure-pipelines.yml for Azure Pipelines * Update azure-pipelines.yml for Azure Pipelines * Update azure-pipelines.yml for Azure Pipelines * Reorder project structure -Bring solution to the root path -Create benchmarks folder and move project -Move test projects from src/test to test -Reorder projects inside solutions -Craete Directory.Build.props for tests projects and propagate code coverage and commons nuget packages. -Update azure .ylm pipeline to produce code coverage diagrams and result * Fix solution items Co-authored-by: Steve Smith Co-authored-by: Daniel Meza * Bump MessagePack from 1.7.3.4 to 1.9.11 in /src/SmartEnum.MessagePack (#68) Bumps [MessagePack](https://github.com/neuecc/MessagePack-CSharp) from 1.7.3.4 to 1.9.11. - [Release notes](https://github.com/neuecc/MessagePack-CSharp/releases) - [Commits](https://github.com/neuecc/MessagePack-CSharp/compare/v1.7.3.4...v1.9.11) Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> * Pulled In Upstream Changes and Resolved Conflicts, Files Moved To New Folder Structure * Merge branch 'master' into smart-flag-enum * adding System.Text.Json converters for SmartFlagEnum Co-authored-by: Steve Smith Co-authored-by: AFMHorizon <52022448+AFM-Horizon@users.noreply.github.com> Co-authored-by: Suhaib Co-authored-by: Daniel Meza Co-authored-by: Daniel Meza Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- README.md | 262 +++++++++++ SmartEnum.sln | 7 + .../SmartEnumNameConverter.cs | 2 + .../SmartFlagEnumNameConverter.cs | 56 +++ .../SmartFlagEnumValueConverter.cs | 63 +++ .../SmartFlagEnumNameFormatter.cs | 38 ++ .../SmartFlagEnumNameResolver.cs | 34 ++ .../SmartFlagEnumValueFormatter.cs | 115 +++++ .../SmartFlagEnumValueResolver.cs | 45 ++ .../SmartFlagEnumNameSurrogate.cs | 34 ++ .../SmartFlagEnumValueSurrogate.cs | 30 ++ .../SmartFlagEnumNameConverter.cs | 46 ++ .../SmartFlagEnumValueConverter.cs | 97 ++++ .../SmartFlagEnumNameFormatter.cs | 38 ++ .../SmartFlagEnumNameResolver.cs | 34 ++ .../SmartFlagEnumValueFormatter.cs | 76 ++++ .../SmartFlagEnumValueResolver.cs | 45 ++ .../AllowNegativeInputValuesAttribute.cs | 11 + .../AllowUnsafeFlagEnumValuesAttribute.cs | 11 + src/SmartEnum/Core/SmartEnumThen.cs | 6 +- src/SmartEnum/Core/SmartEnumWhen.cs | 12 +- .../InvalidFlagEnumValueParseException.cs | 46 ++ .../NegativeValueArgumentException.cs | 51 +++ ...tFlagEnumContainsNegativeValueException.cs | 46 ++ ...DoesNotContainPowerOfTwoValuesException.cs | 56 +++ src/SmartEnum/ISmartEnum.cs | 11 + src/SmartEnum/SmartEnum.cs | 38 +- src/SmartEnum/SmartEnumExtensions.cs | 5 +- src/SmartEnum/SmartFlagEngine.cs | 218 +++++++++ src/SmartEnum/SmartFlagEnum.cs | 429 ++++++++++++++++++ src/SmartEnum/SmartFlagEnumExtensions.cs | 75 +++ src/SmartEnum/ThrowHelper.cs | 29 +- .../SmartEnumSpecimenBuilderCreate.cs | 11 + .../SmartFlagTestEnum.cs | 18 + .../FlagTestEnums.cs | 34 ++ .../SmartFlagEnumNameConverterTests.cs | 97 ++++ .../SmartFlagEnumValueConverterTests.cs | 128 ++++++ .../FlagTestEnums.cs | 29 ++ .../SmartFlagEnumNameFormatterTests.cs | 73 +++ .../SmartFlagEnumValueFormatterTests.cs | 76 ++++ .../FlagTestEnums.cs | 30 ++ .../SmartFlagEnumNameSurrogateTests.cs | 80 ++++ .../SmartFlagEnumValueSurrogateTests.cs | 84 ++++ test/SmartEnum.ProtoBufNet.UnitTests/Utils.cs | 2 + .../FlagTestEnums.cs | 25 + .../SmartFlagEnumNameConverterTests.cs | 91 ++++ .../SmartFlagEnumValueConverterTests.cs | 128 ++++++ .../FlagTestEnums.cs | 26 ++ .../SmartFlagEnumNameFormatterTests.cs | 96 ++++ .../SmartFlagEnumValueFormatterTests.cs | 116 +++++ .../AllowNegativeInputAttributeTests.cs | 51 +++ .../AllowUnsafeFlagEnumAttributeTests.cs | 123 +++++ .../SmartFlagEnum.UnitTests.csproj | 20 + .../SmartFlagEnumCompareTo.cs | 77 ++++ .../SmartFlagEnumEquals.cs | 84 ++++ .../SmartFlagEnumExplicitConversion.cs | 52 +++ .../SmartFlagEnumExtensionTests.cs | 58 +++ .../SmartFlagEnumFromName.cs | 91 ++++ .../SmartFlagEnumFromValue.cs | 207 +++++++++ .../SmartFlagEnumImplicitValueConversion.cs | 22 + .../SmartFlagEnumList.cs | 27 ++ .../SmartFlagEnumStringFromValue.cs | 39 ++ .../SmartFlagEnumToString.cs | 29 ++ .../SmartFlagEnumUsageExample.cs | 67 +++ .../SmartFlagEnumWhenThen.cs | 141 ++++++ .../SmartFlagGetFlagEnumTests.cs | 133 ++++++ .../SmartFlagTestEnum.cs | 131 ++++++ 67 files changed, 4530 insertions(+), 32 deletions(-) create mode 100644 src/SmartEnum.JsonNet/SmartFlagEnumNameConverter.cs create mode 100644 src/SmartEnum.JsonNet/SmartFlagEnumValueConverter.cs create mode 100644 src/SmartEnum.MessagePack/SmartFlagEnumNameFormatter.cs create mode 100644 src/SmartEnum.MessagePack/SmartFlagEnumNameResolver.cs create mode 100644 src/SmartEnum.MessagePack/SmartFlagEnumValueFormatter.cs create mode 100644 src/SmartEnum.MessagePack/SmartFlagEnumValueResolver.cs create mode 100644 src/SmartEnum.ProtoBufNet/SmartFlagEnumNameSurrogate.cs create mode 100644 src/SmartEnum.ProtoBufNet/SmartFlagEnumValueSurrogate.cs create mode 100644 src/SmartEnum.SystemTextJson/SmartFlagEnumNameConverter.cs create mode 100644 src/SmartEnum.SystemTextJson/SmartFlagEnumValueConverter.cs create mode 100644 src/SmartEnum.Utf8Json/SmartFlagEnumNameFormatter.cs create mode 100644 src/SmartEnum.Utf8Json/SmartFlagEnumNameResolver.cs create mode 100644 src/SmartEnum.Utf8Json/SmartFlagEnumValueFormatter.cs create mode 100644 src/SmartEnum.Utf8Json/SmartFlagEnumValueResolver.cs create mode 100644 src/SmartEnum/AllowNegativeInputValuesAttribute.cs create mode 100644 src/SmartEnum/AllowUnsafeFlagEnumValuesAttribute.cs create mode 100644 src/SmartEnum/Exceptions/InvalidFlagEnumValueParseException.cs create mode 100644 src/SmartEnum/Exceptions/NegativeValueArgumentException.cs create mode 100644 src/SmartEnum/Exceptions/SmartFlagEnumContainsNegativeValueException.cs create mode 100644 src/SmartEnum/Exceptions/SmartFlagEnumDoesNotContainPowerOfTwoValuesException.cs create mode 100644 src/SmartEnum/ISmartEnum.cs create mode 100644 src/SmartEnum/SmartFlagEngine.cs create mode 100644 src/SmartEnum/SmartFlagEnum.cs create mode 100644 src/SmartEnum/SmartFlagEnumExtensions.cs create mode 100644 test/SmartEnum.AutoFixture.UnitTests/SmartFlagTestEnum.cs create mode 100644 test/SmartEnum.JsonNet.UnitTests/FlagTestEnums.cs create mode 100644 test/SmartEnum.JsonNet.UnitTests/SmartFlagEnumNameConverterTests.cs create mode 100644 test/SmartEnum.JsonNet.UnitTests/SmartFlagEnumValueConverterTests.cs create mode 100644 test/SmartEnum.MessagePack.UnitTests/FlagTestEnums.cs create mode 100644 test/SmartEnum.MessagePack.UnitTests/SmartFlagEnumNameFormatterTests.cs create mode 100644 test/SmartEnum.MessagePack.UnitTests/SmartFlagEnumValueFormatterTests.cs create mode 100644 test/SmartEnum.ProtoBufNet.UnitTests/FlagTestEnums.cs create mode 100644 test/SmartEnum.ProtoBufNet.UnitTests/SmartFlagEnumNameSurrogateTests.cs create mode 100644 test/SmartEnum.ProtoBufNet.UnitTests/SmartFlagEnumValueSurrogateTests.cs create mode 100644 test/SmartEnum.SystemTextJson.UnitTests/FlagTestEnums.cs create mode 100644 test/SmartEnum.SystemTextJson.UnitTests/SmartFlagEnumNameConverterTests.cs create mode 100644 test/SmartEnum.SystemTextJson.UnitTests/SmartFlagEnumValueConverterTests.cs create mode 100644 test/SmartEnum.Utf8Json.UnitTests/FlagTestEnums.cs create mode 100644 test/SmartEnum.Utf8Json.UnitTests/SmartFlagEnumNameFormatterTests.cs create mode 100644 test/SmartEnum.Utf8Json.UnitTests/SmartFlagEnumValueFormatterTests.cs create mode 100644 test/SmartFlagEnum.UnitTests/AllowNegativeInputAttributeTests.cs create mode 100644 test/SmartFlagEnum.UnitTests/AllowUnsafeFlagEnumAttributeTests.cs create mode 100644 test/SmartFlagEnum.UnitTests/SmartFlagEnum.UnitTests.csproj create mode 100644 test/SmartFlagEnum.UnitTests/SmartFlagEnumCompareTo.cs create mode 100644 test/SmartFlagEnum.UnitTests/SmartFlagEnumEquals.cs create mode 100644 test/SmartFlagEnum.UnitTests/SmartFlagEnumExplicitConversion.cs create mode 100644 test/SmartFlagEnum.UnitTests/SmartFlagEnumExtensionTests.cs create mode 100644 test/SmartFlagEnum.UnitTests/SmartFlagEnumFromName.cs create mode 100644 test/SmartFlagEnum.UnitTests/SmartFlagEnumFromValue.cs create mode 100644 test/SmartFlagEnum.UnitTests/SmartFlagEnumImplicitValueConversion.cs create mode 100644 test/SmartFlagEnum.UnitTests/SmartFlagEnumList.cs create mode 100644 test/SmartFlagEnum.UnitTests/SmartFlagEnumStringFromValue.cs create mode 100644 test/SmartFlagEnum.UnitTests/SmartFlagEnumToString.cs create mode 100644 test/SmartFlagEnum.UnitTests/SmartFlagEnumUsageExample.cs create mode 100644 test/SmartFlagEnum.UnitTests/SmartFlagEnumWhenThen.cs create mode 100644 test/SmartFlagEnum.UnitTests/SmartFlagGetFlagEnumTests.cs create mode 100644 test/SmartFlagEnum.UnitTests/SmartFlagTestEnum.cs diff --git a/README.md b/README.md index 3988757e..877757bf 100644 --- a/README.md +++ b/README.md @@ -350,6 +350,267 @@ testEnumVar N.B. For performance critical code the fluent interface carries some overhead that you may wish to avoid. See the available [benchmarks](src/SmartEnum.Benchmarks) code for your use case. +### SmartFlagEnum + +Support has been added for a `Flag` functionality. +This feature is similar to the behaviour seen when applying the `[Flag]` attribute to Enums in the .NET Framework +All methods available on the `SmartFlagEnum` class return an `IEnumerable` with one or more values depending on the value provided/method called. +Some Functionality is shared with the original SmartEnum class, listed below are the variations. + +### Setting SmartFlagEnum Values + +When setting the values for a `SmartFlagEnum` It is imperative to provide values as powers of two. If at least one value is not set as power of two or two or more power of two values are provided inconsecutively (eg: 1, 2, no four!, 8) a `SmartFlagEnumDoesNotContainPowerOfTwoValuesException` will be thrown. + +```csharp +public class SmartFlagTestEnum : SmartFlagEnum + { + public static readonly SmartFlagTestEnum None = new SmartFlagTestEnum(nameof(None), 0); + public static readonly SmartFlagTestEnum Card = new SmartFlagTestEnum(nameof(Card), 1); + public static readonly SmartFlagTestEnum Cash = new SmartFlagTestEnum(nameof(Cash), 2); + public static readonly SmartFlagTestEnum Bpay = new SmartFlagTestEnum(nameof(Bpay), 4); + public static readonly SmartFlagTestEnum Paypal = new SmartFlagTestEnum(nameof(Paypal), 8); + public static readonly SmartFlagTestEnum BankTransfer = new SmartFlagTestEnum(nameof(BankTransfer), 16); + + public SmartFlagTestEnum(string name, int value) : base(name, value) + { + } + } +``` + +This behaviour can be disabled by applying the `AllowUnsafeFlagEnumValuesAttribute` to the smart enum class. Note: If power of two values are not provided the SmarFlagEnum will not behave as expected! + +```csharp +[AllowUnsafeFlagEnumValues] +public class SmartFlagTestEnum : SmartFlagEnum + { + public static readonly SmartFlagTestEnum None = new SmartFlagTestEnum(nameof(None), 0); + public static readonly SmartFlagTestEnum Card = new SmartFlagTestEnum(nameof(Card), 1); + public static readonly SmartFlagTestEnum Cash = new SmartFlagTestEnum(nameof(Cash), 2); + public static readonly SmartFlagTestEnum Bpay = new SmartFlagTestEnum(nameof(Bpay), 4); + public static readonly SmartFlagTestEnum Paypal = new SmartFlagTestEnum(nameof(Paypal), 8); + public static readonly SmartFlagTestEnum BankTransfer = new SmartFlagTestEnum(nameof(BankTransfer), 16); + + public SmartFlagTestEnum(string name, int value) : base(name, value) + { + } + } +``` + +`Combination` values can be provided explicitly and will be returned in place of the multiple flag values that would have been returned from the `FromValue()` method. + +```csharp +public class SmartFlagTestEnum : SmartFlagEnum + { + public static readonly SmartFlagTestEnum None = new SmartFlagTestEnum(nameof(None), 0); + public static readonly SmartFlagTestEnum Card = new SmartFlagTestEnum(nameof(Card), 1); + public static readonly SmartFlagTestEnum Cash = new SmartFlagTestEnum(nameof(Cash), 2); + public static readonly SmartFlagTestEnum CardAndCash = new SmartFlagTestEnum(nameof(CardAndCash), 3); -- Explicit `Combination` value + public static readonly SmartFlagTestEnum Bpay = new SmartFlagTestEnum(nameof(Bpay), 4); + public static readonly SmartFlagTestEnum Paypal = new SmartFlagTestEnum(nameof(Paypal), 8); + public static readonly SmartFlagTestEnum BankTransfer = new SmartFlagTestEnum(nameof(BankTransfer), 16); + + public SmartFlagTestEnum(string name, int value) : base(name, value) + { + } + } +``` + +These explicit values can be provided above the highest allowable flag value without consequence, however attempting to access a value that is higher than the maximum flag value that has not explicitly been provided (for example 4) will cause a `SmartEnumNotFoundException` to be thrown. + +```csharp +public class SmartFlagTestEnum : SmartFlagEnum + { + public static readonly SmartFlagTestEnum None = new SmartFlagTestEnum(nameof(None), 0); + public static readonly SmartFlagTestEnum Card = new SmartFlagTestEnum(nameof(Card), 1); + public static readonly SmartFlagTestEnum Cash = new SmartFlagTestEnum(nameof(Cash), 2); + public static readonly SmartFlagTestEnum AfterPay = new SmartFlagTestEnum(nameof(AfterPay), 5); + + public SmartFlagTestEnum(string name, int value) : base(name, value) + { + } + } + + var myFlagEnums = FromValue(3) -- Works! + -and- + var myFlagEnums = FromValue(5) -- Works! + -but- + Var myFlagEnums = FromValue(4) -- will throw an exception :( +``` + +A Negative One (-1) value may be provided as an `All` value. When a value of -1 is passed into any of the `FromValue()` methods an IEnumerable containing all values (excluding 0) will be returned. +If an explicit `Combination` value exists with a value of -1 this will be returned instead. + +```csharp +public class SmartFlagTestEnum : SmartFlagEnum + { + public static readonly SmartFlagTestEnum All = new SmartFlagTestEnum(nameof(All), -1); + public static readonly SmartFlagTestEnum None = new SmartFlagTestEnum(nameof(None), 0); + public static readonly SmartFlagTestEnum Card = new SmartFlagTestEnum(nameof(Card), 1); + public static readonly SmartFlagTestEnum Cash = new SmartFlagTestEnum(nameof(Cash), 2); + public static readonly SmartFlagTestEnum Bpay = new SmartFlagTestEnum(nameof(Bpay), 4); + public static readonly SmartFlagTestEnum Paypal = new SmartFlagTestEnum(nameof(Paypal), 8); + public static readonly SmartFlagTestEnum BankTransfer = new SmartFlagTestEnum(nameof(BankTransfer), 16); + + public SmartFlagTestEnum(string name, int value) : base(name, value) + { + } + } +``` + +### Usage - (SmartFlagEnum) + +```csharp +public abstract class EmployeeType : SmartFlagEnum + { + public static readonly EmployeeType Director = new DirectorType(); + public static readonly EmployeeType Manager = new ManagerType(); + public static readonly EmployeeType Assistant = new AssistantType(); + + private EmployeeType(string name, int value) : base(name, value) + { + } + + public abstract decimal BonusSize { get; } + + private sealed class DirectorType : EmployeeType + { + public DirectorType() : base("Director", 1) { } + + public override decimal BonusSize => 100_000m; + } + + private sealed class ManagerType : EmployeeType + { + public ManagerType() : base("Manager", 2) { } + + public override decimal BonusSize => 10_000m; + } + + private sealed class AssistantType : EmployeeType + { + public AssistantType() : base("Assistant", 4) { } + + public override decimal BonusSize => 1_000m; + } + } + + public class SmartFlagEnumUsageExample + { + public void UseSmartFlagEnumOne() + { + var result = EmployeeType.FromValue(3).ToList(); + + var outputString = ""; + foreach (var employeeType in result) + { + outputString += $"{employeeType.Name} earns ${employeeType.BonusSize} bonus this year.\n"; + } + + => "Director earns $100000 bonus this year.\n" + "Manager earns $10000 bonus this year.\n" + } + + public void UseSmartFlagEnumTwo() + { + EmployeeType.FromValueToString(-1) + => "Director, Manager, Assistant" + } + + public void UseSmartFlagEnumTwo() + { + EmployeeType.FromValueToString(EmployeeType.Assistant | EmployeeType.Director) + => "Director, Assistant" + } + } + +``` + +### FromName() + +Access an `IEnumerable` of enum instances by matching a string containing one or more enum names seperated by commas to its `Names` property: + +```csharp +var myFlagEnums = TestFlagEnum.FromName("One, Two"); +``` + +Exception `SmartEnumNotFoundException` is thrown when no names are found. Alternatively, you can use `TryFromName` that returns `false` when no names are found: + +```csharp +if (TestFlagEnum.TryFromName("One, Two", out var myFlagEnums)) +{ + // use myFlagEnums here +} +``` + +Both methods have a `ignoreCase` parameter (the default is case sensitive). + +### FromValue() + +Access an `IEnumerable` of enum instances by matching a value: + +```csharp +var myFlagEnums = TestFlagEnum.FromValue(3); +``` + +Exception `SmartEnumNotFoundException` is thrown when no values are found. Alternatively, you can use `TryFromValue` that returns `false` when values are not found: + +```csharp +if (TestFlagEnum.TryFromValue(3, out var myFlagEnums)) +{ + // use myFlagEnums here +} +``` + +Note: Negative values other than (-1) passed into this method will cause a `NegativeValueArgumentException` to be thrown, this behaviour can be disabled by applying the `AllowNegativeInput` attribute to the desired `SmartFlagEnum` class. + +```csharp +[AllowNegativeInput] +public class SmartFlagTestEnum : SmartFlagEnum + { + public static readonly SmartFlagTestEnum None = new SmartFlagTestEnum(nameof(None), 0); + public static readonly SmartFlagTestEnum Card = new SmartFlagTestEnum(nameof(Card), 1); + + public SmartFlagTestEnum(string name, int value) : base(name, value) + { + } + } +``` + +Note: `FromValue()` will accept any input that can be succesfully parsed as an integer. If an invalid value is supplied it will throw an `InvalidFlagEnumValueParseException`. + +### FromValueToString() + +Return a string representation of a series of enum instances name's: + +```csharp +var myFlagEnumString = TestFlagEnum.FromValueToString(3); +``` + +Exception `SmartEnumNotFoundException` is thrown when no values are found. Alternatively, you can use `TryFromValueToString` that returns `false` when values are not found: + +```csharp +if (TestFlagEnum.TryFromValueToString(3, out var myFlagEnumsAsString)) +{ + // use myFlagEnumsAsString here +} +``` + +Note: Negative values other than (-1) passed into this method will cause a `NegativeValueArgumentException` to be thrown, this behaviour can be disabled by applying the `AllowNegativeInput` attribute to the desired `SmartFlagEnum` class. + +### BitWiseOrOperator + +The `FromValue()` methods allow the Or ( | ) operator to be used to `add` enum values together and provide multiple values at once. + +```csharp +var myFlagEnums = TestFlagEnum.FromValue(TestFlagEnum.One | TestFlagEnum.Two); +``` + +This will only work where the type of the `SmartFlagEnum` has been specified as `Int32` or else can be explicitly cast as an `Int32`. + +```csharp +var myFlagEnums = TestFlagEnumDecimal.FromValue((int)TestFlagEnum.One | (int)TestFlagEnum.Two); +``` + ### Persisting with EF Core 2.1 or higher EF Core 2.1 introduced [value conversions](https://docs.microsoft.com/en-us/ef/core/modeling/value-conversions) which can be used to map SmartEnum types to simple database types. For example, given an entity named `Policy` with a property `PolicyStatus` that is a SmartEnum, you could use the following code to persist just the value to the database: @@ -431,6 +692,7 @@ uses the `Value`: } ``` +Note: The SmartFlagEnum works identically to the SmartEnum when being Serialized and Deserialized. ## References - [Listing Strongly Typed Enums...)](https://ardalis.com/listing-strongly-typed-enum-options-in-c) diff --git a/SmartEnum.sln b/SmartEnum.sln index 7268b962..e79d1c0f 100644 --- a/SmartEnum.sln +++ b/SmartEnum.sln @@ -50,6 +50,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{FA199ECB-5F2 EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "benchmarks", "benchmarks", "{5952C160-ABAA-4302-9150-0928E82545B1}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SmartFlagEnum.UnitTests", "test\SmartFlagEnum.UnitTests\SmartFlagEnum.UnitTests.csproj", "{A927CEF1-A29C-4A8B-A590-7907ED93231F}" +EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SmartEnum.SystemTextJson", "src\SmartEnum.SystemTextJson\SmartEnum.SystemTextJson.csproj", "{A7F1C0E7-F641-4800-98DA-FA7A7B7B76E9}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SmartEnum.SystemTextJson.UnitTests", "test\SmartEnum.SystemTextJson.UnitTests\SmartEnum.SystemTextJson.UnitTests.csproj", "{3EBD6CA5-CF2C-4350-922E-CEE2AE01445A}" @@ -120,6 +122,10 @@ Global {66EF3D1A-32AE-4B28-BC3E-7441615722A9}.Debug|Any CPU.Build.0 = Debug|Any CPU {66EF3D1A-32AE-4B28-BC3E-7441615722A9}.Release|Any CPU.ActiveCfg = Release|Any CPU {66EF3D1A-32AE-4B28-BC3E-7441615722A9}.Release|Any CPU.Build.0 = Release|Any CPU + {A927CEF1-A29C-4A8B-A590-7907ED93231F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A927CEF1-A29C-4A8B-A590-7907ED93231F}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A927CEF1-A29C-4A8B-A590-7907ED93231F}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A927CEF1-A29C-4A8B-A590-7907ED93231F}.Release|Any CPU.Build.0 = Release|Any CPU {A7F1C0E7-F641-4800-98DA-FA7A7B7B76E9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {A7F1C0E7-F641-4800-98DA-FA7A7B7B76E9}.Debug|Any CPU.Build.0 = Debug|Any CPU {A7F1C0E7-F641-4800-98DA-FA7A7B7B76E9}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -158,6 +164,7 @@ Global {C65837A3-1AB1-4AD4-9D9E-C3FF437A5714} = {79268877-BBEF-4DE2-B8D9-697F21933159} {8FC7B9E5-B651-42BC-B21B-B45F0C8A239B} = {FA199ECB-5F29-442A-AAC6-91DBCB7A5A04} {66EF3D1A-32AE-4B28-BC3E-7441615722A9} = {79268877-BBEF-4DE2-B8D9-697F21933159} + {A927CEF1-A29C-4A8B-A590-7907ED93231F} = {79268877-BBEF-4DE2-B8D9-697F21933159} {A7F1C0E7-F641-4800-98DA-FA7A7B7B76E9} = {FA199ECB-5F29-442A-AAC6-91DBCB7A5A04} {3EBD6CA5-CF2C-4350-922E-CEE2AE01445A} = {79268877-BBEF-4DE2-B8D9-697F21933159} {CDA8EF6E-F678-4FD3-80B0-5C61FC76EE51} = {FA199ECB-5F29-442A-AAC6-91DBCB7A5A04} diff --git a/src/SmartEnum.JsonNet/SmartEnumNameConverter.cs b/src/SmartEnum.JsonNet/SmartEnumNameConverter.cs index 96b3bfbe..24cb4060 100644 --- a/src/SmartEnum.JsonNet/SmartEnumNameConverter.cs +++ b/src/SmartEnum.JsonNet/SmartEnumNameConverter.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; + namespace Ardalis.SmartEnum.JsonNet { using Newtonsoft.Json; diff --git a/src/SmartEnum.JsonNet/SmartFlagEnumNameConverter.cs b/src/SmartEnum.JsonNet/SmartFlagEnumNameConverter.cs new file mode 100644 index 00000000..05b31ad3 --- /dev/null +++ b/src/SmartEnum.JsonNet/SmartFlagEnumNameConverter.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Data.SqlTypes; +using System.Linq; +using System.Text; +using Newtonsoft.Json; + +namespace Ardalis.SmartEnum.JsonNet +{ + public class SmartFlagEnumNameConverter : JsonConverter + where TEnum : SmartFlagEnum + where TValue : struct, IComparable, IEquatable + { + public override bool CanRead => true; + + public override bool CanWrite => true; + + public override TEnum ReadJson(JsonReader reader, Type objectType, TEnum existingValue, bool hasExistingValue, JsonSerializer serializer) + { + switch (reader.TokenType) + { + case JsonToken.String: + return GetFromName((string)reader.Value); + + default: + throw new JsonSerializationException($"Unexpected token {reader.TokenType} when parsing a smart flag enum."); + } + + TEnum GetFromName(string name) + { + try + { + return SmartFlagEnum.FromName(name, false).FirstOrDefault(); + } + catch (Exception ex) + { + throw new JsonSerializationException($"Error converting value '{name}' to a smart flag enum.", ex); + } + } + } + + /// + /// + /// + /// + /// + /// + public override void WriteJson(JsonWriter writer, TEnum value, JsonSerializer serializer) + { + if (value is null) + writer.WriteNull(); + else + writer.WriteValue(value.Name); + } + } +} diff --git a/src/SmartEnum.JsonNet/SmartFlagEnumValueConverter.cs b/src/SmartEnum.JsonNet/SmartFlagEnumValueConverter.cs new file mode 100644 index 00000000..ca0eb3af --- /dev/null +++ b/src/SmartEnum.JsonNet/SmartFlagEnumValueConverter.cs @@ -0,0 +1,63 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Newtonsoft.Json; + +namespace Ardalis.SmartEnum.JsonNet +{ + public class SmartFlagEnumValueConverter : JsonConverter + where TEnum : SmartFlagEnum + where TValue: struct, IEquatable, IComparable + { + public override bool CanRead => true; + + public override bool CanWrite => true; + + /// + /// + /// + /// + /// + /// + /// + /// + /// + public override TEnum ReadJson(JsonReader reader, Type objectType, TEnum existingValue, bool hasExistingValue, JsonSerializer serializer) + { + try + { + TValue value; + if (reader.TokenType == JsonToken.Integer && typeof(TValue) != typeof(long) && typeof(TValue) != typeof(bool)) + { + value = (TValue)Convert.ChangeType(reader.Value, typeof(TValue)); + } + else + { + value = (TValue)reader.Value; + } + + return SmartFlagEnum.DeserializeValue(value); + } + catch (Exception ex) + { + throw new JsonSerializationException($"Error converting {reader.Value ?? "Null"} to {objectType.Name}.", ex); + } + } + + /// + /// + /// + /// + /// + /// + public override void WriteJson(JsonWriter writer, TEnum value, JsonSerializer serializer) + { + if (value is null) + writer.WriteNull(); + else + writer.WriteValue(value.Value); + } + + } +} diff --git a/src/SmartEnum.MessagePack/SmartFlagEnumNameFormatter.cs b/src/SmartEnum.MessagePack/SmartFlagEnumNameFormatter.cs new file mode 100644 index 00000000..5d291494 --- /dev/null +++ b/src/SmartEnum.MessagePack/SmartFlagEnumNameFormatter.cs @@ -0,0 +1,38 @@ +using System.Linq; + +namespace Ardalis.SmartEnum.MessagePack +{ + using System; + using global::MessagePack; + using global::MessagePack.Formatters; + + + public sealed class SmartFlagEnumNameFormatter : IMessagePackFormatter + where TEnum : SmartFlagEnum + where TValue : struct, IEquatable, IComparable + { + public int Serialize(ref byte[] bytes, int offset, TEnum value, IFormatterResolver formatterResolver) + { + if (value is null) + { + return MessagePackBinary.WriteNil(ref bytes, offset); + } + + return MessagePackBinary.WriteString(ref bytes, offset, value.Name); + } + + public TEnum Deserialize(byte[] bytes, int offset, IFormatterResolver formatterResolver, out int readSize) + { + if (MessagePackBinary.IsNil(bytes, offset)) + { + readSize = 1; + return null; + } + + var name = MessagePackBinary.ReadString(bytes, offset, out readSize); + return SmartFlagEnum.FromName(name).FirstOrDefault(); + } + } +} + + diff --git a/src/SmartEnum.MessagePack/SmartFlagEnumNameResolver.cs b/src/SmartEnum.MessagePack/SmartFlagEnumNameResolver.cs new file mode 100644 index 00000000..32ac16b5 --- /dev/null +++ b/src/SmartEnum.MessagePack/SmartFlagEnumNameResolver.cs @@ -0,0 +1,34 @@ +using global::MessagePack; +using global::MessagePack.Formatters; +using System; + +namespace Ardalis.SmartEnum.MessagePack +{ + public class SmartFlagEnumNameResolver : IFormatterResolver + { + public static readonly SmartFlagEnumNameResolver Instance = new SmartFlagEnumNameResolver(); + + private SmartFlagEnumNameResolver() + { + } + + public IMessagePackFormatter GetFormatter() => + FormatterCache.Formatter; + + private static class FormatterCache + { + public static readonly IMessagePackFormatter Formatter; + +#pragma warning disable S3963 // "static" fields should be initialized inline + static FormatterCache() + { + if (typeof(T).IsSmartFlagEnum(out var genericArguments)) + { + var formatterType = typeof(SmartFlagEnumNameFormatter<,>).MakeGenericType(genericArguments); + Formatter = (IMessagePackFormatter)Activator.CreateInstance(formatterType); + } + } +#pragma warning restore S3963 // "static" fields should be initialized inline + } + } +} diff --git a/src/SmartEnum.MessagePack/SmartFlagEnumValueFormatter.cs b/src/SmartEnum.MessagePack/SmartFlagEnumValueFormatter.cs new file mode 100644 index 00000000..9c0c4f38 --- /dev/null +++ b/src/SmartEnum.MessagePack/SmartFlagEnumValueFormatter.cs @@ -0,0 +1,115 @@ +namespace Ardalis.SmartEnum.MessagePack +{ + using System; + using global::MessagePack; + using global::MessagePack.Formatters; + + /// + /// + /// + /// + /// + public class SmartFlagEnumValueFormatter : IMessagePackFormatter + where TEnum : SmartFlagEnum + where TValue : struct, IEquatable, IComparable + { + /// + /// + /// + /// + /// + /// + /// + /// + public int Serialize(ref byte[] bytes, int offset, TEnum value, IFormatterResolver formatterResolver) + { + if (value is null) + return MessagePackBinary.WriteNil(ref bytes, offset); + + return Write(ref bytes, offset, value.Value); + } + + /// + /// + /// + /// + /// + /// + /// + /// + public TEnum Deserialize(byte[] bytes, int offset, IFormatterResolver formatterResolver, out int readSize) + { + if (MessagePackBinary.IsNil(bytes, offset)) + { + readSize = 1; + return default; + } + + return SmartFlagEnum.DeserializeValue(Read(ref bytes, offset, out readSize)); + } + + /// + /// + /// + /// + /// + /// + /// + public int Write(ref byte[] bytes, int offset, TValue value) + { + if (typeof(TValue) == typeof(byte)) + return MessagePackBinary.WriteByte(ref bytes, offset, (byte)(object)value); + if (typeof(TValue) == typeof(sbyte)) + return MessagePackBinary.WriteSByte(ref bytes, offset, (sbyte)(object)value); + if (typeof(TValue) == typeof(short)) + return MessagePackBinary.WriteInt16(ref bytes, offset, (short)(object)value); + if (typeof(TValue) == typeof(ushort)) + return MessagePackBinary.WriteUInt16(ref bytes, offset, (ushort)(object)value); + if (typeof(TValue) == typeof(int)) + return MessagePackBinary.WriteInt32(ref bytes, offset, (int)(object)value); + if (typeof(TValue) == typeof(uint)) + return MessagePackBinary.WriteUInt32(ref bytes, offset, (uint)(object)value); + if (typeof(TValue) == typeof(long)) + return MessagePackBinary.WriteInt64(ref bytes, offset, (long)(object)value); + if (typeof(TValue) == typeof(ulong)) + return MessagePackBinary.WriteUInt64(ref bytes, offset, (ulong)(object)value); + if (typeof(TValue) == typeof(float)) + return MessagePackBinary.WriteSingle(ref bytes, offset, (float)(object)value); + if (typeof(TValue) == typeof(double)) + return MessagePackBinary.WriteDouble(ref bytes, offset, (double)(object)value); + throw new ArgumentOutOfRangeException(nameof(value), $"{typeof(TValue)} is not supported."); // should not get to here + } + + /// + /// + /// + /// + /// + /// + /// + public TValue Read(ref byte[] bytes, int offset, out int readSize) + { + if (typeof(TValue) == typeof(byte)) + return (TValue)(object)MessagePackBinary.ReadByte(bytes, offset, out readSize); + if (typeof(TValue) == typeof(sbyte)) + return (TValue)(object)MessagePackBinary.ReadSByte(bytes, offset, out readSize); + if (typeof(TValue) == typeof(short)) + return (TValue)(object)MessagePackBinary.ReadInt16(bytes, offset, out readSize); + if (typeof(TValue) == typeof(ushort)) + return (TValue)(object)MessagePackBinary.ReadUInt16(bytes, offset, out readSize); + if (typeof(TValue) == typeof(int)) + return (TValue)(object)MessagePackBinary.ReadInt32(bytes, offset, out readSize); + if (typeof(TValue) == typeof(uint)) + return (TValue)(object)MessagePackBinary.ReadUInt32(bytes, offset, out readSize); + if (typeof(TValue) == typeof(long)) + return (TValue)(object)MessagePackBinary.ReadInt64(bytes, offset, out readSize); + if (typeof(TValue) == typeof(ulong)) + return (TValue)(object)MessagePackBinary.ReadUInt64(bytes, offset, out readSize); + if (typeof(TValue) == typeof(float)) + return (TValue)(object)MessagePackBinary.ReadSingle(bytes, offset, out readSize); + if (typeof(TValue) == typeof(double)) + return (TValue)(object)MessagePackBinary.ReadDouble(bytes, offset, out readSize); + throw new ArgumentOutOfRangeException(typeof(TValue).ToString(), $"{typeof(TValue)} is not supported."); // should not get to here + } + } +} \ No newline at end of file diff --git a/src/SmartEnum.MessagePack/SmartFlagEnumValueResolver.cs b/src/SmartEnum.MessagePack/SmartFlagEnumValueResolver.cs new file mode 100644 index 00000000..8b7fb3b9 --- /dev/null +++ b/src/SmartEnum.MessagePack/SmartFlagEnumValueResolver.cs @@ -0,0 +1,45 @@ +namespace Ardalis.SmartEnum.MessagePack +{ + using System; + using global::MessagePack; + using global::MessagePack.Formatters; + + /// + /// + /// + public class SmartFlagEnumValueResolver : IFormatterResolver + { + /// + /// Return the instance. + /// + public static readonly SmartFlagEnumValueResolver Instance = new SmartFlagEnumValueResolver(); + + private SmartFlagEnumValueResolver() + { + } + + /// + /// + /// + /// + /// + public IMessagePackFormatter GetFormatter() => + FormatterCache.Formatter; + + private static class FormatterCache + { + public static readonly IMessagePackFormatter Formatter; + +#pragma warning disable S3963 // "static" fields should be initialized inline + static FormatterCache() + { + if (typeof(T).IsSmartFlagEnum(out var genericArguments)) + { + var formatterType = typeof(SmartFlagEnumValueFormatter<,>).MakeGenericType(genericArguments); + Formatter = (IMessagePackFormatter)Activator.CreateInstance(formatterType); + } + } +#pragma warning restore S3963 // "static" fields should be initialized inline + } + } +} \ No newline at end of file diff --git a/src/SmartEnum.ProtoBufNet/SmartFlagEnumNameSurrogate.cs b/src/SmartEnum.ProtoBufNet/SmartFlagEnumNameSurrogate.cs new file mode 100644 index 00000000..94eed195 --- /dev/null +++ b/src/SmartEnum.ProtoBufNet/SmartFlagEnumNameSurrogate.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using ProtoBuf; + +namespace Ardalis.SmartEnum.ProtoBufNet +{ + [ProtoContract] + public class SmartFlagEnumNameSurrogate + where TEnum : SmartFlagEnum + where TValue : struct, IEquatable, IComparable + { + [ProtoMember(1, IsRequired = true)] + public string Name { get; set; } + + public static implicit operator SmartFlagEnumNameSurrogate(TEnum smartFlagEnum) + { + if (smartFlagEnum is null) + return null; + + return new SmartFlagEnumNameSurrogate { Name = smartFlagEnum.Name }; + } + + public static implicit operator TEnum(SmartFlagEnumNameSurrogate surrogate) + { + if (surrogate is null) + return null; + + return SmartFlagEnum.FromName(surrogate.Name).FirstOrDefault(); + } + } +} diff --git a/src/SmartEnum.ProtoBufNet/SmartFlagEnumValueSurrogate.cs b/src/SmartEnum.ProtoBufNet/SmartFlagEnumValueSurrogate.cs new file mode 100644 index 00000000..2fd4b28f --- /dev/null +++ b/src/SmartEnum.ProtoBufNet/SmartFlagEnumValueSurrogate.cs @@ -0,0 +1,30 @@ +namespace Ardalis.SmartEnum.ProtoBufNet +{ + using System; + using ProtoBuf; + + [ProtoContract] + public class SmartFlagEnumValueSurrogate + where TEnum : SmartFlagEnum + where TValue : struct, IEquatable, IComparable + { + [ProtoMember(1, IsRequired = true)] + TValue Value { get; set; } + + public static implicit operator SmartFlagEnumValueSurrogate(TEnum smartEnum) + { + if (smartEnum is null) + return null; + + return new SmartFlagEnumValueSurrogate { Value = smartEnum.Value }; + } + + public static implicit operator TEnum(SmartFlagEnumValueSurrogate surrogate) + { + if (surrogate is null) + return null; + + return SmartFlagEnum.DeserializeValue(surrogate.Value); + } + } +} diff --git a/src/SmartEnum.SystemTextJson/SmartFlagEnumNameConverter.cs b/src/SmartEnum.SystemTextJson/SmartFlagEnumNameConverter.cs new file mode 100644 index 00000000..c1da3e4d --- /dev/null +++ b/src/SmartEnum.SystemTextJson/SmartFlagEnumNameConverter.cs @@ -0,0 +1,46 @@ +namespace Ardalis.SmartEnum.SystemTextJson +{ + using System; + using System.Linq; + using System.Text.Json; + using System.Text.Json.Serialization; + + public class SmartFlagEnumNameConverter : JsonConverter + where TEnum : SmartFlagEnum + where TValue : struct, IComparable, IEquatable + { + public override bool HandleNull => true; + + public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + switch (reader.TokenType) + { + case JsonTokenType.String: + return GetFromName(reader.GetString()); + + default: + throw new JsonException($"Unexpected token {reader.TokenType} when parsing a smart flag enum."); + } + + TEnum GetFromName(string name) + { + try + { + return SmartFlagEnum.FromName(name, false).FirstOrDefault(); + } + catch (Exception ex) + { + throw new JsonException($"Error converting value '{name}' to a smart flag enum.", ex); + } + } + } + + public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options) + { + if (value == null) + writer.WriteNullValue(); + else + writer.WriteStringValue(value.Name.ToString()); + } + } +} diff --git a/src/SmartEnum.SystemTextJson/SmartFlagEnumValueConverter.cs b/src/SmartEnum.SystemTextJson/SmartFlagEnumValueConverter.cs new file mode 100644 index 00000000..4bed0ad2 --- /dev/null +++ b/src/SmartEnum.SystemTextJson/SmartFlagEnumValueConverter.cs @@ -0,0 +1,97 @@ +namespace Ardalis.SmartEnum.SystemTextJson +{ + using System; + using System.Linq; + using System.Text.Json; + using System.Text.Json.Serialization; + + public class SmartFlagEnumValueConverter : JsonConverter + where TEnum : SmartFlagEnum + where TValue: struct, IEquatable, IComparable + { + public override bool HandleNull => true; + + public override TEnum Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if (reader.TokenType == JsonTokenType.Null) + throw new JsonException($"Error converting value 'Null' to a smart flag enum."); + + return GetFromValue(ReadValue(ref reader)); + } + + private TEnum GetFromValue(TValue value) + { + try + { + return SmartFlagEnum.DeserializeValue(value); + } + catch (Exception ex) + { + throw new JsonException($"Error converting value '{value}' to a smart flag enum.", ex); + } + } + + private TValue ReadValue(ref Utf8JsonReader reader) + { + try + { + if (typeof(TValue) == typeof(bool)) + return (TValue)(object)reader.GetBoolean(); + if (typeof(TValue) == typeof(byte)) + return (TValue)(object)reader.GetByte(); + if (typeof(TValue) == typeof(sbyte)) + return (TValue)(object)reader.GetSByte(); + if (typeof(TValue) == typeof(short)) + return (TValue)(object)reader.GetInt16(); + if (typeof(TValue) == typeof(ushort)) + return (TValue)(object)reader.GetUInt16(); + if (typeof(TValue) == typeof(int)) + return (TValue)(object)reader.GetInt32(); + if (typeof(TValue) == typeof(uint)) + return (TValue)(object)reader.GetUInt32(); + if (typeof(TValue) == typeof(long)) + return (TValue)(object)reader.GetInt64(); + if (typeof(TValue) == typeof(ulong)) + return (TValue)(object)reader.GetUInt64(); + if (typeof(TValue) == typeof(float)) + return (TValue)(object)reader.GetSingle(); + if (typeof(TValue) == typeof(double)) + return (TValue)(object)reader.GetDouble(); + if (typeof(TValue) == typeof(string)) + return (TValue)(object)reader.GetString(); + } + catch (InvalidOperationException ex) + { + throw new JsonException($"Error converting token '{reader.TokenType}' to a smart flag enum.", ex); + } + + throw new ArgumentOutOfRangeException(typeof(TValue).ToString(), $"{typeof(TValue).Name} is not supported."); + } + + public override void Write(Utf8JsonWriter writer, TEnum value, JsonSerializerOptions options) + { + if (value == null) + writer.WriteNullValue(); + else if (typeof(TValue) == typeof(bool)) + writer.WriteBooleanValue((bool)(object)value.Value); + else if (typeof(TValue) == typeof(short)) + writer.WriteNumberValue((int)(short)(object)value.Value); + else if (typeof(TValue) == typeof(int)) + writer.WriteNumberValue((int)(object)value.Value); + else if (typeof(TValue) == typeof(double)) + writer.WriteNumberValue((double)(object)value.Value); + else if (typeof(TValue) == typeof(decimal)) + writer.WriteNumberValue((decimal)(object)value.Value); + else if (typeof(TValue) == typeof(ulong)) + writer.WriteNumberValue((ulong)(object)value.Value); + else if (typeof(TValue) == typeof(uint)) + writer.WriteNumberValue((uint)(object)value.Value); + else if (typeof(TValue) == typeof(float)) + writer.WriteNumberValue((float)(object)value.Value); + else if (typeof(TValue) == typeof(long)) + writer.WriteNumberValue((long)(object)value.Value); + else + writer.WriteStringValue(value.Value.ToString()); + } + } +} diff --git a/src/SmartEnum.Utf8Json/SmartFlagEnumNameFormatter.cs b/src/SmartEnum.Utf8Json/SmartFlagEnumNameFormatter.cs new file mode 100644 index 00000000..a0cffe23 --- /dev/null +++ b/src/SmartEnum.Utf8Json/SmartFlagEnumNameFormatter.cs @@ -0,0 +1,38 @@ +using System.Linq; + +namespace Ardalis.SmartEnum.Utf8Json +{ + using global::Utf8Json; + using global::Utf8Json.Internal; + using System; + + /// + /// + /// + /// + /// + public class SmartFlagEnumNameFormatter : IJsonFormatter + where TEnum : SmartFlagEnum + where TValue : struct, IEquatable, IComparable + { + public void Serialize(ref JsonWriter writer, TEnum value, IJsonFormatterResolver formatterResolver) + { + if (value is null) + { + writer.WriteNull(); + return; + } + + writer.WriteString(value.Name); + } + + public TEnum Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver) + { + if (reader.ReadIsNull()) + return null; + + var name = reader.ReadString(); + return SmartFlagEnum.FromName(name).FirstOrDefault(); + } + } +} \ No newline at end of file diff --git a/src/SmartEnum.Utf8Json/SmartFlagEnumNameResolver.cs b/src/SmartEnum.Utf8Json/SmartFlagEnumNameResolver.cs new file mode 100644 index 00000000..353fff44 --- /dev/null +++ b/src/SmartEnum.Utf8Json/SmartFlagEnumNameResolver.cs @@ -0,0 +1,34 @@ +namespace Ardalis.SmartEnum.Utf8Json +{ + using System; + using global::Utf8Json; + using global::Utf8Json.Formatters; + + public class SmartFlagEnumNameResolver : IJsonFormatterResolver + { + public static readonly SmartFlagEnumNameResolver Instance = new SmartFlagEnumNameResolver(); + + private SmartFlagEnumNameResolver() + { + } + + public IJsonFormatter GetFormatter() => + FormatterCache.Formatter; + + private static class FormatterCache + { + public static readonly IJsonFormatter Formatter; + +#pragma warning disable S3963 // "static" fields should be initialized inline + static FormatterCache() + { + if (typeof(T).IsSmartFlagEnum(out var genericArguments)) + { + var formatterType = typeof(SmartFlagEnumNameFormatter<,>).MakeGenericType(genericArguments); + Formatter = (IJsonFormatter)Activator.CreateInstance(formatterType); + } + } +#pragma warning restore S3963 // "static" fields should be initialized inline + } + } +} \ No newline at end of file diff --git a/src/SmartEnum.Utf8Json/SmartFlagEnumValueFormatter.cs b/src/SmartEnum.Utf8Json/SmartFlagEnumValueFormatter.cs new file mode 100644 index 00000000..e9517904 --- /dev/null +++ b/src/SmartEnum.Utf8Json/SmartFlagEnumValueFormatter.cs @@ -0,0 +1,76 @@ +namespace Ardalis.SmartEnum.Utf8Json +{ + using global::Utf8Json; + using global::Utf8Json.Internal; + using System; + + public class SmartFlagEnumValueFormatter : IJsonFormatter + where TEnum : SmartFlagEnum + where TValue : struct, IEquatable, IComparable + { + public void Serialize(ref JsonWriter writer, TEnum value, IJsonFormatterResolver formatterResolver) + { + if (value is null) + { + writer.WriteNull(); + return; + } + + else if (typeof(TValue) == typeof(byte)) + writer.WriteByte((byte)(object)value.Value); + else if (typeof(TValue) == typeof(sbyte)) + writer.WriteSByte((sbyte)(object)value.Value); + else if (typeof(TValue) == typeof(short)) + writer.WriteInt16((short)(object)value.Value); + else if (typeof(TValue) == typeof(ushort)) + writer.WriteUInt16((ushort)(object)value.Value); + else if (typeof(TValue) == typeof(int)) + writer.WriteInt32((int)(object)value.Value); + else if (typeof(TValue) == typeof(uint)) + writer.WriteUInt32((uint)(object)value.Value); + else if (typeof(TValue) == typeof(long)) + writer.WriteInt64((long)(object)value.Value); + else if (typeof(TValue) == typeof(ulong)) + writer.WriteUInt64((ulong)(object)value.Value); + else if (typeof(TValue) == typeof(float)) + writer.WriteSingle((float)(object)value.Value); + else if (typeof(TValue) == typeof(double)) + writer.WriteDouble((double)(object)value.Value); + else + throw new ArgumentOutOfRangeException(typeof(TValue).ToString(), $"{typeof(TValue).Name} is not supported."); + } + + public TEnum Deserialize(ref JsonReader reader, IJsonFormatterResolver formatterResolver) + { + if (reader.ReadIsNull()) + return null; + + return SmartFlagEnum.DeserializeValue(ReadValue(ref reader)); + } + + TValue ReadValue(ref JsonReader reader) + { + if (typeof(TValue) == typeof(byte)) + return (TValue)(object)reader.ReadByte(); + if (typeof(TValue) == typeof(sbyte)) + return (TValue)(object)reader.ReadSByte(); + if (typeof(TValue) == typeof(short)) + return (TValue)(object)reader.ReadInt16(); + if (typeof(TValue) == typeof(ushort)) + return (TValue)(object)reader.ReadUInt16(); + if (typeof(TValue) == typeof(int)) + return (TValue)(object)reader.ReadInt32(); + if (typeof(TValue) == typeof(uint)) + return (TValue)(object)reader.ReadUInt32(); + if (typeof(TValue) == typeof(long)) + return (TValue)(object)reader.ReadInt64(); + if (typeof(TValue) == typeof(ulong)) + return (TValue)(object)reader.ReadUInt64(); + if (typeof(TValue) == typeof(float)) + return (TValue)(object)reader.ReadSingle(); + if (typeof(TValue) == typeof(double)) + return (TValue)(object)reader.ReadDouble(); + throw new ArgumentOutOfRangeException(typeof(TValue).ToString(), $"{typeof(TValue).Name} is not supported."); + } + } +} \ No newline at end of file diff --git a/src/SmartEnum.Utf8Json/SmartFlagEnumValueResolver.cs b/src/SmartEnum.Utf8Json/SmartFlagEnumValueResolver.cs new file mode 100644 index 00000000..b3fc2286 --- /dev/null +++ b/src/SmartEnum.Utf8Json/SmartFlagEnumValueResolver.cs @@ -0,0 +1,45 @@ +namespace Ardalis.SmartEnum.Utf8Json +{ + using System; + using global::Utf8Json; + using global::Utf8Json.Formatters; + + /// + /// + /// + public class SmartFlagEnumValueResolver : IJsonFormatterResolver + { + /// + /// + /// + public static readonly SmartFlagEnumValueResolver Instance = new SmartFlagEnumValueResolver(); + + public SmartFlagEnumValueResolver() + { + } + + /// + /// + /// + /// + /// + public IJsonFormatter GetFormatter() => + FormatterCache.Formatter; + + private static class FormatterCache + { + public static readonly IJsonFormatter Formatter; + +#pragma warning disable S3963 // "static" fields should be initialized inline + static FormatterCache() + { + if (typeof(T).IsSmartFlagEnum(out var genericArguments)) + { + var formatterType = typeof(SmartFlagEnumValueFormatter<,>).MakeGenericType(genericArguments); + Formatter = (IJsonFormatter)Activator.CreateInstance(formatterType); + } + } +#pragma warning restore S3963 // "static" fields should be initialized inline + } + } +} \ No newline at end of file diff --git a/src/SmartEnum/AllowNegativeInputValuesAttribute.cs b/src/SmartEnum/AllowNegativeInputValuesAttribute.cs new file mode 100644 index 00000000..2e53847d --- /dev/null +++ b/src/SmartEnum/AllowNegativeInputValuesAttribute.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ardalis.SmartEnum +{ + [AttributeUsage(AttributeTargets.Class)] + public class AllowNegativeInputValuesAttribute : Attribute + { + } +} diff --git a/src/SmartEnum/AllowUnsafeFlagEnumValuesAttribute.cs b/src/SmartEnum/AllowUnsafeFlagEnumValuesAttribute.cs new file mode 100644 index 00000000..6cc6b880 --- /dev/null +++ b/src/SmartEnum/AllowUnsafeFlagEnumValuesAttribute.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ardalis.SmartEnum +{ + [AttributeUsage(AttributeTargets.Class)] + public class AllowUnsafeFlagEnumValuesAttribute : Attribute + { + } +} diff --git a/src/SmartEnum/Core/SmartEnumThen.cs b/src/SmartEnum/Core/SmartEnumThen.cs index fff4be00..93469f56 100644 --- a/src/SmartEnum/Core/SmartEnumThen.cs +++ b/src/SmartEnum/Core/SmartEnumThen.cs @@ -3,14 +3,14 @@ namespace Ardalis.SmartEnum.Core { public readonly struct SmartEnumThen - where TEnum : SmartEnum + where TEnum : ISmartEnum where TValue : IEquatable, IComparable { private readonly bool isMatch; - private readonly SmartEnum smartEnum; + private readonly ISmartEnum smartEnum; private readonly bool stopEvaluating; - internal SmartEnumThen(bool isMatch, bool stopEvaluating, SmartEnum smartEnum) + internal SmartEnumThen(bool isMatch, bool stopEvaluating, ISmartEnum smartEnum) { this.isMatch = isMatch; this.smartEnum = smartEnum; diff --git a/src/SmartEnum/Core/SmartEnumWhen.cs b/src/SmartEnum/Core/SmartEnumWhen.cs index 725e7d0a..2fa1c0b8 100644 --- a/src/SmartEnum/Core/SmartEnumWhen.cs +++ b/src/SmartEnum/Core/SmartEnumWhen.cs @@ -5,13 +5,13 @@ namespace Ardalis.SmartEnum.Core { public readonly struct SmartEnumWhen - where TEnum : SmartEnum + where TEnum : ISmartEnum where TValue : IEquatable, IComparable { - private readonly SmartEnum smartEnum; + private readonly ISmartEnum smartEnum; private readonly bool stopEvaluating; - internal SmartEnumWhen(bool stopEvaluating, SmartEnum smartEnum) + internal SmartEnumWhen(bool stopEvaluating, ISmartEnum smartEnum) { this.stopEvaluating = stopEvaluating; this.smartEnum = smartEnum; @@ -35,7 +35,7 @@ public void Default(Action action) /// /// A collection of values to compare to this instance. /// A executor object to execute a supplied action. - public SmartEnumThen When(SmartEnum smartEnumWhen) => + public SmartEnumThen When(ISmartEnum smartEnumWhen) => new SmartEnumThen(isMatch: smartEnum.Equals(smartEnumWhen), stopEvaluating: stopEvaluating, smartEnum: smartEnum); /// @@ -44,7 +44,7 @@ public SmartEnumThen When(SmartEnum smartEnumWhen) /// /// A collection of values to compare to this instance. /// A executor object to execute a supplied action. - public SmartEnumThen When(params SmartEnum[] smartEnums) => + public SmartEnumThen When(params ISmartEnum[] smartEnums) => new SmartEnumThen(isMatch: smartEnums.Contains(smartEnum), stopEvaluating: stopEvaluating, smartEnum: smartEnum); /// @@ -53,7 +53,7 @@ public SmartEnumThen When(params SmartEnum[] smart /// /// A collection of values to compare to this instance. /// A executor object to execute a supplied action. - public SmartEnumThen When(IEnumerable> smartEnums) => + public SmartEnumThen When(IEnumerable smartEnums) => new SmartEnumThen(isMatch: smartEnums.Contains(smartEnum), stopEvaluating: stopEvaluating, smartEnum: smartEnum); } } \ No newline at end of file diff --git a/src/SmartEnum/Exceptions/InvalidFlagEnumValueParseException.cs b/src/SmartEnum/Exceptions/InvalidFlagEnumValueParseException.cs new file mode 100644 index 00000000..8ca32463 --- /dev/null +++ b/src/SmartEnum/Exceptions/InvalidFlagEnumValueParseException.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ardalis.SmartEnum.Exceptions +{ + [Serializable] + public class InvalidFlagEnumValueParseException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public InvalidFlagEnumValueParseException() : base() + { + } + + /// + /// Initializes a new instance of the class with serialized data. + /// + /// The object that holds the serialized object data. + /// The contextual information about the source or destination. + protected InvalidFlagEnumValueParseException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) + : base(info, context) + { + } + + /// + /// Initializes a new instance of the class with a user specified error . + /// + /// The error message that explains the reason for the exception. + public InvalidFlagEnumValueParseException(string message) : base(message) + { + } + + /// + /// Initializes a new instance of the class with a user specified error + /// and a wrapped that is the cause of this exception. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception. If the parameter is not a null reference, + /// the current exception is raised in a catch block that handles the inner exception. + public InvalidFlagEnumValueParseException(string message, Exception innerException) : base(message, innerException) + { + } + } +} diff --git a/src/SmartEnum/Exceptions/NegativeValueArgumentException.cs b/src/SmartEnum/Exceptions/NegativeValueArgumentException.cs new file mode 100644 index 00000000..138fa386 --- /dev/null +++ b/src/SmartEnum/Exceptions/NegativeValueArgumentException.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ardalis.SmartEnum.Exceptions +{ + /// + /// The exception that is thrown when a negative value is provided as an argument value. + /// + [Serializable] + public class NegativeValueArgumentException : ArgumentException + { + /// + /// Initializes a new instance of the class. + /// + public NegativeValueArgumentException() : base() + { + } + + /// + /// Initializes a new instance of the class with serialized data. + /// + /// The object that holds the serialized object data. + /// The contextual information about the source or destination. + protected NegativeValueArgumentException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) + : base(info, context) + { + } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The error message that explains the reason for the exception. + public NegativeValueArgumentException(string message) : base(message) + { + } + + /// + /// Initializes a new instance of the class with a specified error message and + /// a reference to the inner exception that is the cause of this exception. + /// + /// The error message that explains the reason for the exception. + /// + /// The exception that is the cause of the current exception. If the parameter is not a null reference, + /// the current exception is raised in a catch block that handles the inner exception. + /// + public NegativeValueArgumentException(string message, Exception innerException) : base(message, innerException) + { + } + } +} diff --git a/src/SmartEnum/Exceptions/SmartFlagEnumContainsNegativeValueException.cs b/src/SmartEnum/Exceptions/SmartFlagEnumContainsNegativeValueException.cs new file mode 100644 index 00000000..2e2bbfce --- /dev/null +++ b/src/SmartEnum/Exceptions/SmartFlagEnumContainsNegativeValueException.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ardalis.SmartEnum.Exceptions +{ + [Serializable] + public class SmartFlagEnumContainsNegativeValueException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public SmartFlagEnumContainsNegativeValueException() : base() + { + } + + /// + /// Initializes a new instance of the class with serialized data. + /// + /// The object that holds the serialized object data. + /// The contextual information about the source or destination. + protected SmartFlagEnumContainsNegativeValueException(System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) + : base(info, context) + { + } + + /// + /// Initializes a new instance of the class with a user specified error . + /// + /// The error message that explains the reason for the exception. + public SmartFlagEnumContainsNegativeValueException(string message) : base(message) + { + } + + /// + /// Initializes a new instance of the class with a user specified error + /// and a wrapped that is the cause of this exception. + /// + /// The error message that explains the reason for the exception. + /// The exception that is the cause of the current exception. If the parameter is not a null reference, + /// the current exception is raised in a catch block that handles the inner exception. + public SmartFlagEnumContainsNegativeValueException(string message, Exception innerException) : base(message, innerException) + { + } + } +} diff --git a/src/SmartEnum/Exceptions/SmartFlagEnumDoesNotContainPowerOfTwoValuesException.cs b/src/SmartEnum/Exceptions/SmartFlagEnumDoesNotContainPowerOfTwoValuesException.cs new file mode 100644 index 00000000..852b68e5 --- /dev/null +++ b/src/SmartEnum/Exceptions/SmartFlagEnumDoesNotContainPowerOfTwoValuesException.cs @@ -0,0 +1,56 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ardalis.SmartEnum.Exceptions +{ + /// + /// The exception that is thrown when a does not contain consecutive power of two values. + /// + [Serializable] + public class SmartFlagEnumDoesNotContainPowerOfTwoValuesException : Exception + { + /// + /// Initializes a new instance of the class. + /// + public SmartFlagEnumDoesNotContainPowerOfTwoValuesException() + : base() + { + } + + /// + /// Initializes a new instance of the class with serialized data. + /// + /// The object that holds the serialized object data. + /// The contextual information about the source or destination. + protected SmartFlagEnumDoesNotContainPowerOfTwoValuesException( + System.Runtime.Serialization.SerializationInfo info, System.Runtime.Serialization.StreamingContext context) + : base(info, context) + { + } + + /// + /// Initializes a new instance of the class with a specified error message. + /// + /// The error message that explains the reason for the exception. + public SmartFlagEnumDoesNotContainPowerOfTwoValuesException(string message) + : base(message) + { + } + + /// + /// Initializes a new instance of the class with a specified error message and + /// a reference to the inner exception that is the cause of this exception. + /// + /// The error message that explains the reason for the exception. + /// + /// The exception that is the cause of the current exception. If the parameter is not a null reference, + /// the current exception is raised in a catch block that handles the inner exception. + /// + public SmartFlagEnumDoesNotContainPowerOfTwoValuesException(string message, Exception innerException) + : base(message, innerException) + { + } + + } +} diff --git a/src/SmartEnum/ISmartEnum.cs b/src/SmartEnum/ISmartEnum.cs new file mode 100644 index 00000000..e8106ff6 --- /dev/null +++ b/src/SmartEnum/ISmartEnum.cs @@ -0,0 +1,11 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ardalis.SmartEnum +{ + public interface ISmartEnum + { + //Empty Interface + } +} diff --git a/src/SmartEnum/SmartEnum.cs b/src/SmartEnum/SmartEnum.cs index 9d684f4f..356a9655 100644 --- a/src/SmartEnum/SmartEnum.cs +++ b/src/SmartEnum/SmartEnum.cs @@ -1,4 +1,6 @@ -namespace Ardalis.SmartEnum +using System.Runtime.InteropServices.ComTypes; + +namespace Ardalis.SmartEnum { using System; using System.Collections.Generic; @@ -31,6 +33,7 @@ protected SmartEnum(string name, int value) : /// The type of the inner value. /// public abstract class SmartEnum : + ISmartEnum, IEquatable>, IComparable> where TEnum : SmartEnum @@ -39,10 +42,10 @@ public abstract class SmartEnum : static readonly Lazy _enumOptions = new Lazy(GetAllOptions, LazyThreadSafetyMode.ExecutionAndPublication); - static readonly Lazy> _fromName = + static readonly Lazy> _fromName = new Lazy>(() => _enumOptions.Value.ToDictionary(item => item.Name)); - static readonly Lazy> _fromNameIgnoreCase = + static readonly Lazy> _fromNameIgnoreCase = new Lazy>(() => _enumOptions.Value.ToDictionary(item => item.Name, StringComparer.OrdinalIgnoreCase)); static readonly Lazy> _fromValue = @@ -82,19 +85,6 @@ private static TEnum[] GetAllOptions() private readonly string _name; private readonly TValue _value; - /// - /// Gets the name. - /// - /// A that is the name of the . - public string Name => - _name; - - /// - /// Gets the value. - /// - /// A that is the value of the . - public TValue Value => - _value; protected SmartEnum(string name, TValue value) { @@ -108,11 +98,19 @@ protected SmartEnum(string name, TValue value) } /// - /// Gets the type of the inner value. + /// Gets the name. /// - /// A that is the type of the value of the . - public Type GetValueType() => - typeof(TValue); + /// A that is the name of the . + public string Name => + _name; + + /// + /// Gets the value. + /// + /// A that is the value of the . + public TValue Value => + _value; + /// /// Gets the item associated with the specified name. diff --git a/src/SmartEnum/SmartEnumExtensions.cs b/src/SmartEnum/SmartEnumExtensions.cs index 4fa70b48..39c446c8 100644 --- a/src/SmartEnum/SmartEnumExtensions.cs +++ b/src/SmartEnum/SmartEnumExtensions.cs @@ -1,3 +1,6 @@ +using System.Globalization; +using System.Linq; + namespace Ardalis.SmartEnum { using System; @@ -38,7 +41,7 @@ public static bool TryGetValues(this Type type, out IEnumerable enums) { while (type != null) { - if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(SmartEnum<,>)) + if (type.IsGenericType && type.GetGenericTypeDefinition() == typeof(SmartEnum<,>) || type.IsGenericType && type.GetGenericTypeDefinition() == typeof(SmartFlagEnum<,>)) { var listPropertyInfo = type.GetProperty("List", BindingFlags.Public | BindingFlags.Static); enums = (IEnumerable)listPropertyInfo.GetValue(type, null); diff --git a/src/SmartEnum/SmartFlagEngine.cs b/src/SmartEnum/SmartFlagEngine.cs new file mode 100644 index 00000000..47fba4fe --- /dev/null +++ b/src/SmartEnum/SmartFlagEngine.cs @@ -0,0 +1,218 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Text; +using Ardalis.SmartEnum.Exceptions; + +namespace Ardalis.SmartEnum +{ + public abstract class SmartFlagEngine + where TEnum : SmartFlagEnum + where TValue : IEquatable, IComparable + { + protected SmartFlagEngine() { } + + /// + /// Returns an representing a given + /// + /// The value to retrieve. + /// an of from which to retrieve values. + /// + protected static IEnumerable GetFlagEnumValues(TValue value, IEnumerable allEnumList) + { + GuardAgainstNull(value); + GuardAgainstInvalidInputValue(value); + GuardAgainstNegativeInputValue(value); + + var inputValueAsInt = int.Parse(value.ToString()); + var enumFlagStateDictionary = new Dictionary(); + var inputEnumList = allEnumList.ToList(); + + ApplyUnsafeFlagEnumAttributeSettings(inputEnumList); + + var maximumAllowedValue = CalculateHighestAllowedFlagValue(inputEnumList); + + var typeMaxValue = GetMaxValue(); + + foreach (var enumValue in inputEnumList) + { + var currentEnumValueAsInt = int.Parse(enumValue.Value.ToString()); + + CheckEnumForNegativeValues(currentEnumValueAsInt); + + if (currentEnumValueAsInt == inputValueAsInt) + return new List { enumValue }; + + if (inputValueAsInt == -1 || value.Equals(typeMaxValue)) + { + return inputEnumList.Where(x => long.Parse(x.Value.ToString()) > 0); + } + + AssignFlagStateValuesToDictionary(inputValueAsInt, currentEnumValueAsInt, enumValue, enumFlagStateDictionary); + } + + return inputValueAsInt > maximumAllowedValue ? default : CreateSmartEnumReturnList(enumFlagStateDictionary); + } + + private static void GuardAgainstNull(TValue value) + { + if (value == null) + ThrowHelper.ThrowArgumentNullException(nameof(value)); + } + + private static void GuardAgainstInvalidInputValue(TValue value) + { + if (!int.TryParse(value.ToString(), out _)) + ThrowHelper.ThrowInvalidValueCastException(value); + } + + private static void GuardAgainstNegativeInputValue(TValue value) + { + AllowNegativeInputValuesAttribute attribute = (AllowNegativeInputValuesAttribute) + Attribute.GetCustomAttribute(typeof(TEnum), typeof(AllowNegativeInputValuesAttribute)); + + if (attribute == null && int.Parse(value.ToString()) < -1) + { + ThrowHelper.ThrowNegativeValueArgumentException(value); + } + } + + private static void CheckEnumForNegativeValues(int value) + { + if (value < -1) + ThrowHelper.ThrowContainsNegativeValueException(); + } + + private static int CalculateHighestAllowedFlagValue(List inputEnumList) + { + return (HighestFlagValue(inputEnumList) * 2) - 1; + } + + private static void AssignFlagStateValuesToDictionary(int inputValueAsInt, int currentEnumValue, TEnum enumValue, IDictionary enumFlagStateDictionary) + { + if (!enumFlagStateDictionary.ContainsKey(enumValue) && currentEnumValue != 0) + { + bool flagState = (inputValueAsInt & currentEnumValue) == currentEnumValue; + enumFlagStateDictionary.Add(enumValue, flagState); + } + } + + private static IEnumerable CreateSmartEnumReturnList(Dictionary enumFlagStateDictionary) + { + var outputList = new List(); + + foreach (var entry in enumFlagStateDictionary) + { + if (entry.Value) + { + outputList.Add(entry.Key); + } + } + + return outputList.DefaultIfEmpty(); + } + + private static void ApplyUnsafeFlagEnumAttributeSettings(IEnumerable list) + { + AllowUnsafeFlagEnumValuesAttribute attribute = (AllowUnsafeFlagEnumValuesAttribute) + Attribute.GetCustomAttribute(typeof(TEnum), typeof(AllowUnsafeFlagEnumValuesAttribute)); + + if (attribute == null) + { + CheckEnumListForPowersOfTwo(list); + } + } + + private static void CheckEnumListForPowersOfTwo(IEnumerable enumEnumerable) + { + var enumList = enumEnumerable.ToList(); + var enumValueList = new List(); + foreach (var smartFlagEnum in enumList) + { + enumValueList.Add(int.Parse(smartFlagEnum.Value.ToString())); + } + var firstPowerOfTwoValue = 0; + if (int.Parse(enumList[0].Value.ToString()) == 0) + { + enumList.RemoveAt(0); + } + + foreach (var flagEnum in enumList) + { + var x = int.Parse(flagEnum.Value.ToString()); + if (IsPowerOfTwo(x)) + { + firstPowerOfTwoValue = x; + break; + } + } + + var highestValue = HighestFlagValue(enumList); + var currentValue = firstPowerOfTwoValue; + + while (currentValue != highestValue) + { + var nextPowerOfTwoValue = currentValue * 2; + var result = enumValueList.BinarySearch(nextPowerOfTwoValue); + if (result < 0) + { + ThrowHelper.ThrowDoesNotContainPowerOfTwoValuesException(); + } + + currentValue = nextPowerOfTwoValue; + } + } + + private static bool IsPowerOfTwo(int input) + { + if (input != 0 && ((input & (input - 1)) == 0)) + { + return true; + } + + return false; + } + + private static int HighestFlagValue(IReadOnlyList enumList) + { + var highestIndex = enumList.Count - 1; + var highestValue = int.Parse(enumList.Last().Value.ToString()); + if (!IsPowerOfTwo(highestValue)) + { + for (var i = highestIndex; i >= 0; i--) + { + var currentValue = int.Parse(enumList[i].Value.ToString()); + if (IsPowerOfTwo(currentValue)) + { + highestValue = currentValue; + break; + } + } + } + + return highestValue; + } + + /// + /// Gets the largest possible value of the underlying type for the SmartFlagEnum. + /// + /// If the underlying type + /// does not define a MaxValue field, this exception is thrown. + /// + /// The value of the constant MaxValue field defined by the underlying type . + private static TValue GetMaxValue() + { + FieldInfo maxValueField = typeof(TValue).GetField("MaxValue", BindingFlags.Public + | BindingFlags.Static); + if (maxValueField == null) + throw new NotSupportedException(typeof(TValue).Name); + + TValue maxValue = (TValue)maxValueField.GetValue(null); + + return maxValue; + } + } +} diff --git a/src/SmartEnum/SmartFlagEnum.cs b/src/SmartEnum/SmartFlagEnum.cs new file mode 100644 index 00000000..3b82710b --- /dev/null +++ b/src/SmartEnum/SmartFlagEnum.cs @@ -0,0 +1,429 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Text; +using Ardalis.SmartEnum.Core; +using Ardalis.SmartEnum.Exceptions; + +namespace Ardalis.SmartEnum +{ + /// + /// A base type to use for creating smart enums with inner value of type . + /// + /// The type that is inheriting from this class. + /// + public abstract class SmartFlagEnum : + SmartFlagEnum + where TEnum : SmartFlagEnum + { + protected SmartFlagEnum(string name, int value) : + base(name, value) + { + } + } + + /// + /// A base type to use for creating smart enums. + /// + /// The type that is inheriting from this class. + /// The type of the inner value. + /// + public abstract class SmartFlagEnum : + SmartFlagEngine, + ISmartEnum, + IEquatable>, + IComparable> + where TEnum : SmartFlagEnum + where TValue : IEquatable, IComparable + { + static readonly Lazy> _fromName = + new Lazy>(() => GetAllOptions().ToDictionary(item => item.Name)); + + static readonly Lazy> _fromNameIgnoreCase = + new Lazy>(() => GetAllOptions().ToDictionary(item => item.Name, StringComparer.OrdinalIgnoreCase)); + + private static IEnumerable GetAllOptions() + { + Type baseType = typeof(TEnum); + IEnumerable enumTypes = Assembly.GetAssembly(baseType).GetTypes().Where(t => baseType.IsAssignableFrom(t)); + + List options = new List(); + foreach (Type enumType in enumTypes) + { + List typeEnumOptions = enumType.GetFieldsOfType(); + options.AddRange(typeEnumOptions); + } + + return options.OrderBy(t => t.Value); + } + + /// + /// Gets a collection containing all the instances of . + /// + /// A containing all the instances of . + /// Retrieves all the instances of referenced by public static read-only fields in the current class or its bases. + public static IReadOnlyCollection List => + _fromName.Value.Values + .ToList() + .AsReadOnly(); + + private readonly string _name; + private readonly TValue _value; + + /// + /// Gets the names. + /// + /// A that is the names of the . + public string Name => + _name; + + /// + /// Gets the value. + /// + /// A that is the value of the . + public TValue Value => + _value; + + protected SmartFlagEnum(string name, TValue value) + { + if (String.IsNullOrEmpty(name)) + ThrowHelper.ThrowArgumentNullOrEmptyException(nameof(name)); + if (value == null) + ThrowHelper.ThrowArgumentNullException(nameof(value)); + + _name = name; + _value = value; + } + + /// + /// Gets the items associated with the specified names. + /// + /// The names of the item/s to get. + /// true to ignore case during the comparison; otherwise, false. + /// + /// The items associated with the specified . + /// + /// is null. + /// does not exist. + /// + /// + public static IEnumerable FromName(string names, bool ignoreCase = false, bool deserialize = false) + { + if (String.IsNullOrEmpty(names)) + ThrowHelper.ThrowArgumentNullOrEmptyException(nameof(names)); + + if (ignoreCase) + return FromName(_fromNameIgnoreCase.Value); + else + return FromName(_fromName.Value); + + IEnumerable FromName(Dictionary dictionary) + { + if (!dictionary.TryGetFlagEnumValuesByName(names, out var result)) + { + ThrowHelper.ThrowNameNotFoundException(names); + } + return result; + } + } + + /// + /// Gets the items associated with the specified . + /// + /// The names of the item/s to get. + /// + /// When this method returns, contains the item associated with the specified names, if the key is found; + /// otherwise, null. This parameter is passed uninitialized. + /// + /// true if the contains an item with the specified names; otherwise, false. + /// + /// is null. + /// + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool TryFromName(string names, out IEnumerable result) => + TryFromName(names, false, out result); + + /// + /// Gets the items associated with the specified . + /// + /// The names of the item/s to get. + /// true to ignore case during the comparison; otherwise, false. + /// + /// When this method returns, contains the items associated with the specified names, if any names are found; + /// otherwise, null. This parameter is passed uninitialized. + /// + /// true if the contains an item with the specified names; otherwise, false. + /// + /// is null. + /// + /// + public static bool TryFromName(string names, bool ignoreCase, out IEnumerable result) + { + if (String.IsNullOrEmpty(names)) + ThrowHelper.ThrowArgumentNullOrEmptyException(nameof(names)); + + if (ignoreCase) + return _fromNameIgnoreCase.Value.TryGetFlagEnumValuesByName(names, out result); + else + return _fromName.Value.TryGetFlagEnumValuesByName(names, out result); + } + + /// + /// Gets an associated with the specified . + /// + /// The value of the item/s to get. + /// + /// An containing the item/s found. + /// + /// does not exist. + /// + /// + public static IEnumerable FromValue(TValue value) + { + if (value == null) + ThrowHelper.ThrowArgumentNullException(nameof(value)); + + if (GetFlagEnumValues(value, GetAllOptions()) == null) + { + ThrowHelper.ThrowValueNotFoundException(value); + } + + return GetFlagEnumValues(value, GetAllOptions()); + } + + /// + /// Attempts to retrieve a single by value. (Bypasses all Flag behaviour) + /// + /// does not exist. + /// + /// + public static TEnum DeserializeValue(TValue value) + { + var enumList = GetAllOptions(); + + foreach (var smartFlagEnum in enumList) + { + if (smartFlagEnum.Value.Equals(value)) + { + return smartFlagEnum; + } + } + + ThrowHelper.ThrowValueNotFoundException(value); + + return null; + } + + /// + /// Gets an associated with the specified . + /// + /// The value of the item/s to get. + /// The value to return when no items found. + /// + /// An containing the item/s found. + /// If the specified value is not found, returns . + /// + /// + /// + public static IEnumerable FromValue(TValue value, IEnumerable defaultValue) + { + if (value == null) + ThrowHelper.ThrowArgumentNullException(nameof(value)); + + return !TryFromValue(value, out var result) ? defaultValue : result; + } + + /// + /// Gets an associated with the specified . + /// + /// The value of the item/s to get. + /// + /// When this method returns, contains the IEnumerable of item/s associated with the specified value, if any value is found; + /// otherwise, null. This parameter is passed uninitialized. + /// + /// true if the contains any items with the specified names; otherwise, false. + /// + /// + /// + public static bool TryFromValue(TValue value, out IEnumerable result) + { + if (value == null || !int.TryParse(value.ToString(), out _)) + { + result = default; + return false; + } + + + result = GetFlagEnumValues(value, GetAllOptions()); + if (result == null) + { + return false; + } + return true; + } + + /// + /// Returns a representation of a given as a series of comma delineated enum names. + /// + /// The value of the item/s to get. + /// A comma delineated series of enum names as a + public static string FromValueToString(TValue value) + { + if (!TryFromValue(value, out _)) + { + ThrowHelper.ThrowValueNotFoundException(value); + } + + return FormatEnumListString(GetFlagEnumValues(value, GetAllOptions())); + } + + /// + /// Returns a representation of a given as a series of comma delineated enum names. + /// + /// The value of the item/s to get. + /// + /// When this method returns, contains the string representation associated with the specified value, if any value is found; + /// otherwise, returns null. + /// A comma delineated series of enum names as a + public static bool TryFromValueToString(TValue value, out string result) + { + if (!TryFromValue(value, out var enumResult)) + { + result = default; + return false; + } + + result = FormatEnumListString(GetFlagEnumValues(value, enumResult)); + return true; + } + + private static string FormatEnumListString(IEnumerable enumInputList) + { + var enumList = enumInputList.ToList(); + var sb = new StringBuilder(); + + foreach (var smartFlagEnum in enumList) + { + sb.Append(smartFlagEnum.Name); + if (enumList.Last().Name != smartFlagEnum.Name && enumList.Count > 1) + { + sb.Append(", "); + } + } + + return sb.ToString(); + } + + public override string ToString() => + _name; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public override int GetHashCode() => + _value.GetHashCode(); + + /// + /// + /// + /// + /// + public override bool Equals(object obj) => + (obj is SmartFlagEnum other) && Equals(other); + + /// + /// Returns a value indicating whether this instance is equal to a specified value. + /// + /// An value to compare to this instance. + /// true if has the same value as this instance; otherwise, false. + public virtual bool Equals(SmartFlagEnum other) + { + // check if same instance + if (Object.ReferenceEquals(this, other)) + return true; + + // it's not same instance so + // check if it's not null and is same value + if (other is null) + return false; + + return _value.Equals(other._value); + } + + /// + /// When this instance is one of the specified parameters. + /// Execute the action in the subsequent call to Then(). + /// + /// A collection of values to compare to this instance. + /// A executor object to execute a supplied action. + public SmartEnumThen When(SmartFlagEnum smartFlagEnumWhen) => + new SmartEnumThen(this.Equals(smartFlagEnumWhen), false, this); + + /// + /// When this instance is one of the specified parameters. + /// Execute the action in the subsequent call to Then(). + /// + /// A collection of values to compare to this instance. + /// A executor object to execute a supplied action. + public SmartEnumThen When(params SmartFlagEnum[] smartEnums) => + new SmartEnumThen(smartEnums.Contains(this), false, this); + + /// + /// When this instance is one of the specified parameters. + /// Execute the action in the subsequent call to Then(). + /// + /// A collection of values to compare to this instance. + /// A executor object to execute a supplied action. + public SmartEnumThen When(IEnumerable> smartEnums) => + new SmartEnumThen(smartEnums.Contains(this), false, this); + + public static bool operator ==(SmartFlagEnum left, SmartFlagEnum right) + { + // Handle null on left side + if (left is null) + return right is null; // null == null = true + + // Equals handles null on right side + return left.Equals(right); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator !=(SmartFlagEnum left, SmartFlagEnum right) => + !(left == right); + + /// + /// Compares this instance to a specified and returns an indication of their relative values. + /// + /// An value to compare to this instance. + /// A signed number indicating the relative values of this instance and . + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public virtual int CompareTo(SmartFlagEnum other) => + _value.CompareTo(other._value); + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator <(SmartFlagEnum left, SmartFlagEnum right) => + left.CompareTo(right) < 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator <=(SmartFlagEnum left, SmartFlagEnum right) => + left.CompareTo(right) <= 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator >(SmartFlagEnum left, SmartFlagEnum right) => + left.CompareTo(right) > 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static bool operator >=(SmartFlagEnum left, SmartFlagEnum right) => + left.CompareTo(right) >= 0; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static implicit operator TValue(SmartFlagEnum smartFlagEnum) => + smartFlagEnum._value; + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static explicit operator SmartFlagEnum(TValue value) => + FromValue(value).First(); + } +} diff --git a/src/SmartEnum/SmartFlagEnumExtensions.cs b/src/SmartEnum/SmartFlagEnumExtensions.cs new file mode 100644 index 00000000..af3efbd4 --- /dev/null +++ b/src/SmartEnum/SmartFlagEnumExtensions.cs @@ -0,0 +1,75 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace Ardalis.SmartEnum +{ + public static class SmartFlagEnumExtensions + { + public static bool IsSmartFlagEnum(this Type type) => + IsSmartFlagEnum(type, out var _); + + /// + /// + /// + /// + /// + /// + public static bool IsSmartFlagEnum(this Type type, out Type[] genericArguments) + { + if (type is null || type.IsAbstract || type.IsGenericTypeDefinition) + { + genericArguments = null; + return false; + } + + do + { + if (type.IsGenericType && + type.GetGenericTypeDefinition() == typeof(SmartFlagEnum<,>)) + { + genericArguments = type.GetGenericArguments(); + return true; + } + + type = type.BaseType; + } + while (!(type is null)); + + genericArguments = null; + return false; + } + + public static bool TryGetFlagEnumValuesByName(this Dictionary dictionary, string names, out IEnumerable outputEnums) + where TEnum : SmartFlagEnum + where TValue : IEquatable, IComparable + { + var outputList = new List(dictionary.Count); + + var commaSplitNameList = names.Replace(" ", "").Trim().Split(','); + Array.Sort(commaSplitNameList); + + foreach (var enumValue in dictionary.Values) + { + var result = Array.BinarySearch(commaSplitNameList, enumValue.Name); + if (result >= 0) + { + outputList.Add(enumValue); + } + } + + if (!outputList.Any()) + { + outputEnums = null; + return false; + } + + outputEnums = outputList.ToList(); + return true; + } + } +} diff --git a/src/SmartEnum/ThrowHelper.cs b/src/SmartEnum/ThrowHelper.cs index 50e26bca..c6e6f01a 100644 --- a/src/SmartEnum/ThrowHelper.cs +++ b/src/SmartEnum/ThrowHelper.cs @@ -1,4 +1,5 @@ using System; +using Ardalis.SmartEnum.Exceptions; namespace Ardalis.SmartEnum { @@ -11,13 +12,37 @@ public static void ThrowArgumentNullOrEmptyException(string paramName) => throw new ArgumentException("Argument cannot be null or empty.", paramName); public static void ThrowNameNotFoundException(string name) - where TEnum : SmartEnum + where TEnum : ISmartEnum where TValue : IEquatable, IComparable => throw new SmartEnumNotFoundException($"No {typeof(TEnum).Name} with Name \"{name}\" found."); public static void ThrowValueNotFoundException(TValue value) - where TEnum : SmartEnum + where TEnum : ISmartEnum where TValue : IEquatable, IComparable => throw new SmartEnumNotFoundException($"No {typeof(TEnum).Name} with Value {value} found."); + + public static void ThrowContainsNegativeValueException() + where TEnum : ISmartEnum + where TValue : IEquatable, IComparable + => throw new SmartFlagEnumContainsNegativeValueException($"The {typeof(TEnum).Name} contains negative values other than (-1)."); + + public static void ThrowInvalidValueCastException(TValue value) + where TEnum : ISmartEnum + where TValue : IEquatable, IComparable + => throw new InvalidFlagEnumValueParseException($"The value: {value} input to {typeof(TEnum).Name} could not be parsed into an integer value."); + + public static void ThrowNegativeValueArgumentException(TValue value) + where TEnum : ISmartEnum + where TValue : IEquatable, IComparable + => throw new NegativeValueArgumentException($"The value: {value} input to {typeof(TEnum).Name} was a negative number other than (-1)."); + public static void ThrowNegativeValueArgumentException(int value) + where TEnum : ISmartEnum + where TValue : IEquatable, IComparable + => throw new NegativeValueArgumentException($"The value: {value} input to {typeof(TEnum).Name} was a negative number other than (-1)."); + + public static void ThrowDoesNotContainPowerOfTwoValuesException() + where TEnum : ISmartEnum + where TValue : IEquatable, IComparable + => throw new SmartFlagEnumDoesNotContainPowerOfTwoValuesException($"the {typeof(TEnum).Name} does not contain consecutive power of two values."); } } diff --git a/test/SmartEnum.AutoFixture.UnitTests/SmartEnumSpecimenBuilderCreate.cs b/test/SmartEnum.AutoFixture.UnitTests/SmartEnumSpecimenBuilderCreate.cs index fddcb116..c2244758 100644 --- a/test/SmartEnum.AutoFixture.UnitTests/SmartEnumSpecimenBuilderCreate.cs +++ b/test/SmartEnum.AutoFixture.UnitTests/SmartEnumSpecimenBuilderCreate.cs @@ -16,5 +16,16 @@ public void ReturnsEnumGivenNoExplicitPriorUse() result.Should().BeSameAs(TestEnum.One); } + + [Fact] + public void ReturnsSmartEnumGivenNoExplicitPriorUse() + { + var fixture = new Fixture() + .Customize(new SmartEnumCustomization()); + + var result = fixture.Create(); + + result.Should().BeSameAs(SmartFlagTestEnum.One); + } } } \ No newline at end of file diff --git a/test/SmartEnum.AutoFixture.UnitTests/SmartFlagTestEnum.cs b/test/SmartEnum.AutoFixture.UnitTests/SmartFlagTestEnum.cs new file mode 100644 index 00000000..12f2fb6a --- /dev/null +++ b/test/SmartEnum.AutoFixture.UnitTests/SmartFlagTestEnum.cs @@ -0,0 +1,18 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Ardalis.SmartEnum; + +namespace Ardalis.SmartEnum.AutoFixture.UnitTests +{ + class SmartFlagTestEnum : SmartFlagEnum + { + public static readonly SmartFlagTestEnum One = new SmartFlagTestEnum(nameof(One), 1); + public static readonly SmartFlagTestEnum Two = new SmartFlagTestEnum(nameof(Two), 2); + public static readonly SmartFlagTestEnum Three = new SmartFlagTestEnum(nameof(Three), 3); + + public SmartFlagTestEnum(string name, int value) : base(name, value) + { + } + } +} diff --git a/test/SmartEnum.JsonNet.UnitTests/FlagTestEnums.cs b/test/SmartEnum.JsonNet.UnitTests/FlagTestEnums.cs new file mode 100644 index 00000000..243ae619 --- /dev/null +++ b/test/SmartEnum.JsonNet.UnitTests/FlagTestEnums.cs @@ -0,0 +1,34 @@ +using System; +using System.Collections.Generic; +using System.Text; +using System.Threading; +using Ardalis.SmartEnum; + +namespace Ardalis.SmartEnum.JsonNet.UnitTests +{ + public class FlagTestEnums + { + public sealed class FlagTestEnumInt16 : SmartFlagEnum + { + public static readonly FlagTestEnumInt16 Instance = new FlagTestEnumInt16(nameof(Instance), 1); + + FlagTestEnumInt16(string name, short value) : base(name, value) { } + } + + public sealed class FlagTestEnumInt32 : SmartFlagEnum + { + public static readonly FlagTestEnumInt32 Instance = new FlagTestEnumInt32(nameof(Instance), 1); + public static readonly FlagTestEnumInt32 Instance2 = new FlagTestEnumInt32(nameof(Instance2), 2); + public static readonly FlagTestEnumInt32 Instance3 = new FlagTestEnumInt32(nameof(Instance3), 4); + + FlagTestEnumInt32(string name, int value) : base(name, value) { } + } + + public sealed class FlagTestEnumDouble : SmartFlagEnum + { + public static readonly FlagTestEnumDouble Instance = new FlagTestEnumDouble(nameof(Instance), 1.0); + + FlagTestEnumDouble(string name, double value) : base(name, value) { } + } + } +} diff --git a/test/SmartEnum.JsonNet.UnitTests/SmartFlagEnumNameConverterTests.cs b/test/SmartEnum.JsonNet.UnitTests/SmartFlagEnumNameConverterTests.cs new file mode 100644 index 00000000..9e26baa3 --- /dev/null +++ b/test/SmartEnum.JsonNet.UnitTests/SmartFlagEnumNameConverterTests.cs @@ -0,0 +1,97 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Ardalis.SmartEnum; +using Ardalis.SmartEnum.JsonNet; +using Ardalis.SmartEnum.JsonNet.UnitTests; +using FluentAssertions; +using Newtonsoft.Json; +using Xunit; + +namespace Ardalis.SmartEnum.JsonNet.UnitTests +{ + public class SmartFlagEnumNameConverterTests + { + public class FlagTestClass + { + [JsonConverter(typeof(SmartFlagEnumNameConverter))] + public FlagTestEnums.FlagTestEnumInt16 Int16 { get; set; } + + [JsonConverter(typeof(SmartFlagEnumNameConverter))] + public FlagTestEnums.FlagTestEnumInt32 Int32 { get; set; } + + [JsonConverter(typeof(SmartFlagEnumNameConverter))] + public FlagTestEnums.FlagTestEnumDouble Double { get; set; } + } + + static readonly FlagTestClass TestInstance = new FlagTestClass + { + Int16 = FlagTestEnums.FlagTestEnumInt16.Instance, + Int32 = FlagTestEnums.FlagTestEnumInt32.Instance, + Double = FlagTestEnums.FlagTestEnumDouble.Instance + }; + + private const string JsonString = @"{ + ""Int16"": ""Instance"", + ""Int32"": ""Instance"", + ""Double"": ""Instance"" +}"; + + [Fact] + public void SerializesNames() + { + var json = JsonConvert.SerializeObject(TestInstance, Formatting.Indented); + + json.Should().Be(JsonString); + } + + [Fact] + public void DeserializesNames() + { + var obj = JsonConvert.DeserializeObject(JsonString); + + obj.Int16.Should().BeSameAs(FlagTestEnums.FlagTestEnumInt16.Instance); + obj.Int32.Should().BeSameAs(FlagTestEnums.FlagTestEnumInt32.Instance); + obj.Double.Should().BeSameAs(FlagTestEnums.FlagTestEnumDouble.Instance); + } + + [Fact] + public void DeserializesNullByDefault() + { + const string json = @"{}"; + + var obj = JsonConvert.DeserializeObject(json); + + obj.Int16.Should().BeNull(); + obj.Int32.Should().BeNull(); + obj.Double.Should().BeNull(); + } + + [Fact] + public void DeserializeThrowsWhenNotFound() + { + const string json = @"{ ""int32"": ""Not Found"" }"; + + Action act = () => JsonConvert.DeserializeObject(json); + + act.Should() + .Throw() + .WithMessage($@"Error converting value 'Not Found' to a smart flag enum.") + .WithInnerException() + .WithMessage($@"No {nameof(FlagTestEnums.FlagTestEnumInt32)} with Name ""Not Found"" found."); + } + + [Fact] + public void DeserializeThrowsWhenNull() + { + const string json = @"{ ""int16"": null }"; + + Action act = () => JsonConvert.DeserializeObject(json); + + act.Should() + .Throw() + .WithMessage($@"Unexpected token Null when parsing a smart flag enum."); + } + } +} diff --git a/test/SmartEnum.JsonNet.UnitTests/SmartFlagEnumValueConverterTests.cs b/test/SmartEnum.JsonNet.UnitTests/SmartFlagEnumValueConverterTests.cs new file mode 100644 index 00000000..3bd5f0af --- /dev/null +++ b/test/SmartEnum.JsonNet.UnitTests/SmartFlagEnumValueConverterTests.cs @@ -0,0 +1,128 @@ +using Ardalis.SmartEnum; +using Ardalis.SmartEnum.JsonNet; +using FluentAssertions; +using Newtonsoft.Json; +using System; +using Xunit; + +namespace Ardalis.SmartEnum.JsonNet.UnitTests +{ + public class SmartFlagEnumValueConverterTests + { + public class TestClass + { + [JsonConverter(typeof(SmartFlagEnumValueConverter))] + public FlagTestEnums.FlagTestEnumInt16 Int16 { get; set; } + + [JsonConverter(typeof(SmartFlagEnumValueConverter))] + public FlagTestEnums.FlagTestEnumInt32 Int32 { get; set; } + + [JsonConverter(typeof(SmartFlagEnumValueConverter))] + public FlagTestEnums.FlagTestEnumDouble Double { get; set; } + } + + static readonly TestClass TestInstance = new TestClass + { + Int16 = FlagTestEnums.FlagTestEnumInt16.Instance, + Int32 = FlagTestEnums.FlagTestEnumInt32.Instance2, + Double = FlagTestEnums.FlagTestEnumDouble.Instance + }; + + private const string JsonString = @"{ + ""Int16"": 1, + ""Int32"": 2, + ""Double"": 1.0 +}"; + [Fact] + public void SerializesValue() + { + var json = JsonConvert.SerializeObject(TestInstance, Formatting.Indented); + + json.Should().Be(JsonString); + } + + [Fact] + public void DeserializesValue() + { + var obj = JsonConvert.DeserializeObject(JsonString); + + Assert.Equal(obj.Int32, FlagTestEnums.FlagTestEnumInt32.Instance2); + Assert.Equal(obj.Int16, FlagTestEnums.FlagTestEnumInt16.Instance); + Assert.Equal(obj.Double, FlagTestEnums.FlagTestEnumDouble.Instance); + } + + [Fact] + public void DeserializesNullByDefault() + { + string json = @"{}"; + + var obj = JsonConvert.DeserializeObject(json); + + obj.Int16.Should().BeNull(); + obj.Int32.Should().BeNull(); + obj.Double.Should().BeNull(); + } + + [Fact] + public void DeserializeThrowsWhenNotFound() + { + string json = @"{ ""int32"": 42 }"; + + Action act = () => JsonConvert.DeserializeObject(json); + + act.Should() + .Throw() + .WithMessage($@"Error converting 42 to FlagTestEnumInt32.") + .WithInnerException() + .WithMessage($@"No {nameof(FlagTestEnums.FlagTestEnumInt32)} with Value 42 found."); + } + + [Fact] + public void DeserializeThrowsWhenImplicitFlagEnumValueIsGiven() + { + string json = @"{ ""int32"": 3 }"; + + Action act = () => JsonConvert.DeserializeObject(json); + + Assert.True(FlagTestEnums.FlagTestEnumInt32.Instance.Value == 1); + Assert.True(FlagTestEnums.FlagTestEnumInt32.Instance2.Value == 2); + + act.Should() + .Throw() + .WithMessage($@"Error converting 3 to FlagTestEnumInt32.") + .WithInnerException() + .WithMessage($@"No {nameof(FlagTestEnums.FlagTestEnumInt32)} with Value 3 found."); + } + + public static TheoryData NotValidData => + new TheoryData + { + { @"{ ""Int16"": true }", @"Error converting True to FlagTestEnumInt16." }, + { @"{ ""Int32"": true }", @"Error converting True to FlagTestEnumInt32." }, + { @"{ ""Double"": true }", @"Error converting True to FlagTestEnumDouble." }, + }; + + [Theory] + [MemberData(nameof(NotValidData))] + public void DeserializeThrowsWhenNotValid(string json, string message) + { + Action act = () => JsonConvert.DeserializeObject(json); + + act.Should() + .Throw() + .WithMessage(message); + } + + [Fact] + public void DeserializeThrowsWhenNull() + { + string json = @"{ ""int32"": null }"; + + Action act = () => JsonConvert.DeserializeObject(json); + + act.Should() + .Throw() + .WithMessage($@"Error converting Null to FlagTestEnumInt32."); + } + } +} diff --git a/test/SmartEnum.MessagePack.UnitTests/FlagTestEnums.cs b/test/SmartEnum.MessagePack.UnitTests/FlagTestEnums.cs new file mode 100644 index 00000000..64f23a3c --- /dev/null +++ b/test/SmartEnum.MessagePack.UnitTests/FlagTestEnums.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace Ardalis.SmartEnum.MessagePack.UnitTests +{ + public sealed class FlagTestEnumInt16 : SmartFlagEnum + { + public static readonly FlagTestEnumInt16 Instance = new FlagTestEnumInt16(nameof(Instance), 1); + + FlagTestEnumInt16(string name, short value) : base(name, value) { } + } + + public sealed class FlagTestEnumInt32 : SmartFlagEnum + { + public static readonly FlagTestEnumInt32 Instance = new FlagTestEnumInt32(nameof(Instance), 1); + public static readonly FlagTestEnumInt32 Instance2 = new FlagTestEnumInt32(nameof(Instance2), 2); + public static readonly FlagTestEnumInt32 Instance3 = new FlagTestEnumInt32(nameof(Instance3), 4); + + FlagTestEnumInt32(string name, int value) : base(name, value) { } + } + + public sealed class FlagTestEnumDouble : SmartFlagEnum + { + public static readonly FlagTestEnumDouble Instance = new FlagTestEnumDouble(nameof(Instance), 1.0); + + FlagTestEnumDouble(string name, double value) : base(name, value) { } + } +} diff --git a/test/SmartEnum.MessagePack.UnitTests/SmartFlagEnumNameFormatterTests.cs b/test/SmartEnum.MessagePack.UnitTests/SmartFlagEnumNameFormatterTests.cs new file mode 100644 index 00000000..cf89f362 --- /dev/null +++ b/test/SmartEnum.MessagePack.UnitTests/SmartFlagEnumNameFormatterTests.cs @@ -0,0 +1,73 @@ +using Ardalis.SmartEnum.MessagePack; +using Ardalis.SmartEnum.MessagePack.UnitTests; +using FluentAssertions; +using MessagePack; +using MessagePack.Resolvers; +using Xunit; + +namespace Ardalis.SmartEnum.MessagePack.UnitTests +{ + public class SmartFlagEnumNameFormatterTests + { + [MessagePackObject] + public class FlagTestClass + { + [Key(0)] + [MessagePackFormatter(typeof(SmartFlagEnumNameFormatter))] + public FlagTestEnumInt16 Int16 { get; set; } + + [Key(1)] + [MessagePackFormatter(typeof(SmartFlagEnumNameFormatter))] + public FlagTestEnumInt32 Int32 { get; set; } + + [Key(2)] + [MessagePackFormatter(typeof(SmartFlagEnumNameFormatter))] + public FlagTestEnumDouble Double { get; set; } + } + + static readonly FlagTestClass NullFlagTestInstance = new FlagTestClass + { + Int16 = null, + Int32 = null, + Double = null, + }; + + static readonly FlagTestClass FlagTestInstance = new FlagTestClass + { + Int16 = FlagTestEnumInt16.Instance, + Int32 = FlagTestEnumInt32.Instance, + Double = FlagTestEnumDouble.Instance, + }; + + static readonly string JsonString = @"[""Instance"",""Instance"",""Instance""]"; + + static SmartFlagEnumNameFormatterTests() + { + CompositeResolver.Register( + new SmartFlagEnumNameFormatter(), + new SmartFlagEnumNameFormatter(), + new SmartFlagEnumNameFormatter() + ); + } + + [Fact] + public void SerializesValue() + { + var message = MessagePackSerializer.Serialize(FlagTestInstance); + + MessagePackSerializer.ToJson(message).Should().Be(JsonString); + } + + [Fact] + public void DeserializesValue() + { + var message = MessagePackSerializer.Serialize(FlagTestInstance); + + var obj = MessagePackSerializer.Deserialize(message); + + obj.Int16.Should().BeSameAs(FlagTestEnumInt16.Instance); + obj.Int32.Should().BeSameAs(FlagTestEnumInt32.Instance); + obj.Double.Should().BeSameAs(FlagTestEnumDouble.Instance); + } + } +} diff --git a/test/SmartEnum.MessagePack.UnitTests/SmartFlagEnumValueFormatterTests.cs b/test/SmartEnum.MessagePack.UnitTests/SmartFlagEnumValueFormatterTests.cs new file mode 100644 index 00000000..d700ed5a --- /dev/null +++ b/test/SmartEnum.MessagePack.UnitTests/SmartFlagEnumValueFormatterTests.cs @@ -0,0 +1,76 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Ardalis.SmartEnum.MessagePack; +using Ardalis.SmartEnum.MessagePack.UnitTests; +using FluentAssertions; +using MessagePack; +using MessagePack.Resolvers; +using Xunit; + +namespace Ardalis.SmartEnum.MessagePack.UnitTests +{ + public class SmartFlagEnumValueFormatterTests + { + [MessagePackObject] + public class FlagTestClass + { + [Key(0)] + [MessagePackFormatter(typeof(SmartFlagEnumValueFormatter))] + public FlagTestEnumInt16 Int16 { get; set; } + + [Key(1)] + [MessagePackFormatter(typeof(SmartFlagEnumValueFormatter))] + public FlagTestEnumInt32 Int32 { get; set; } + + [Key(2)] + [MessagePackFormatter(typeof(SmartFlagEnumValueFormatter))] + public FlagTestEnumDouble Double { get; set; } + } + + static readonly FlagTestClass NullFlagTestInstance = new FlagTestClass() + { + Int16 = null, + Int32 = null, + Double = null, + }; + + static readonly FlagTestClass FlagTestInstance = new FlagTestClass() + { + Int16 = FlagTestEnumInt16.Instance, + Int32 = FlagTestEnumInt32.Instance2, + Double = FlagTestEnumDouble.Instance, + }; + + static readonly string JsonString = @"[1,2,1]"; + + static SmartFlagEnumValueFormatterTests() + { + CompositeResolver.Register( + new SmartFlagEnumValueFormatter(), + new SmartFlagEnumValueFormatter(), + new SmartFlagEnumValueFormatter() + ); + } + + [Fact] + public void SerializesValue() + { + var message = MessagePackSerializer.Serialize(FlagTestInstance); + + MessagePackSerializer.ToJson(message).Should().Be(JsonString); + } + + [Fact] + public void DeserializesValue() + { + var message = MessagePackSerializer.Serialize(FlagTestInstance); + + var obj = MessagePackSerializer.Deserialize(message); + + obj.Int16.Should().BeSameAs(FlagTestEnumInt16.Instance); + obj.Int32.Should().BeSameAs(FlagTestEnumInt32.Instance2); + obj.Double.Should().BeSameAs(FlagTestEnumDouble.Instance); + } + } +} diff --git a/test/SmartEnum.ProtoBufNet.UnitTests/FlagTestEnums.cs b/test/SmartEnum.ProtoBufNet.UnitTests/FlagTestEnums.cs new file mode 100644 index 00000000..d5c04906 --- /dev/null +++ b/test/SmartEnum.ProtoBufNet.UnitTests/FlagTestEnums.cs @@ -0,0 +1,30 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Ardalis.SmartEnum; + +namespace Ardalis.SmartEnum.ProtoBufNet.UnitTests +{ + public sealed class FlagTestEnumInt16 : SmartFlagEnum + { + public static readonly FlagTestEnumInt16 Instance = new FlagTestEnumInt16(nameof(Instance), 1); + + FlagTestEnumInt16(string name, short value) : base(name, value) { } + } + + public sealed class FlagTestEnumInt32 : SmartFlagEnum + { + public static readonly FlagTestEnumInt32 Instance = new FlagTestEnumInt32(nameof(Instance), 1); + public static readonly FlagTestEnumInt32 Instance2 = new FlagTestEnumInt32(nameof(Instance2), 2); + public static readonly FlagTestEnumInt32 Instance3 = new FlagTestEnumInt32(nameof(Instance3), 4); + + FlagTestEnumInt32(string name, int value) : base(name, value) { } + } + + public sealed class FlagTestEnumDouble : SmartFlagEnum + { + public static readonly FlagTestEnumDouble Instance = new FlagTestEnumDouble(nameof(Instance), 1.0); + + FlagTestEnumDouble(string name, double value) : base(name, value) { } + } +} diff --git a/test/SmartEnum.ProtoBufNet.UnitTests/SmartFlagEnumNameSurrogateTests.cs b/test/SmartEnum.ProtoBufNet.UnitTests/SmartFlagEnumNameSurrogateTests.cs new file mode 100644 index 00000000..5f67ff50 --- /dev/null +++ b/test/SmartEnum.ProtoBufNet.UnitTests/SmartFlagEnumNameSurrogateTests.cs @@ -0,0 +1,80 @@ +using FluentAssertions; +using ProtoBuf; +using ProtoBuf.Meta; +using Xunit; + +namespace Ardalis.SmartEnum.ProtoBufNet.UnitTests +{ + public class SmartFlagEnumNameSurrogateTests + { + [ProtoContract] + public class TestClass + { + [ProtoMember(1)] + public FlagTestEnumInt16 Int16 { get; set; } + + [ProtoMember(2)] + public FlagTestEnumInt32 Int32 { get; set; } + + [ProtoMember(3)] + public FlagTestEnumDouble Double { get; set; } + } + + private static readonly TestClass NullTestInstance = new TestClass + { + Int16 = null, + Int32 = null, + Double = null + }; + + private static readonly TestClass TestInstance = new TestClass + { + Int16 = FlagTestEnumInt16.Instance, + Int32 = FlagTestEnumInt32.Instance2, + Double = FlagTestEnumDouble.Instance + }; + + readonly string SchemaString = + @"syntax = ""proto2""; +package Ardalis.SmartEnum.ProtoBufNet; + +message SmartFlagEnumNameSurrogate_FlagTestEnumInt32_Int32 { + required string Name = 1; +} +"; + + readonly RuntimeTypeModel model; + + public SmartFlagEnumNameSurrogateTests() + { + model = RuntimeTypeModel.Create(); + model.Add(typeof(FlagTestEnumInt16), false).SetSurrogate(typeof(SmartFlagEnumNameSurrogate)); + model.Add(typeof(FlagTestEnumInt32), false).SetSurrogate(typeof(SmartFlagEnumNameSurrogate)); + model.Add(typeof(FlagTestEnumDouble), false).SetSurrogate(typeof(SmartFlagEnumNameSurrogate)); + } + + [Fact] + public void SchemaValidation() + { + var result = model.GetSchema(typeof(FlagTestEnumInt32)); + + result.Should().BeEquivalentTo(SchemaString); + } + + [Fact] + public void SerializesValue() + { + var result = Utils.DeepClone(TestInstance, model); + + result.Should().BeEquivalentTo(TestInstance); + } + + [Fact] + public void SerializesNull() + { + var result = Utils.DeepClone(NullTestInstance, model); + + result.Should().BeEquivalentTo(NullTestInstance); + } + } +} diff --git a/test/SmartEnum.ProtoBufNet.UnitTests/SmartFlagEnumValueSurrogateTests.cs b/test/SmartEnum.ProtoBufNet.UnitTests/SmartFlagEnumValueSurrogateTests.cs new file mode 100644 index 00000000..eb94274b --- /dev/null +++ b/test/SmartEnum.ProtoBufNet.UnitTests/SmartFlagEnumValueSurrogateTests.cs @@ -0,0 +1,84 @@ +using FluentAssertions; +using ProtoBuf; +using ProtoBuf.Meta; +using Ardalis.SmartEnum.ProtoBufNet.UnitTests; +using Xunit; + +namespace Ardalis.SmartEnum.ProtoBufNet.UnitTests +{ + public class SmartFlagEnumValueSurrogateTests + { + [ProtoContract] + public class TestClass + { + [ProtoMember(1)] + public FlagTestEnumInt16 Int16 { get; set; } + + [ProtoMember(2)] + public FlagTestEnumInt32 Int32 { get; set; } + + [ProtoMember(3)] + public FlagTestEnumDouble Double { get; set; } + } + + static readonly TestClass NullTestInstance = new TestClass + { + Int16 = null, + Int32 = null, + Double = null, + }; + + private static readonly TestClass TestInstance = new TestClass() + { + Int16 = FlagTestEnumInt16.Instance, + Int32 = FlagTestEnumInt32.Instance, + Double = FlagTestEnumDouble.Instance + }; + + readonly string SchemaString = + @"syntax = ""proto2""; +package Ardalis.SmartEnum.ProtoBufNet; + +message SmartFlagEnumValueSurrogate_FlagTestEnumInt32_Int32 { + required int32 Value = 1; +} +"; + + private readonly RuntimeTypeModel model; + + public SmartFlagEnumValueSurrogateTests() + { + model = RuntimeTypeModel.Create(); + model.Add(typeof(FlagTestEnumInt16), false) + .SetSurrogate(typeof(SmartFlagEnumValueSurrogate)); + model.Add(typeof(FlagTestEnumInt32), false) + .SetSurrogate(typeof(SmartFlagEnumValueSurrogate)); + model.Add(typeof(FlagTestEnumDouble), false) + .SetSurrogate(typeof(SmartFlagEnumValueSurrogate)); + } + + [Fact] + public void SchemaValidation() + { + var result = model.GetSchema(typeof(FlagTestEnumInt32)); + + result.Should().BeEquivalentTo(SchemaString); + } + + [Fact] + public void SerializesAndDeserializesValue() + { + var result = Utils.DeepClone(TestInstance, model); + + result.Should().BeEquivalentTo(TestInstance); + } + + [Fact] + public void SerializeNull() + { + var result = Utils.DeepClone(NullTestInstance, model); + + result.Should().BeEquivalentTo(NullTestInstance); + } + } +} \ No newline at end of file diff --git a/test/SmartEnum.ProtoBufNet.UnitTests/Utils.cs b/test/SmartEnum.ProtoBufNet.UnitTests/Utils.cs index 5d2026a0..f04a9cc2 100644 --- a/test/SmartEnum.ProtoBufNet.UnitTests/Utils.cs +++ b/test/SmartEnum.ProtoBufNet.UnitTests/Utils.cs @@ -1,3 +1,5 @@ +using System.Collections.Generic; + namespace Ardalis.SmartEnum.ProtoBufNet { using System.IO; diff --git a/test/SmartEnum.SystemTextJson.UnitTests/FlagTestEnums.cs b/test/SmartEnum.SystemTextJson.UnitTests/FlagTestEnums.cs new file mode 100644 index 00000000..07c06fc0 --- /dev/null +++ b/test/SmartEnum.SystemTextJson.UnitTests/FlagTestEnums.cs @@ -0,0 +1,25 @@ +namespace Ardalis.SmartEnum.SystemTextJson.UnitTests +{ + public sealed class FlagTestEnumInt16 : SmartFlagEnum + { + public static readonly FlagTestEnumInt16 Instance = new FlagTestEnumInt16(nameof(Instance), 1); + + FlagTestEnumInt16(string name, short value) : base(name, value) { } + } + + public sealed class FlagTestEnumInt32 : SmartFlagEnum + { + public static readonly FlagTestEnumInt32 Instance = new FlagTestEnumInt32(nameof(Instance), 1); + public static readonly FlagTestEnumInt32 Instance2 = new FlagTestEnumInt32(nameof(Instance2), 2); + public static readonly FlagTestEnumInt32 Instance3 = new FlagTestEnumInt32(nameof(Instance3), 4); + + FlagTestEnumInt32(string name, int value) : base(name, value) { } + } + + public sealed class FlagTestEnumDouble : SmartFlagEnum + { + public static readonly FlagTestEnumDouble Instance = new FlagTestEnumDouble(nameof(Instance), 1.0); + + FlagTestEnumDouble(string name, double value) : base(name, value) { } + } +} \ No newline at end of file diff --git a/test/SmartEnum.SystemTextJson.UnitTests/SmartFlagEnumNameConverterTests.cs b/test/SmartEnum.SystemTextJson.UnitTests/SmartFlagEnumNameConverterTests.cs new file mode 100644 index 00000000..b6f8a54c --- /dev/null +++ b/test/SmartEnum.SystemTextJson.UnitTests/SmartFlagEnumNameConverterTests.cs @@ -0,0 +1,91 @@ +namespace Ardalis.SmartEnum.SystemTextJson.UnitTests +{ + using FluentAssertions; + using System; + using System.Text.Json; + using System.Text.Json.Serialization; + using Xunit; + + public class SmartFlagEnumNameConverterTests + { + public class FlagTestClass + { + [JsonConverter(typeof(SmartFlagEnumNameConverter))] + public FlagTestEnumInt16 Int16 { get; set; } + + [JsonConverter(typeof(SmartFlagEnumNameConverter))] + public FlagTestEnumInt32 Int32 { get; set; } + + [JsonConverter(typeof(SmartFlagEnumNameConverter))] + public FlagTestEnumDouble Double { get; set; } + } + + static readonly FlagTestClass TestInstance = new FlagTestClass { + Int16 = FlagTestEnumInt16.Instance, + Int32 = FlagTestEnumInt32.Instance, + Double = FlagTestEnumDouble.Instance + }; + + private const string JsonString = @"{ + ""Int16"": ""Instance"", + ""Int32"": ""Instance"", + ""Double"": ""Instance"" +}"; + + [Fact] + public void SerializesNames() + { + var json = JsonSerializer.Serialize(TestInstance, new JsonSerializerOptions { WriteIndented = true }); + + json.Should().Be(JsonString); + } + + [Fact] + public void DeserializesNames() + { + var obj = JsonSerializer.Deserialize(JsonString); + + obj.Int16.Should().BeSameAs(FlagTestEnumInt16.Instance); + obj.Int32.Should().BeSameAs(FlagTestEnumInt32.Instance); + obj.Double.Should().BeSameAs(FlagTestEnumDouble.Instance); + } + + [Fact] + public void DeserializesNullByDefault() + { + const string json = @"{}"; + + var obj = JsonSerializer.Deserialize(json); + + obj.Int16.Should().BeNull(); + obj.Int32.Should().BeNull(); + obj.Double.Should().BeNull(); + } + + [Fact] + public void DeserializeThrowsWhenNotFound() + { + const string json = @"{ ""Int32"": ""Not Found"" }"; + + Action act = () => JsonSerializer.Deserialize(json); + + act.Should() + .Throw() + .WithMessage($@"Error converting value 'Not Found' to a smart flag enum.") + .WithInnerException() + .WithMessage($@"No {nameof(FlagTestEnumInt32)} with Name ""Not Found"" found."); + } + + [Fact] + public void DeserializeThrowsWhenNull() + { + const string json = @"{ ""Int16"": null }"; + + Action act = () => JsonSerializer.Deserialize(json); + + act.Should() + .Throw() + .WithMessage($@"Unexpected token Null when parsing a smart flag enum."); + } + } +} \ No newline at end of file diff --git a/test/SmartEnum.SystemTextJson.UnitTests/SmartFlagEnumValueConverterTests.cs b/test/SmartEnum.SystemTextJson.UnitTests/SmartFlagEnumValueConverterTests.cs new file mode 100644 index 00000000..f209eca1 --- /dev/null +++ b/test/SmartEnum.SystemTextJson.UnitTests/SmartFlagEnumValueConverterTests.cs @@ -0,0 +1,128 @@ +using Ardalis.SmartEnum; +using FluentAssertions; +using System; +using System.Text.Json; +using System.Text.Json.Serialization; +using Xunit; + +namespace Ardalis.SmartEnum.SystemTextJson.UnitTests +{ + public class SmartFlagEnumValueConverterTests + { + public class TestClass + { + [JsonConverter(typeof(SmartFlagEnumValueConverter))] + public FlagTestEnumInt16 Int16 { get; set; } + + [JsonConverter(typeof(SmartFlagEnumValueConverter))] + public FlagTestEnumInt32 Int32 { get; set; } + + [JsonConverter(typeof(SmartFlagEnumValueConverter))] + public FlagTestEnumDouble Double { get; set; } + } + + static readonly TestClass TestInstance = new TestClass + { + Int16 = FlagTestEnumInt16.Instance, + Int32 = FlagTestEnumInt32.Instance2, + Double = FlagTestEnumDouble.Instance + }; + + private const string JsonString = @"{ + ""Int16"": 1, + ""Int32"": 2, + ""Double"": 1 +}"; + [Fact] + public void SerializesValue() + { + var json = JsonSerializer.Serialize(TestInstance, new JsonSerializerOptions { WriteIndented = true }); + + json.Should().Be(JsonString); + } + + [Fact] + public void DeserializesValue() + { + var obj = JsonSerializer.Deserialize(JsonString); + + Assert.Equal(obj.Int32, FlagTestEnumInt32.Instance2); + Assert.Equal(obj.Int16, FlagTestEnumInt16.Instance); + Assert.Equal(obj.Double, FlagTestEnumDouble.Instance); + } + + [Fact] + public void DeserializesNullByDefault() + { + string json = @"{}"; + + var obj = JsonSerializer.Deserialize(json); + + obj.Int16.Should().BeNull(); + obj.Int32.Should().BeNull(); + obj.Double.Should().BeNull(); + } + + [Fact] + public void DeserializeThrowsWhenNotFound() + { + string json = @"{ ""Int32"": 42 }"; + + Action act = () => JsonSerializer.Deserialize(json); + + act.Should() + .Throw() + .WithMessage($@"Error converting value '42' to a smart flag enum.") + .WithInnerException() + .WithMessage($@"No {nameof(FlagTestEnumInt32)} with Value 42 found."); + } + + [Fact] + public void DeserializeThrowsWhenImplicitFlagEnumValueIsGiven() + { + string json = @"{ ""Int32"": 3 }"; + + Action act = () => JsonSerializer.Deserialize(json); + + Assert.True(FlagTestEnumInt32.Instance.Value == 1); + Assert.True(FlagTestEnumInt32.Instance2.Value == 2); + + act.Should() + .Throw() + .WithMessage($@"Error converting value '3' to a smart flag enum.") + .WithInnerException() + .WithMessage($@"No {nameof(FlagTestEnumInt32)} with Value 3 found."); + } + + public static TheoryData NotValidData => + new TheoryData + { + { @"{ ""Int16"": true }", @"Error converting token 'True' to a smart flag enum." }, + { @"{ ""Int32"": true }", @"Error converting token 'True' to a smart flag enum." }, + { @"{ ""Double"": true }", @"Error converting token 'True' to a smart flag enum." }, + }; + + [Theory] + [MemberData(nameof(NotValidData))] + public void DeserializeThrowsWhenNotValid(string json, string message) + { + Action act = () => JsonSerializer.Deserialize(json); + + act.Should() + .Throw() + .WithMessage(message); + } + + [Fact] + public void DeserializeThrowsWhenNull() + { + string json = @"{ ""Int32"": null }"; + + Action act = () => JsonSerializer.Deserialize(json); + + act.Should() + .Throw() + .WithMessage($@"Error converting value 'Null' to a smart flag enum."); + } + } +} diff --git a/test/SmartEnum.Utf8Json.UnitTests/FlagTestEnums.cs b/test/SmartEnum.Utf8Json.UnitTests/FlagTestEnums.cs new file mode 100644 index 00000000..12363ffd --- /dev/null +++ b/test/SmartEnum.Utf8Json.UnitTests/FlagTestEnums.cs @@ -0,0 +1,26 @@ +namespace Ardalis.SmartEnum.Utf8Json.UnitTests +{ + public sealed class FlagTestEnumInt16 : SmartFlagEnum + { + public static readonly FlagTestEnumInt16 Instance = new FlagTestEnumInt16(nameof(Instance), 1); + + FlagTestEnumInt16(string name, short value) : base(name, value) { } + } + + public sealed class FlagTestEnumInt32 : SmartFlagEnum + { + public static readonly FlagTestEnumInt32 Instance = new FlagTestEnumInt32(nameof(Instance), 1); + public static readonly FlagTestEnumInt32 Instance2 = new FlagTestEnumInt32(nameof(Instance2), 2); + public static readonly FlagTestEnumInt32 Instance3 = new FlagTestEnumInt32(nameof(Instance3), 4); + + FlagTestEnumInt32(string name, int value) : base(name, value) { } + } + + public sealed class FlagTestEnumDouble : SmartFlagEnum + { + public static readonly FlagTestEnumDouble Instance = new FlagTestEnumDouble(nameof(Instance), 1.0); + + FlagTestEnumDouble(string name, double value) : base(name, value) { } + } + +} \ No newline at end of file diff --git a/test/SmartEnum.Utf8Json.UnitTests/SmartFlagEnumNameFormatterTests.cs b/test/SmartEnum.Utf8Json.UnitTests/SmartFlagEnumNameFormatterTests.cs new file mode 100644 index 00000000..87ee4190 --- /dev/null +++ b/test/SmartEnum.Utf8Json.UnitTests/SmartFlagEnumNameFormatterTests.cs @@ -0,0 +1,96 @@ +using System; +using System.Text; +using FluentAssertions; +using Utf8Json; +using Utf8Json.Resolvers; +using Xunit; + +namespace Ardalis.SmartEnum.Utf8Json.UnitTests +{ + public class SmartFlagEnumNameFormatterTests + { + public class TestClass + { + [JsonFormatter(typeof(SmartFlagEnumNameFormatter))] + public FlagTestEnumInt16 Int16 { get; set; } + + [JsonFormatter(typeof(SmartFlagEnumNameFormatter))] + public FlagTestEnumInt32 Int32 { get; set; } + + [JsonFormatter(typeof(SmartFlagEnumNameFormatter))] + public FlagTestEnumDouble Double { get; set; } + } + + private static readonly TestClass TestInstance = new TestClass() + { + Int16 = FlagTestEnumInt16.Instance, + Int32 = FlagTestEnumInt32.Instance2, + Double = FlagTestEnumDouble.Instance + }; + + static readonly string JsonString = @"{""Int16"":""Instance"",""Int32"":""Instance2"",""Double"":""Instance""}"; + + static SmartFlagEnumNameFormatterTests() + { + CompositeResolver.Register( + new SmartFlagEnumNameFormatter(), + new SmartFlagEnumNameFormatter(), + new SmartFlagEnumNameFormatter() + ); + } + + [Fact] + public void SerializeNames() + { + var json = JsonSerializer.Serialize(TestInstance); + + Encoding.UTF8.GetString(json).Should().Be(JsonString); + } + + [Fact] + public void DeserializeNames() + { + var obj = JsonSerializer.Deserialize(JsonString); + + obj.Int16.Should().BeSameAs(FlagTestEnumInt16.Instance); + obj.Int32.Should().BeSameAs(FlagTestEnumInt32.Instance2); + obj.Double.Should().BeSameAs(FlagTestEnumDouble.Instance); + } + + [Fact] + public void DeserializesNullByDefault() + { + string json = @"{}"; + + var obj = JsonSerializer.Deserialize(json); + + obj.Int16.Should().BeNull(); + obj.Int32.Should().BeNull(); + obj.Double.Should().BeNull(); + } + + [Fact] + public void DeserializeNull() + { + string json = @"{""Int16"": null, ""Int32"": null, ""Double"": null }"; + + var obj = JsonSerializer.Deserialize(json); + + obj.Int16.Should().BeNull(); + obj.Int32.Should().BeNull(); + obj.Double.Should().BeNull(); + } + + [Fact] + public void DeserializeThrowsWhenNotFound() + { + string json = @"{""Int32"": ""Not Found""}"; + + Action act = () => JsonSerializer.Deserialize(json); + + act.Should() + .Throw() + .WithMessage($@"No {nameof(FlagTestEnumInt32)} with Name ""Not Found"" found."); + } + } +} \ No newline at end of file diff --git a/test/SmartEnum.Utf8Json.UnitTests/SmartFlagEnumValueFormatterTests.cs b/test/SmartEnum.Utf8Json.UnitTests/SmartFlagEnumValueFormatterTests.cs new file mode 100644 index 00000000..ba477bee --- /dev/null +++ b/test/SmartEnum.Utf8Json.UnitTests/SmartFlagEnumValueFormatterTests.cs @@ -0,0 +1,116 @@ +using System; +using System.Text; +using FluentAssertions; +using Microsoft.VisualStudio.TestPlatform.CommunicationUtilities; +using Utf8Json; +using Utf8Json.Resolvers; +using Xunit; + +namespace Ardalis.SmartEnum.Utf8Json.UnitTests +{ + public class SmartFlagEnumValueFormatterTests + { + public class TestClass + { + [JsonFormatter(typeof(SmartFlagEnumValueFormatter))] + public FlagTestEnumInt16 Int16 { get; set; } + + [JsonFormatter(typeof(SmartFlagEnumValueFormatter))] + public FlagTestEnumInt32 Int32 { get; set; } + + [JsonFormatter(typeof(SmartFlagEnumValueFormatter))] + public FlagTestEnumDouble Double { get; set; } + } + + private static readonly TestClass TestInstance = new TestClass() + { + Int16 = FlagTestEnumInt16.Instance, + Int32 = FlagTestEnumInt32.Instance, + Double = FlagTestEnumDouble.Instance + }; + + static readonly string JsonString = @"{""Int16"":1,""Int32"":1,""Double"":1}"; + + static SmartFlagEnumValueFormatterTests() + { + CompositeResolver.Register( + new SmartFlagEnumValueFormatter(), + new SmartFlagEnumValueFormatter(), + new SmartFlagEnumValueFormatter() + ); + } + + [Fact] + public void SerializeValues() + { + var json = JsonSerializer.Serialize(TestInstance); + + Encoding.UTF8.GetString(json).Should().Be(JsonString); + } + + [Fact] + public void DeserializeValues() + { + var obj = JsonSerializer.Deserialize(JsonString); + + obj.Int16.Should().BeSameAs(FlagTestEnumInt16.Instance); + obj.Int32.Should().BeSameAs(FlagTestEnumInt32.Instance); + obj.Double.Should().BeSameAs(FlagTestEnumDouble.Instance); + } + + [Fact] + public void DeserializesNullByDefault() + { + string json = @"{}"; + + var obj = JsonSerializer.Deserialize(json); + + obj.Int16.Should().BeNull(); + obj.Int32.Should().BeNull(); + obj.Double.Should().BeNull(); + } + + [Fact] + public void DeserializesNull() + { + string json = @"{""Int16"": null, ""Int32"": null, ""Double"": null }"; + + var obj = JsonSerializer.Deserialize(json); + + obj.Int16.Should().BeNull(); + obj.Int32.Should().BeNull(); + obj.Double.Should().BeNull(); + } + + [Fact] + public void DeserializeThrowsWhenNotFound() + { + string json = @"{""Int32"": 3}"; + + Action act = () => JsonSerializer.Deserialize(json); + + act.Should() + .Throw() + .WithMessage($@"No {nameof(FlagTestEnumInt32)} with Value 3 found."); + } + + public static TheoryData InvalidData => + new TheoryData() + { + {@"{ ""Int16"": true }", @"expected:'Number Token', actual:'true', at offset:11"}, + {@"{ ""Int32"": true }", @"expected:'Number Token', actual:'true', at offset:11"}, + {@"{ ""Double"": true }", @"expected:'Number Token', actual:'true', at offset:12"}, + }; + + [Theory] + [MemberData(nameof(InvalidData))] + public void DeserializeThrowsWhenNotValid(string json, string message) + { + Action act = () => JsonSerializer.Deserialize(json); + + act.Should() + .Throw() + .WithMessage(message); + } + } +} \ No newline at end of file diff --git a/test/SmartFlagEnum.UnitTests/AllowNegativeInputAttributeTests.cs b/test/SmartFlagEnum.UnitTests/AllowNegativeInputAttributeTests.cs new file mode 100644 index 00000000..e90509c7 --- /dev/null +++ b/test/SmartFlagEnum.UnitTests/AllowNegativeInputAttributeTests.cs @@ -0,0 +1,51 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Ardalis.SmartEnum; +using Ardalis.SmartEnum.Exceptions; +using Xunit; + +namespace Ardalis.SmartFlagEnum.UnitTests +{ + [AllowNegativeInputValues] + public class AllowNegativeAttributeTest : SmartFlagEnum + { + public static readonly AllowNegativeAttributeTest One = new AllowNegativeAttributeTest(nameof(One), 1); + public static readonly AllowNegativeAttributeTest Two = new AllowNegativeAttributeTest(nameof(Two), 2); + + public AllowNegativeAttributeTest(string name, int value) : base(name, value) + { + } + } + + public class AllowNegativeAttributeTestNoAttribute : SmartFlagEnum + { + public static readonly AllowNegativeAttributeTestNoAttribute One = new AllowNegativeAttributeTestNoAttribute(nameof(One), 1); + public static readonly AllowNegativeAttributeTestNoAttribute Two = new AllowNegativeAttributeTestNoAttribute(nameof(Two), 2); + + public AllowNegativeAttributeTestNoAttribute(string name, int value) : base(name, value) + { + } + } + + public class AllowNegativeInputAttributeTests + { + [Fact] + public void ThrowsExceptionGivenNegativeNumber() + { + Assert.Throws(() => AllowNegativeAttributeTestNoAttribute.FromValue(-3)); + } + + [Fact] + public void DoesNothingGivenNegativeNumber() + { + AllowNegativeAttributeTest.FromValue(-3); + } + + [Fact] + public void DoesNothingGivenNegativeOneInput() + { + AllowNegativeAttributeTestNoAttribute.FromValue(-1); + } + } +} diff --git a/test/SmartFlagEnum.UnitTests/AllowUnsafeFlagEnumAttributeTests.cs b/test/SmartFlagEnum.UnitTests/AllowUnsafeFlagEnumAttributeTests.cs new file mode 100644 index 00000000..78a45bac --- /dev/null +++ b/test/SmartFlagEnum.UnitTests/AllowUnsafeFlagEnumAttributeTests.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Ardalis.SmartEnum; +using Ardalis.SmartEnum.Exceptions; +using Xunit; +using Xunit.Sdk; + +namespace Ardalis.SmartFlagEnum.UnitTests +{ + public class SmartTestAttributeSafeContainsPowOfTwo : SmartFlagEnum + { + public static readonly SmartTestAttributeSafeContainsPowOfTwo Cheque = new SmartTestAttributeSafeContainsPowOfTwo(nameof(Cheque), 1); + public static readonly SmartTestAttributeSafeContainsPowOfTwo Card = new SmartTestAttributeSafeContainsPowOfTwo(nameof(Card), 2); + public static readonly SmartTestAttributeSafeContainsPowOfTwo ChequeAndCard = new SmartTestAttributeSafeContainsPowOfTwo(nameof(ChequeAndCard), 3); + public static readonly SmartTestAttributeSafeContainsPowOfTwo Cash = new SmartTestAttributeSafeContainsPowOfTwo(nameof(Cash), 4); + public static readonly SmartTestAttributeSafeContainsPowOfTwo Paypal = new SmartTestAttributeSafeContainsPowOfTwo(nameof(Paypal), 8); + public static readonly SmartTestAttributeSafeContainsPowOfTwo Explicit = new SmartTestAttributeSafeContainsPowOfTwo(nameof(Explicit), 9); + public static readonly SmartTestAttributeSafeContainsPowOfTwo ExplicitAboveMax = new SmartTestAttributeSafeContainsPowOfTwo(nameof(ExplicitAboveMax), 17); + + public SmartTestAttributeSafeContainsPowOfTwo(string name, int value) : base(name, value) + { + } + } + + public class SmartTestAttributeSafeDoesNotContainConsecutivePowOfTwoValues : SmartFlagEnum + { + public static readonly SmartTestAttributeSafeDoesNotContainConsecutivePowOfTwoValues One = new SmartTestAttributeSafeDoesNotContainConsecutivePowOfTwoValues(nameof(One), 1); + //missing Two + public static readonly SmartTestAttributeSafeDoesNotContainConsecutivePowOfTwoValues Three = new SmartTestAttributeSafeDoesNotContainConsecutivePowOfTwoValues(nameof(Three), 3); + public static readonly SmartTestAttributeSafeDoesNotContainConsecutivePowOfTwoValues Four = new SmartTestAttributeSafeDoesNotContainConsecutivePowOfTwoValues(nameof(Four), 4); + + public SmartTestAttributeSafeDoesNotContainConsecutivePowOfTwoValues(string name, int value) : base(name, value) + { + } + } + + [AllowUnsafeFlagEnumValues] + public class SmartTestAttributeUnsafeDoesNotContainPowOfTwoValues : SmartFlagEnum + { + public static readonly SmartTestAttributeUnsafeDoesNotContainPowOfTwoValues One = new SmartTestAttributeUnsafeDoesNotContainPowOfTwoValues(nameof(One), 1); + //Missing Two + public static readonly SmartTestAttributeUnsafeDoesNotContainPowOfTwoValues Three = new SmartTestAttributeUnsafeDoesNotContainPowOfTwoValues(nameof(Three), 3); + public static readonly SmartTestAttributeUnsafeDoesNotContainPowOfTwoValues Four = new SmartTestAttributeUnsafeDoesNotContainPowOfTwoValues(nameof(Four), 4); + + public SmartTestAttributeUnsafeDoesNotContainPowOfTwoValues(string name, int value) : base(name, value) + { + } + } + + public class SmartTestAttributePowerOfTwoValuesStartAboveOne : SmartFlagEnum + { + public static readonly SmartTestAttributePowerOfTwoValuesStartAboveOne Four = new SmartTestAttributePowerOfTwoValuesStartAboveOne(nameof(Four), 4); + public static readonly SmartTestAttributePowerOfTwoValuesStartAboveOne Eight = new SmartTestAttributePowerOfTwoValuesStartAboveOne(nameof(Eight), 8); + + public SmartTestAttributePowerOfTwoValuesStartAboveOne(string name, int value) : base(name, value) + { + } + } + + public class SmartTestAttributePowerOfTwoValuesStartAboveOneNotConsecutivePowOfTwo : SmartFlagEnum + { + public static readonly SmartTestAttributePowerOfTwoValuesStartAboveOneNotConsecutivePowOfTwo Four = new SmartTestAttributePowerOfTwoValuesStartAboveOneNotConsecutivePowOfTwo(nameof(Four), 4); + //Missing 8 + public static readonly SmartTestAttributePowerOfTwoValuesStartAboveOneNotConsecutivePowOfTwo Sixteen = new SmartTestAttributePowerOfTwoValuesStartAboveOneNotConsecutivePowOfTwo(nameof(Sixteen), 16); + + public SmartTestAttributePowerOfTwoValuesStartAboveOneNotConsecutivePowOfTwo(string name, int value) : base(name, value) + { + } + } + + public class SmartTestAttributeSinglePowerOfTwoValue : SmartFlagEnum + { + public static readonly SmartTestAttributeSinglePowerOfTwoValue Four = new SmartTestAttributeSinglePowerOfTwoValue(nameof(Four), 4); + public static readonly SmartTestAttributeSinglePowerOfTwoValue Five = new SmartTestAttributeSinglePowerOfTwoValue(nameof(Five), 5); + public static readonly SmartTestAttributeSinglePowerOfTwoValue Six = new SmartTestAttributeSinglePowerOfTwoValue(nameof(Six), 6); + + public SmartTestAttributeSinglePowerOfTwoValue(string name, int value) : base(name, value) + { + } + } + + public class AllowUnsafeFlagEnumAttributeTests + { + [Fact] + public void DoesNothingIfEnumContainsValidPowerOfTwoValues() + { + SmartTestAttributeSafeContainsPowOfTwo.FromValue(2); + } + + [Fact] + public void ThrowsExceptionIfEnumDoesNotContainPowerOfTwoValues() + { + Assert.Throws(() => + SmartTestAttributeSafeDoesNotContainConsecutivePowOfTwoValues.FromValue(1)); + } + + [Fact] + public void DoesNothingIfEnumDoesNotContainPowerOfTwoValuesAndUnsafeAttributeIsApplied() + { + SmartTestAttributeUnsafeDoesNotContainPowOfTwoValues.FromValue(1); + } + + [Fact] + public void DoesNothingIfEnumContainsPowerOfTwoValuesStartingAtFour() + { + SmartTestAttributePowerOfTwoValuesStartAboveOne.FromValue(4); + } + + [Fact] + public void DoesNothingIfEnumContainsSinglePowerOfTwoValue() + { + SmartTestAttributeSinglePowerOfTwoValue.FromValue(4); + } + + [Fact] + public void ThrowsExceptionIfEnumDoesNotContainConsecutivePowerOfTwoValuesStartingAtFour() + { + Assert.Throws(() => + SmartTestAttributePowerOfTwoValuesStartAboveOneNotConsecutivePowOfTwo.FromValue(4)); + } + } +} diff --git a/test/SmartFlagEnum.UnitTests/SmartFlagEnum.UnitTests.csproj b/test/SmartFlagEnum.UnitTests/SmartFlagEnum.UnitTests.csproj new file mode 100644 index 00000000..9cc4d3e4 --- /dev/null +++ b/test/SmartFlagEnum.UnitTests/SmartFlagEnum.UnitTests.csproj @@ -0,0 +1,20 @@ + + + + netcoreapp3.1 + + false + + + + + + + + + + + + + + diff --git a/test/SmartFlagEnum.UnitTests/SmartFlagEnumCompareTo.cs b/test/SmartFlagEnum.UnitTests/SmartFlagEnumCompareTo.cs new file mode 100644 index 00000000..cea6e1c1 --- /dev/null +++ b/test/SmartFlagEnum.UnitTests/SmartFlagEnumCompareTo.cs @@ -0,0 +1,77 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Ardalis.SmartEnum.UnitTests; +using FluentAssertions; +using Xunit; + +namespace Ardalis.SmartFlagEnum.UnitTests +{ + public class SmartFlagEnumCompareTo + { + public static TheoryData CompareToData => + new TheoryData + { + { SmartFlagTestEnum.Two, SmartFlagTestEnum.One, 1 }, + { SmartFlagTestEnum.Two, SmartFlagTestEnum.Two, 0 }, + { SmartFlagTestEnum.Two, SmartFlagTestEnum.Three, -1 }, + }; + + [Theory] + [MemberData(nameof(CompareToData))] + public void CompareToReturnsExpected(SmartFlagTestEnum left, SmartFlagTestEnum right, int expected) + { + var result = left.CompareTo(right); + + result.Should().Be(expected); + } + + public static TheoryData ComparisonOperatorsData => + new TheoryData + { + { SmartFlagTestEnum.Two, SmartFlagTestEnum.One, false, false, true }, + { SmartFlagTestEnum.Two, SmartFlagTestEnum.Two, false, true, false }, + { SmartFlagTestEnum.Two, SmartFlagTestEnum.Three, true, false, false }, + }; + +#pragma warning disable xUnit1026 // Theory method does not use parameter + + [Theory] + [MemberData(nameof(ComparisonOperatorsData))] + public void LessThanReturnsExpected(SmartFlagTestEnum left, SmartFlagTestEnum right, bool lessThan, bool equalTo, bool greaterThan) + { + var result = left < right; + + result.Should().Be(lessThan); + } + + [Theory] + [MemberData(nameof(ComparisonOperatorsData))] + public void LessThanOrEqualReturnsExpected(SmartFlagTestEnum left, SmartFlagTestEnum right, bool lessThan, bool equalTo, bool greaterThan) + { + var result = left <= right; + + result.Should().Be(lessThan || equalTo); + } + + [Theory] + [MemberData(nameof(ComparisonOperatorsData))] + public void GreaterThanReturnsExpected(SmartFlagTestEnum left, SmartFlagTestEnum right, bool lessThan, bool equalTo, bool greaterThan) + { + var result = left > right; + + result.Should().Be(greaterThan); + } + + [Theory] + [MemberData(nameof(ComparisonOperatorsData))] + public void GreaterThanOrEqualReturnsExpected(SmartFlagTestEnum left, SmartFlagTestEnum right, bool lessThan, bool equalTo, bool greaterThan) + { + var result = left >= right; + + result.Should().Be(greaterThan || equalTo); + } + +#pragma warning restore xUnit1026 // Theory method does not use parameter + } +} diff --git a/test/SmartFlagEnum.UnitTests/SmartFlagEnumEquals.cs b/test/SmartFlagEnum.UnitTests/SmartFlagEnumEquals.cs new file mode 100644 index 00000000..7b28ea06 --- /dev/null +++ b/test/SmartFlagEnum.UnitTests/SmartFlagEnumEquals.cs @@ -0,0 +1,84 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Ardalis.SmartEnum; +using Ardalis.SmartEnum.UnitTests; +using FluentAssertions; +using Xunit; + +namespace Ardalis.SmartFlagEnum.UnitTests +{ + public class SmartFlagEnumEquals + { + private class TestFlagEnum2 : SmartFlagEnum + { + public static TestFlagEnum2 One = new TestFlagEnum2(nameof(One), 1); + protected TestFlagEnum2(string name, int value) : base(name, value) + { + } + } + + public static TheoryData EqualsObjectData => + new TheoryData + { + { SmartFlagTestEnum.One, null, false }, + { SmartFlagTestEnum.One, SmartFlagTestEnum.One, true }, + { SmartFlagTestEnum.One, TestFlagEnum2.One, false }, + { SmartFlagTestEnum.One, SmartFlagTestEnum.Two, false }, + }; + + [Theory] + [MemberData(nameof(EqualsObjectData))] + public void EqualsObjectReturnsExpected(SmartFlagTestEnum left, object right, bool expected) + { + var result = left.Equals(right); + + result.Should().Be(expected); + } + + public static TheoryData EqualsSmartEnumData => + new TheoryData + { + { SmartFlagTestEnum.One, null, false }, + { SmartFlagTestEnum.One, SmartFlagTestEnum.One, true }, + { SmartFlagTestEnum.One, SmartFlagTestEnum.Two, false }, + }; + + [Theory] + [MemberData(nameof(EqualsSmartEnumData))] + public void EqualsSmartEnumReturnsExpected(SmartFlagTestEnum left, object right, bool expected) + { + var result = left.Equals(right); + + result.Should().Be(expected); + } + + public static TheoryData EqualOperatorData => + new TheoryData + { + { null, null, true }, + { null, SmartFlagTestEnum.One, false }, + { SmartFlagTestEnum.One, null, false }, + { SmartFlagTestEnum.One, SmartFlagTestEnum.One, true }, + { SmartFlagTestEnum.One, SmartFlagTestEnum.Two, false }, + }; + + [Theory] + [MemberData(nameof(EqualOperatorData))] + public void EqualOperatorReturnsExpected(SmartFlagTestEnum left, SmartFlagTestEnum right, bool expected) + { + var result = left == right; + + result.Should().Be(expected); + } + + [Theory] + [MemberData(nameof(EqualOperatorData))] + public void NotEqualOperatorReturnsExpected(SmartFlagTestEnum left, SmartFlagTestEnum right, bool expected) + { + var result = left != right; + + result.Should().Be(!expected); + } + } +} diff --git a/test/SmartFlagEnum.UnitTests/SmartFlagEnumExplicitConversion.cs b/test/SmartFlagEnum.UnitTests/SmartFlagEnumExplicitConversion.cs new file mode 100644 index 00000000..ac6b8fe1 --- /dev/null +++ b/test/SmartFlagEnum.UnitTests/SmartFlagEnumExplicitConversion.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Ardalis.SmartEnum.UnitTests; +using FluentAssertions; +using Xunit; + +namespace Ardalis.SmartFlagEnum.UnitTests +{ + public class SmartFlagEnumExplicitConversion + { + [Fact] + public void ReturnsFirstEnumResultFromGivenValue() + { + int value = 3; + + var result = (SmartFlagTestEnum) value; + + result.Should().BeSameAs(SmartFlagTestEnum.One); + } + + [Fact] + public void ReturnsFirstEnumResultFromGivenValueV2() + { + int value = 10; + + var result = (SmartFlagTestEnum) value; + + result.Should().BeSameAs(SmartFlagTestEnum.Two); + } + + [Fact] + public void ReturnsFirstEnumResultFromGivenNullableValue() + { + int? value = 3; + + var result = (SmartFlagTestEnum) value; + + result.Should().BeSameAs(SmartFlagTestEnum.One); + } + + [Fact] + public void ReturnsEnumFromGivenNullableValueAsNull() + { + int? value = null; + + var result = (SmartFlagTestEnum) value; + + result.Should().BeNull(); + } + } +} diff --git a/test/SmartFlagEnum.UnitTests/SmartFlagEnumExtensionTests.cs b/test/SmartFlagEnum.UnitTests/SmartFlagEnumExtensionTests.cs new file mode 100644 index 00000000..caff7348 --- /dev/null +++ b/test/SmartFlagEnum.UnitTests/SmartFlagEnumExtensionTests.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Ardalis.SmartEnum; +using Ardalis.SmartEnum.UnitTests; +using FluentAssertions; +using Xunit; + +namespace Ardalis.SmartFlagEnum.UnitTests +{ + public class SmartFlagEnumExtensionTests + { + public abstract class AbstractEnum : SmartFlagEnum + { + protected AbstractEnum(string name, int value) : base(name, value) { } + } + + public class GenericEnum : + SmartFlagEnum, T> + where T : struct, IEquatable, IComparable + { + protected GenericEnum(string name, T value) : base(name, value) { } + } + + public static TheoryData IsSmartEnumData => + new TheoryData + { + { typeof(int), false, null }, + { typeof(AbstractEnum), false, null }, + { typeof(GenericEnum<>), false, null }, + { typeof(SmartFlagTestEnum), true, new Type[] { typeof(SmartFlagTestEnum), typeof(int) }}, + }; + + [Theory] + [MemberData(nameof(IsSmartEnumData))] +#pragma warning disable xUnit1026 // Theory methods should use all of their parameters + public void IsSmartEnumReturnsExpected(Type type, bool expectedResult, Type[] _) +#pragma warning restore xUnit1026 // Theory methods should use all of their parameters + { + var result = type.IsSmartFlagEnum(); + + result.Should().Be(expectedResult); + } + + [Theory] + [MemberData(nameof(IsSmartEnumData))] + public void IsSmartEnum2ReturnsExpected(Type type, bool expectedResult, Type[] expectedGenericArguments) + { + var result = type.IsSmartFlagEnum(out var genericArguments); + + result.Should().Be(expectedResult); + if (result) + { + genericArguments.Should().Equal(expectedGenericArguments); + } + } + } +} diff --git a/test/SmartFlagEnum.UnitTests/SmartFlagEnumFromName.cs b/test/SmartFlagEnum.UnitTests/SmartFlagEnumFromName.cs new file mode 100644 index 00000000..343607ce --- /dev/null +++ b/test/SmartFlagEnum.UnitTests/SmartFlagEnumFromName.cs @@ -0,0 +1,91 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Ardalis.SmartEnum; +using Ardalis.SmartEnum.UnitTests; +using FluentAssertions; +using Xunit; + +namespace Ardalis.SmartFlagEnum.UnitTests +{ + public class SmartFlagEnumFromName + { + [Fact] + public void IgnoreCaseReturnsIEnumerableWithSameValues() + { + var result = SmartFlagTestEnum.FromName("One, Two", true).ToList(); + + Assert.Equal("One", result[0].Name); + Assert.Equal("Two", result[1].Name); + } + + [Fact] + public void ThrowsExceptionGivenOutOfRangeValue() + { + Assert.Throws(() => SmartFlagTestEnum.FromName("Invalid, Value")); + } + + [Fact] + public void CaseSensitiveReturnsIEnumerableWithSameValues() + { + var result = SmartFlagTestEnum.FromName("One, Two", false).ToList(); + + Assert.Equal("One", result[0].Name); + Assert.Equal("Two", result[1].Name); + } + + [Fact] + public void ReturnsIEnumerableWithSameValues() + { + var result = SmartFlagTestEnum.FromName("Five, Four, Three, One, Two, Zero, Other, Values, Ignored", false).ToList(); + + Assert.Equal("Zero", result[0].Name); + Assert.Equal("One", result[1].Name); + Assert.Equal("Two", result[2].Name); + Assert.Equal("Three", result[3].Name); + Assert.Equal("Four", result[4].Name); + Assert.Equal("Five", result[5].Name); + } + + [Fact] + public void ReturnsIEnumerableWithSameValuesKeyboardBashTest() + { + var result = SmartFlagTestEnum.FromName("Five, Four, Three, One, Two, Zero, Other, Values, Ignored, asd, sdf, dfg ,dfg,asd,dfg,sadf,gh,dfgh,afd,asd,asd,af,asf,asf,,asffgfdg,,sdfg,sdg,sd,g,sdf,xcv,cvb,xzcv,asdf,ghdfcg,xc,asdf,asd,dfg,xcvb,asdf,asd,sfg,dfg,xcvb,szxdf,as,fdxc,v,sdf,sdf,sdf,sdf,xdc,sfdg,cvb,dfg,vbcndfg,sdf,das,vxc,gfds,asdf,afds,dgs", false).ToList(); + + Assert.Equal("Zero", result[0].Name); + Assert.Equal("One", result[1].Name); + Assert.Equal("Two", result[2].Name); + Assert.Equal("Three", result[3].Name); + Assert.Equal("Four", result[4].Name); + Assert.Equal("Five", result[5].Name); + } + + [Fact] + public void ReturnsIEnumerableWithSameValuesZeroTest() + { + var result = SmartFlagTestEnum.FromName("Zero, One").ToList(); + + Assert.Equal("Zero", result[0].Name); + Assert.Equal("One", result[1].Name); + } + + [Fact] + public void ReturnsIEnumerableWithSameValuesTryFromName() + { + var boolResult = SmartFlagTestEnum.TryFromName("One", out var result); + + Assert.True(boolResult); + Assert.Equal(SmartFlagTestEnum.One, result.ToList()[0]); + } + + [Fact] + public void ReturnsNullWhenGivenInvalidValue() + { + var boolResult = SmartFlagTestEnum.TryFromName("RandomString", out var result); + + Assert.False(boolResult); + Assert.Null(result); + } + } +} diff --git a/test/SmartFlagEnum.UnitTests/SmartFlagEnumFromValue.cs b/test/SmartFlagEnum.UnitTests/SmartFlagEnumFromValue.cs new file mode 100644 index 00000000..5f3c82b6 --- /dev/null +++ b/test/SmartFlagEnum.UnitTests/SmartFlagEnumFromValue.cs @@ -0,0 +1,207 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Ardalis.SmartEnum; +using Ardalis.SmartEnum.UnitTests; +using Xunit; + +namespace Ardalis.SmartFlagEnum.UnitTests +{ + public class SmartFlagEnumFromValue + { + [Theory] + [InlineData(1, "One")] + [InlineData(2, "Two")] + [InlineData(3, "One, Two")] + [InlineData(4, "Three")] + [InlineData(5, "One, Three")] + [InlineData(6, "Two, Three")] + [InlineData(7, "One, Two, Three")] + [InlineData(8, "Four")] + [InlineData(9, "One, Four")] + [InlineData(10, "Two, Four")] + [InlineData(11, "One, Two, Four")] + [InlineData(12, "Three, Four")] + [InlineData(13, "One, Three, Four")] + [InlineData(14, "Two, Three, Four")] + [InlineData(15, "One, Two, Three, Four")] + [InlineData(16, "Five")] + [InlineData(17, "One, Five")] + [InlineData(18, "Two, Five")] + [InlineData(19, "One, Two, Five")] + [InlineData(20, "Three, Five")] + [InlineData(21, "One, Three, Five")] + [InlineData(22, "Two, Three, Five")] + [InlineData(23, "One, Two, Three, Five")] + [InlineData(24, "Four, Five")] + [InlineData(25, "One, Four, Five")] + [InlineData(26, "Two, Four, Five")] + [InlineData(27, "One, Two, Four, Five")] + [InlineData(28, "Three, Four, Five")] + [InlineData(29, "One, Three, Four, Five")] + [InlineData(30, "Two, Three, Four, Five")] + [InlineData(31, "One, Two, Three, Four, Five")] + public void ReturnValidStringValues(int inputValue, string outputValue) + { + var result = SmartFlagTestEnum.FromValueToString(inputValue); + Assert.Equal(outputValue, result); + } + + [Fact] + public void ReturnsIEnumerableWithSingleValidValue() + { + SmartFlagTestEnum.TryFromValue(1, out var TEnum); + + var list = TEnum.ToList(); + + Assert.Single(TEnum); + Assert.Equal("One", list[0].Name); + } + + [Fact] + public void ReturnsIEnumerableWithTwoValidValues() + { + SmartFlagTestEnum.TryFromValue(3, out var Tenum); + + var list = Tenum.ToList(); + + Assert.Equal(2, Tenum.Count()); + Assert.Equal("One", list[0].Name); + Assert.Equal("Two", list[1].Name); + } + + [Fact] + public void ReturnsStringWithFourValidValues() + { + var result = SmartFlagTestEnum.FromValueToString(15); + + Assert.Equal("One, Two, Three, Four", result); + } + + [Fact] + public void ReturnsStringWithSingleValidValue() + { + var result = SmartFlagTestEnum.FromValueToString(16); + + Assert.Equal("Five", result); + } + + [Fact] + public void ThrowsExceptionWhenEnumIsAboveAllowableRangeFromValueToString() + { + Assert.Throws(() => SmartFlagTestEnum.FromValueToString(32)); + } + + [Fact] + public void ThrowsExceptionWhenEnumIsAboveAllowableRangeFromValue() + { + Assert.Throws(() => SmartFlagTestEnum.FromValue(32)); + } + + [Fact] + public void DoesNothingIfValueEqualsMaximumValue_BoundaryValueTest() + { + var result = SmartFlagTestEnum.FromValue(31); + } + + [Fact] + public void ThrowsExceptionWhenEnumIsAboveAllowableRangeV2() + { + Assert.Throws(() => SmartFlagTestEnumV2.FromValueToString(16)); + } + + [Fact] + public void OrBitwiseOperatorReturnsIEnumerableWithTwoSameValues() + { + SmartFlagTestEnum.TryFromValue(SmartFlagTestEnum.One | SmartFlagTestEnum.Two, out var enumList); + + var list = enumList.ToList(); + + Assert.Equal(2, enumList.Count()); + Assert.Equal("One", list[0].Name); + Assert.Equal("Two", list[1].ToString()); + } + + [Fact] + public void OrBitwiseOperatorReturnsIEnumerableWithThreeSameValues() + { + SmartFlagTestEnum.TryFromValue((SmartFlagTestEnum.One | SmartFlagTestEnum.Two | SmartFlagTestEnum.Three), out var output); + + var list = output.ToList(); + + Assert.Equal("One", list[0].Name); + Assert.Equal("Two", list[1].Name); + Assert.Equal("Three", list[2].Name); + + } + + [Fact] + public void OrBitwiseOperatorReturnsStringWithThreeSameValues() + { + var result = SmartFlagTestEnum.FromValueToString((SmartFlagTestEnum.One | SmartFlagTestEnum.Two | SmartFlagTestEnum.Three)); + + Assert.Equal("One, Two, Three", result); + } + + [Fact] + public void OrBitwiseOnNonIntTypeWorks() + { + var result = FlagEnumDecimal.FromValue((int)FlagEnumDecimal.One | (int)FlagEnumDecimal.Two).ToList(); + + Assert.Equal("One", result[0].Name); + Assert.Equal("Two", result[1].Name); + } + + [Fact] + public void SmartFlagEnumWithZeroValueReturnsValidValues() + { + var result = SmartFlagTestEnum.FromValue(3).ToList(); + + Assert.Equal("One", result[0].Name); + Assert.Equal("Two", result[1].Name); + } + + [Fact] + public void ZeroValueReturnsIEnumerableWithZeroValueEnum() + { + var result = SmartFlagTestEnum.FromValue(0).ToList(); + + Assert.Equal("Zero", result[0].Name); + } + + [Fact] + public void DecimalInputReturnsValidIEnumerableWithTwoValues() + { + var result = FlagEnumDecimal.FromValue(3M).ToList(); + + Assert.Equal("One", result[0].Name); + Assert.Equal("Two", result[1].Name); + } + + [Fact] + public void TryFromValueToStringReturnValidString() + { + var boolReturn = SmartFlagTestEnum.TryFromValueToString(3, out var result); + + Assert.True(boolReturn); + Assert.Equal("One, Two", result); + } + + [Fact] + public void TryFromValueToStringReturnsNullGivenInvalidValue() + { + var boolReturn = SmartFlagTestEnum.TryFromValueToString(345, out var result); + + Assert.False(boolReturn); + Assert.Null(result); + } + + [Fact] + public void FromValueHighNumberToStringThrowsSmartEnumNotFoundException() + { + Assert.Throws(() => SmartFlagTestEnum.FromValueToString(124)); + } + } +} + diff --git a/test/SmartFlagEnum.UnitTests/SmartFlagEnumImplicitValueConversion.cs b/test/SmartFlagEnum.UnitTests/SmartFlagEnumImplicitValueConversion.cs new file mode 100644 index 00000000..bc0c0423 --- /dev/null +++ b/test/SmartFlagEnum.UnitTests/SmartFlagEnumImplicitValueConversion.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Ardalis.SmartEnum.UnitTests; +using FluentAssertions; +using Xunit; + +namespace Ardalis.SmartFlagEnum.UnitTests +{ + public class SmartFlagEnumImplicitValueConversion + { + [Fact] + public void ReturnsValueOfGivenEnum() + { + var smartFlagEnum = SmartFlagTestEnum.One; + + int result = smartFlagEnum; + + result.Should().Be(smartFlagEnum.Value); + } + } +} diff --git a/test/SmartFlagEnum.UnitTests/SmartFlagEnumList.cs b/test/SmartFlagEnum.UnitTests/SmartFlagEnumList.cs new file mode 100644 index 00000000..675888a6 --- /dev/null +++ b/test/SmartFlagEnum.UnitTests/SmartFlagEnumList.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Ardalis.SmartEnum.UnitTests; +using FluentAssertions; +using Xunit; + +namespace Ardalis.SmartFlagEnum.UnitTests +{ + public class SmartFlagEnumList + { + [Fact] + public void ReturnsAllDefinedSmartEnums() + { + var result = SmartFlagTestEnum.List; + + result.Should().BeEquivalentTo(new[] { + SmartFlagTestEnum.Zero, + SmartFlagTestEnum.One, + SmartFlagTestEnum.Two, + SmartFlagTestEnum.Three, + SmartFlagTestEnum.Four, + SmartFlagTestEnum.Five, + }); + } + } +} diff --git a/test/SmartFlagEnum.UnitTests/SmartFlagEnumStringFromValue.cs b/test/SmartFlagEnum.UnitTests/SmartFlagEnumStringFromValue.cs new file mode 100644 index 00000000..8a9ec4df --- /dev/null +++ b/test/SmartFlagEnum.UnitTests/SmartFlagEnumStringFromValue.cs @@ -0,0 +1,39 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Text; +using Ardalis.SmartEnum; +using Ardalis.SmartEnum.Exceptions; +using Ardalis.SmartEnum.UnitTests; +using FluentAssertions; +using Xunit; + +namespace Ardalis.SmartFlagEnum.UnitTests +{ + public class SmartFlagEnumStringFromValue + { + + [Fact] + public void ThrowsGivenNonMatchingValue() + { + var value = string.Empty; + + Action action = () => SmartFlagTestStringEnum.FromValue(value); + + action.Should() + .ThrowExactly() + .WithMessage($"The value: {value.ToString()} input to {nameof(SmartFlagTestStringEnum)} could not be parsed into an integer value."); + } + + [Fact] + public void ReturnsDefaultEnumGivenNonMatchingValue() + { + var value = string.Empty; + var defaultEnumValue = new List{SmartFlagTestStringEnum.One, SmartFlagTestStringEnum.Two}; + + var result = SmartFlagTestStringEnum.FromValue(value, defaultEnumValue); + + result.Should().BeSameAs(defaultEnumValue); + } + } +} diff --git a/test/SmartFlagEnum.UnitTests/SmartFlagEnumToString.cs b/test/SmartFlagEnum.UnitTests/SmartFlagEnumToString.cs new file mode 100644 index 00000000..adea8712 --- /dev/null +++ b/test/SmartFlagEnum.UnitTests/SmartFlagEnumToString.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Ardalis.SmartEnum.UnitTests; +using FluentAssertions; +using Xunit; + +namespace Ardalis.SmartFlagEnum.UnitTests +{ + public class SmartFlagEnumToString + { + public static TheoryData NameData => + new TheoryData + { + SmartFlagTestEnum.One, + SmartFlagTestEnum.Two, + SmartFlagTestEnum.Three, + }; + + [Theory] + [MemberData(nameof(NameData))] + public void ReturnsFormattedNameAndValue(SmartFlagTestEnum smartEnum) + { + var result = smartEnum.ToString(); + + result.Should().Be(smartEnum.Name); + } + } +} diff --git a/test/SmartFlagEnum.UnitTests/SmartFlagEnumUsageExample.cs b/test/SmartFlagEnum.UnitTests/SmartFlagEnumUsageExample.cs new file mode 100644 index 00000000..ee133a37 --- /dev/null +++ b/test/SmartFlagEnum.UnitTests/SmartFlagEnumUsageExample.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Ardalis.SmartEnum; +using Xunit; + +namespace Ardalis.SmartFlagEnum.UnitTests +{ + public abstract class EmployeeType : SmartFlagEnum + { + public static readonly EmployeeType Director = new DirectorType(); + public static readonly EmployeeType Manager = new ManagerType(); + public static readonly EmployeeType Assistant = new AssistantType(); + + private EmployeeType(string name, int value) : base(name, value) + { + } + + public abstract decimal BonusSize { get; } + + private sealed class DirectorType : EmployeeType + { + public DirectorType() : base("Director", 1) { } + + public override decimal BonusSize => 100_000m; + } + + private sealed class ManagerType : EmployeeType + { + public ManagerType() : base("Manager", 2) { } + + public override decimal BonusSize => 10_000m; + } + + private sealed class AssistantType : EmployeeType + { + public AssistantType() : base("Assistant", 4) { } + + public override decimal BonusSize => 1_000m; + } + } + + public class SmartFlagEnumUsageExample + { + [Fact] + public void UseSmartFlagEnum() + { + var result = EmployeeType.FromValue(3).ToList(); + + var outputString = ""; + foreach (var employeeType in result) + { + outputString += $"{employeeType.Name} earns ${employeeType.BonusSize} bonus this year.\n"; + } + + Assert.Equal("Director earns $100000 bonus this year.\n" + "Manager earns $10000 bonus this year.\n", + outputString); + + var allResult = EmployeeType.FromValueToString(-1); + Assert.Equal("Director, Manager, Assistant", allResult); + + var orResult = EmployeeType.FromValueToString(EmployeeType.Assistant | EmployeeType.Director); + Assert.Equal("Director, Assistant", orResult); + } + } +} diff --git a/test/SmartFlagEnum.UnitTests/SmartFlagEnumWhenThen.cs b/test/SmartFlagEnum.UnitTests/SmartFlagEnumWhenThen.cs new file mode 100644 index 00000000..bf2b1821 --- /dev/null +++ b/test/SmartFlagEnum.UnitTests/SmartFlagEnumWhenThen.cs @@ -0,0 +1,141 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Ardalis.SmartEnum.UnitTests; +using FluentAssertions; +using Xunit; + +namespace Ardalis.SmartFlagEnum.UnitTests +{ + public class SmartFlagEnumWhenThen + { + public static TheoryData NameData => + new TheoryData() + { + SmartFlagTestEnum.One, + SmartFlagTestEnum.Two, + SmartFlagTestEnum.Three + }; + + [Fact] + public void DefaultConditionDoesNotRunWhenConditionMet() + { + var one = SmartFlagTestEnum.One; + + var firstActionRun = false; + var defaultActionRun = false; + + one + .When(SmartFlagTestEnum.One).Then(() => firstActionRun = true) + .Default(() => defaultActionRun = true); + + firstActionRun.Should().BeTrue(); + defaultActionRun.Should().BeFalse(); + } + + [Fact] + public void DefaultConditionRunsWhenNoConditionMet() + { + var three = SmartFlagTestEnum.Three; + + var firstActionRun = false; + var secondActionRun = false; + var defaultActionRun = false; + + three + .When(SmartFlagTestEnum.One).Then(() => firstActionRun = true) + .When(SmartFlagTestEnum.Two).Then(() => secondActionRun = true) + .Default(() => defaultActionRun = true); + + firstActionRun.Should().BeFalse(); + secondActionRun.Should().BeFalse(); + defaultActionRun.Should().BeTrue(); + } + + [Fact] + public void WhenFirstConditionMetFirstActionRuns() + { + var one = SmartFlagTestEnum.One; + + var firstActionRun = false; + var secondActionRun = false; + + one + .When(SmartFlagTestEnum.One).Then(() => firstActionRun = true) + .When(SmartFlagTestEnum.Two).Then(() => secondActionRun = true); + + firstActionRun.Should().BeTrue(); + secondActionRun.Should().BeFalse(); + } + + [Fact] + public void WhenFirstConditionMetSubsequentActionsNotRun() + { + var one = SmartFlagTestEnum.One; + + var firstActionRun = false; + var secondActionRun = false; + + one + .When(SmartFlagTestEnum.One).Then(() => firstActionRun = true) + .When(SmartFlagTestEnum.One).Then(() => secondActionRun = true); + + firstActionRun.Should().BeTrue(); + secondActionRun.Should().BeFalse(); + } + + [Fact] + public void WhenMatchesLastListActionRuns() + { + var three = SmartFlagTestEnum.Three; + + var firstActionRun = false; + var secondActionRun = false; + var thirdActionRun = false; + + three + .When(SmartFlagTestEnum.One).Then(() => firstActionRun = true) + .When(SmartFlagTestEnum.Two).Then(() => secondActionRun = true) + .When(new List { SmartFlagTestEnum.One, SmartFlagTestEnum.Two, SmartFlagTestEnum.Three }).Then(() => thirdActionRun = true); + + firstActionRun.Should().BeFalse(); + secondActionRun.Should().BeFalse(); + thirdActionRun.Should().BeTrue(); + } + + [Fact] + public void WhenMatchesLastParameterActionRuns() + { + var three = SmartFlagTestEnum.Three; + + var firstActionRun = false; + var secondActionRun = false; + var thirdActionRun = false; + + three + .When(SmartFlagTestEnum.One).Then(() => firstActionRun = true) + .When(SmartFlagTestEnum.Two).Then(() => secondActionRun = true) + .When(SmartFlagTestEnum.One, SmartFlagTestEnum.Two, SmartFlagTestEnum.Three).Then(() => thirdActionRun = true); + + firstActionRun.Should().BeFalse(); + secondActionRun.Should().BeFalse(); + thirdActionRun.Should().BeTrue(); + } + + [Fact] + public void WhenSecondConditionMetSecondActionRuns() + { + var two = SmartFlagTestEnum.Two; + + var firstActionRun = false; + var secondActionRun = false; + + two + .When(SmartFlagTestEnum.One).Then(() => firstActionRun = true) + .When(SmartFlagTestEnum.Two).Then(() => secondActionRun = true); + + firstActionRun.Should().BeFalse(); + secondActionRun.Should().BeTrue(); + } + } +} diff --git a/test/SmartFlagEnum.UnitTests/SmartFlagGetFlagEnumTests.cs b/test/SmartFlagEnum.UnitTests/SmartFlagGetFlagEnumTests.cs new file mode 100644 index 00000000..7308460a --- /dev/null +++ b/test/SmartFlagEnum.UnitTests/SmartFlagGetFlagEnumTests.cs @@ -0,0 +1,133 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Ardalis.SmartEnum; +using Ardalis.SmartEnum.Exceptions; +using Ardalis.SmartEnum.UnitTests; +using Xunit; + +namespace Ardalis.SmartFlagEnum.UnitTests +{ + public class SmartFlagGetFlagEnumTests + { + [Fact] + public void DoesNothingIfEnumValuesArePowerOfTwo() + { + SmartFlagTestEnum.FromValue(3); + } + + [Fact] + public void ValidResultWhenEnumFlagCombinationValuesAreExplicitlyDeclared() + { + var result = SmartFlagExplicitDeclaredCombinationTestEnum.FromValue(3).ToList(); + + Assert.Equal("Three", result[0].Name); + } + + [Fact] + public void ValidResultWhenEnumFlagCombinationValuesAreExplicitlyDeclaredGivenCombinationValueGreaterThanMaximumFlagValue() + { + var result = SmartFlagExplicitDeclaredCombinationTestEnum.FromValue(5).ToList(); + + Assert.Equal("Five", result[0].Name); + } + + [Fact] + public void ThrowsExceptionGivenNonImplicitCombinationValueGreaterThanMaximumFlagValue() + { + Assert.Throws(() => SmartFlagExplicitDeclaredCombinationTestEnum.FromValue(4)); + } + + [Fact] + public void ReturnsValidResultWhenNegativeOneAllValueIncluded() + { + var result = SmartFlagNegativeAndZeroMultiValueTestEnum.FromValue(3).ToList(); + + Assert.Equal(2, result.Count); + Assert.Equal("One", result[0].Name); + Assert.Equal("Two", result[1].Name); + } + + [Fact] + public void ReturnsNoneEnumWhenZeroValueInput() + { + var result = SmartFlagNegativeAndZeroMultiValueTestEnum.FromValue(0).ToList(); + + Assert.Single(result); + Assert.Equal("None", result[0].Name); + } + + [Fact] + public void ReturnsAllEnumWhenNegativeOneValueInput() + { + var result = SmartFlagNegativeAndZeroMultiValueTestEnum.FromValue(-1).ToList(); + + Assert.Single(result); + Assert.Equal("All", result[0].Name); + } + + [Fact] + public void DoesNothingIfEnumValuesArePositive() + { + SmartFlagTestEnum.FromValue(3); + } + + [Fact] + public void ThrowsExceptionIfValueIsNegative() + { + Assert.Throws(() => SmartFlagNegativeTestEnum.FromValue(1)); + } + + [Fact] + public void ThrowsExceptionIfValueIsNegativeFirstValueNegative() + { + Assert.Throws(() => SmartFlagNegativeTestEnum.FromValue(1)); + } + + [Fact] + public void AlternativeValueBitshiftNotationReturnsValidValue() + { + var result = SmartFlagAlternativeValueNotation.FromValue(3).ToList(); + + Assert.Equal(2 , result.Count); + Assert.Equal("One", result[0].Name); + Assert.Equal("Two", result[1].Name); + } + + [Fact] + public void ReturnsAllWhenGivenInputNegativeOne() + { + var result = SmartFlagTestEnum.FromValue(-1).ToList(); + + Assert.Equal(5, result.Count); + Assert.Equal("One", result[0].Name); + Assert.Equal("Two", result[1].Name); + Assert.Equal("Three", result[2].Name); + Assert.Equal("Four", result[3].Name); + Assert.Equal("Five", result[4].Name); + } + + [Fact] + public void ReturnsAllExplicitCombinationValueWhenGivenNegativeOne() + { + var result = SmartFlagNegativeAndZeroMultiValueTestEnum.FromValue(-1).ToList(); + + Assert.Single(result); + Assert.Equal("All", result[0].Name); + } + + [Fact] + public void ReturnsAllWhenGivenIntegerMaxValue() + { + var result = SmartFlagTestEnum.FromValue(int.MaxValue).ToList(); + + Assert.Equal(5, result.Count); + Assert.Equal("One", result[0].Name); + Assert.Equal("Two", result[1].Name); + Assert.Equal("Three", result[2].Name); + Assert.Equal("Four", result[3].Name); + Assert.Equal("Five", result[4].Name); + } + } +} diff --git a/test/SmartFlagEnum.UnitTests/SmartFlagTestEnum.cs b/test/SmartFlagEnum.UnitTests/SmartFlagTestEnum.cs new file mode 100644 index 00000000..5061495b --- /dev/null +++ b/test/SmartFlagEnum.UnitTests/SmartFlagTestEnum.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using System.Text; +using Ardalis.SmartEnum; + +namespace Ardalis.SmartEnum.UnitTests +{ + public class SmartFlagTestEnum : SmartFlagEnum + { + public static readonly SmartFlagTestEnum Zero = new SmartFlagTestEnum(nameof(Zero), 0); + public static readonly SmartFlagTestEnum One = new SmartFlagTestEnum(nameof(One), 1); + public static readonly SmartFlagTestEnum Two = new SmartFlagTestEnum(nameof(Two), 2); + public static readonly SmartFlagTestEnum Three = new SmartFlagTestEnum(nameof(Three), 4); + public static readonly SmartFlagTestEnum Four = new SmartFlagTestEnum(nameof(Four), 8); + public static readonly SmartFlagTestEnum Five = new SmartFlagTestEnum(nameof(Five), 16); + + public SmartFlagTestEnum(string name, int value) : base(name, value) + { + } + } + + public class SmartFlagExplicitDeclaredCombinationTestEnum : SmartFlagEnum + { + public static readonly SmartFlagExplicitDeclaredCombinationTestEnum Zero = new SmartFlagExplicitDeclaredCombinationTestEnum(nameof(Zero), 0); + public static readonly SmartFlagExplicitDeclaredCombinationTestEnum One = new SmartFlagExplicitDeclaredCombinationTestEnum(nameof(One), 1); + public static readonly SmartFlagExplicitDeclaredCombinationTestEnum Two = new SmartFlagExplicitDeclaredCombinationTestEnum(nameof(Two), 2); + public static readonly SmartFlagExplicitDeclaredCombinationTestEnum Three = new SmartFlagExplicitDeclaredCombinationTestEnum(nameof(Three), 3); + public static readonly SmartFlagExplicitDeclaredCombinationTestEnum Five = new SmartFlagExplicitDeclaredCombinationTestEnum(nameof(Five), 5); + + public SmartFlagExplicitDeclaredCombinationTestEnum(string name, int value) : base(name, value) + { + } + } + + public class FlagEnumDecimal : SmartFlagEnum + { + public static readonly FlagEnumDecimal Zero = new FlagEnumDecimal(nameof(Zero), 0M); + public static readonly FlagEnumDecimal One = new FlagEnumDecimal(nameof(One), 1M); + public static readonly FlagEnumDecimal Two = new FlagEnumDecimal(nameof(Two), 2M); + public static readonly FlagEnumDecimal Three = new FlagEnumDecimal(nameof(Three), 4M); + public static readonly FlagEnumDecimal Four = new FlagEnumDecimal(nameof(Four), 8M); + public static readonly FlagEnumDecimal Five = new FlagEnumDecimal(nameof(Five), 16M); + + public FlagEnumDecimal(string name, decimal value) : base(name, value) + { + } + } + + public class SmartFlagTestEnumV2 : SmartFlagEnum + { + public static readonly SmartFlagTestEnumV2 One = new SmartFlagTestEnumV2(nameof(One), 1); + public static readonly SmartFlagTestEnumV2 Two = new SmartFlagTestEnumV2(nameof(Two), 2); + public static readonly SmartFlagTestEnumV2 Three = new SmartFlagTestEnumV2(nameof(Three), 4); + public static readonly SmartFlagTestEnumV2 Four = new SmartFlagTestEnumV2(nameof(Four), 8); + + public SmartFlagTestEnumV2(string name, int value) : base(name, value) + { + } + } + + public class SmartFlagNotPowerOfTwoTestEnum : SmartFlagEnum + { + public static readonly SmartFlagNotPowerOfTwoTestEnum Zero = new SmartFlagNotPowerOfTwoTestEnum(nameof(Zero), 0); + public static readonly SmartFlagNotPowerOfTwoTestEnum One = new SmartFlagNotPowerOfTwoTestEnum(nameof(One), 3); + public static readonly SmartFlagNotPowerOfTwoTestEnum Two = new SmartFlagNotPowerOfTwoTestEnum(nameof(Two), 4); + public static readonly SmartFlagNotPowerOfTwoTestEnum Three = new SmartFlagNotPowerOfTwoTestEnum(nameof(Three), 7); + public static readonly SmartFlagNotPowerOfTwoTestEnum Four = new SmartFlagNotPowerOfTwoTestEnum(nameof(Four), 9); + public static readonly SmartFlagNotPowerOfTwoTestEnum Five = new SmartFlagNotPowerOfTwoTestEnum(nameof(Five), 15); + + public SmartFlagNotPowerOfTwoTestEnum(string name, int value) : base(name, value) + { + } + } + + public class SmartFlagNegativeTestEnum : SmartFlagEnum + { + public static readonly SmartFlagNegativeTestEnum One = new SmartFlagNegativeTestEnum(nameof(One), 1); + public static readonly SmartFlagNegativeTestEnum Two = new SmartFlagNegativeTestEnum(nameof(Two), -2); + + public SmartFlagNegativeTestEnum(string name, int value) : base(name, value) + { + } + } + + public class SmartFlagNegativeTestEnumV2 : SmartFlagEnum + { + public static readonly SmartFlagNegativeTestEnumV2 Negative = new SmartFlagNegativeTestEnumV2(nameof(Negative), -34); + public static readonly SmartFlagNegativeTestEnumV2 One = new SmartFlagNegativeTestEnumV2(nameof(One), 1); + public static readonly SmartFlagNegativeTestEnumV2 Two = new SmartFlagNegativeTestEnumV2(nameof(Two), 2); + public static readonly SmartFlagNegativeTestEnumV2 Three = new SmartFlagNegativeTestEnumV2(nameof(Three), 4); + + public SmartFlagNegativeTestEnumV2(string name, int value) : base(name, value) + { + } + } + + public class SmartFlagNegativeAndZeroMultiValueTestEnum : SmartFlagEnum + { + public static readonly SmartFlagNegativeAndZeroMultiValueTestEnum All = new SmartFlagNegativeAndZeroMultiValueTestEnum(nameof(All), -1); + public static readonly SmartFlagNegativeAndZeroMultiValueTestEnum None = new SmartFlagNegativeAndZeroMultiValueTestEnum(nameof(None), 0); + public static readonly SmartFlagNegativeAndZeroMultiValueTestEnum One = new SmartFlagNegativeAndZeroMultiValueTestEnum(nameof(One), 1); + public static readonly SmartFlagNegativeAndZeroMultiValueTestEnum Two = new SmartFlagNegativeAndZeroMultiValueTestEnum(nameof(Two), 2); + + public SmartFlagNegativeAndZeroMultiValueTestEnum(string name, int value) : base(name, value) + { + } + } + + public class SmartFlagTestStringEnum : SmartFlagEnum + { + public static readonly SmartFlagTestStringEnum One = new SmartFlagTestStringEnum(nameof(One), nameof(One)); + public static readonly SmartFlagTestStringEnum Two = new SmartFlagTestStringEnum(nameof(Two), nameof(Two)); + public static readonly SmartFlagTestStringEnum Three = new SmartFlagTestStringEnum(nameof(Three), nameof(Three)); + + public SmartFlagTestStringEnum(string name, string value) : base(name, value) + { + } + } + + public class SmartFlagAlternativeValueNotation : SmartFlagEnum + { + public static readonly SmartFlagAlternativeValueNotation None = new SmartFlagAlternativeValueNotation(nameof(None), 0); + public static readonly SmartFlagAlternativeValueNotation One = new SmartFlagAlternativeValueNotation(nameof(One), 1 << 0); + public static readonly SmartFlagAlternativeValueNotation Two = new SmartFlagAlternativeValueNotation(nameof(Two), 1 << 1); + public static readonly SmartFlagAlternativeValueNotation Three = new SmartFlagAlternativeValueNotation(nameof(Three), 1 << 2); + + public SmartFlagAlternativeValueNotation(string name, int value) : base(name, value) + { + } + } +}