From bad71f8da4704863d2920968112e91e38ff934f4 Mon Sep 17 00:00:00 2001 From: Matthias Gernand Date: Sun, 29 May 2022 22:55:16 +0200 Subject: [PATCH] Added initial version of the library. (#1) --- Fluxera.StronglyTypedId.sln | 125 +++++++++++++++ GitVersion.yml | 1 + README.md | 30 ++++ azure-pipelines.yml | 86 ++++++++++ icon.png | Bin 0 -> 6680 bytes src/.gitkeep | 1 + src/Directory.Build.props | 36 +++++ ...StronglyTypedId.EntityFrameworkCore.csproj | 37 +++++ .../ModelBuilderExtensions.cs | 53 +++++++ .../StronglyTypedIdConverter.cs | 39 +++++ .../CompositeContractResolver.cs | 56 +++++++ .../Fluxera.StronglyTypedId.JsonNet.csproj | 37 +++++ .../JsonSerializerSettingsExtensions.cs | 24 +++ .../StronglyTypedIdContractResolver.cs | 27 ++++ .../StronglyTypedIdConverter.cs | 46 ++++++ .../BsonMapperExtensions.cs | 40 +++++ .../Fluxera.StronglyTypedId.LiteDB.csproj | 37 +++++ .../StronglyTypedIdConverter.cs | 51 ++++++ .../ConventionPackExtensions.cs | 24 +++ .../Fluxera.StronglyTypedId.MongoDB.csproj | 37 +++++ .../StronglyTypedId.cs | 47 ++++++ .../StronglyTypedIdConvention.cs | 31 ++++ ...xera.StronglyTypedId.SystemTextJson.csproj | 37 +++++ .../JsonSerializerOptionsExtensions.cs | 21 +++ .../StronglyTypedIdConverter.cs | 41 +++++ .../StronglyTypedIdJsonConverterFactory.cs | 29 ++++ .../Fluxera.StronglyTypedId.csproj | 33 ++++ .../IStronglyTypedId.cs | 19 +++ .../PropertyAccessor.cs | 61 +++++++ .../StronglyTypedId.cs | 150 ++++++++++++++++++ .../StronglyTypedIdExtensions.cs | 60 +++++++ tests/.gitkeep | 1 + tests/Directory.Build.props | 17 ++ .../DbContextFactory.cs | 21 +++ ...pedId.EntityFrameworkCore.UnitTests.csproj | 24 +++ .../ModelBuilderExtensionsTests.cs | 21 +++ .../NoModelCacheKeyFactory.cs | 17 ++ .../Person.cs | 12 ++ .../PersonFactory.cs | 22 +++ .../PersonId.cs | 10 ++ .../TestDbContext.cs | 67 ++++++++ .../ConverterTests.cs | 52 ++++++ ...a.StronglyTypedId.JsonNet.UnitTests.csproj | 21 +++ .../PersonId.cs | 10 ++ .../ConverterTests.cs | 45 ++++++ ...ra.StronglyTypedId.LiteDB.UnitTests.csproj | 22 +++ .../PersonId.cs | 10 ++ .../ConverterTests.cs | 47 ++++++ ...a.StronglyTypedId.MongoDB.UnitTests.csproj | 22 +++ .../PersonId.cs | 10 ++ .../ConverterTests.cs | 46 ++++++ ...glyTypedId.SystemTextJson.UnitTests.csproj | 22 +++ .../PersonId.cs | 10 ++ .../EqualsOperatorsTests.cs | 73 +++++++++ .../Fluxera.StronglyTypedId.UnitTests.csproj | 29 ++++ .../GetHashCodeTests.cs | 50 ++++++ .../Model/CustomerId.cs | 10 ++ 57 files changed, 2007 insertions(+) create mode 100644 Fluxera.StronglyTypedId.sln create mode 100644 GitVersion.yml create mode 100644 azure-pipelines.yml create mode 100644 icon.png create mode 100644 src/.gitkeep create mode 100644 src/Directory.Build.props create mode 100644 src/Fluxera.StronglyTypedId.EntityFrameworkCore/Fluxera.StronglyTypedId.EntityFrameworkCore.csproj create mode 100644 src/Fluxera.StronglyTypedId.EntityFrameworkCore/ModelBuilderExtensions.cs create mode 100644 src/Fluxera.StronglyTypedId.EntityFrameworkCore/StronglyTypedIdConverter.cs create mode 100644 src/Fluxera.StronglyTypedId.JsonNet/CompositeContractResolver.cs create mode 100644 src/Fluxera.StronglyTypedId.JsonNet/Fluxera.StronglyTypedId.JsonNet.csproj create mode 100644 src/Fluxera.StronglyTypedId.JsonNet/JsonSerializerSettingsExtensions.cs create mode 100644 src/Fluxera.StronglyTypedId.JsonNet/StronglyTypedIdContractResolver.cs create mode 100644 src/Fluxera.StronglyTypedId.JsonNet/StronglyTypedIdConverter.cs create mode 100644 src/Fluxera.StronglyTypedId.LiteDB/BsonMapperExtensions.cs create mode 100644 src/Fluxera.StronglyTypedId.LiteDB/Fluxera.StronglyTypedId.LiteDB.csproj create mode 100644 src/Fluxera.StronglyTypedId.LiteDB/StronglyTypedIdConverter.cs create mode 100644 src/Fluxera.StronglyTypedId.MongoDB/ConventionPackExtensions.cs create mode 100644 src/Fluxera.StronglyTypedId.MongoDB/Fluxera.StronglyTypedId.MongoDB.csproj create mode 100644 src/Fluxera.StronglyTypedId.MongoDB/StronglyTypedId.cs create mode 100644 src/Fluxera.StronglyTypedId.MongoDB/StronglyTypedIdConvention.cs create mode 100644 src/Fluxera.StronglyTypedId.SystemTextJson/Fluxera.StronglyTypedId.SystemTextJson.csproj create mode 100644 src/Fluxera.StronglyTypedId.SystemTextJson/JsonSerializerOptionsExtensions.cs create mode 100644 src/Fluxera.StronglyTypedId.SystemTextJson/StronglyTypedIdConverter.cs create mode 100644 src/Fluxera.StronglyTypedId.SystemTextJson/StronglyTypedIdJsonConverterFactory.cs create mode 100644 src/Fluxera.StronglyTypedId/Fluxera.StronglyTypedId.csproj create mode 100644 src/Fluxera.StronglyTypedId/IStronglyTypedId.cs create mode 100644 src/Fluxera.StronglyTypedId/PropertyAccessor.cs create mode 100644 src/Fluxera.StronglyTypedId/StronglyTypedId.cs create mode 100644 src/Fluxera.StronglyTypedId/StronglyTypedIdExtensions.cs create mode 100644 tests/.gitkeep create mode 100644 tests/Directory.Build.props create mode 100644 tests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests/DbContextFactory.cs create mode 100644 tests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests.csproj create mode 100644 tests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests/ModelBuilderExtensionsTests.cs create mode 100644 tests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests/NoModelCacheKeyFactory.cs create mode 100644 tests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests/Person.cs create mode 100644 tests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests/PersonFactory.cs create mode 100644 tests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests/PersonId.cs create mode 100644 tests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests/TestDbContext.cs create mode 100644 tests/Fluxera.StronglyTypedId.JsonNet.UnitTests/ConverterTests.cs create mode 100644 tests/Fluxera.StronglyTypedId.JsonNet.UnitTests/Fluxera.StronglyTypedId.JsonNet.UnitTests.csproj create mode 100644 tests/Fluxera.StronglyTypedId.JsonNet.UnitTests/PersonId.cs create mode 100644 tests/Fluxera.StronglyTypedId.LiteDB.UnitTests/ConverterTests.cs create mode 100644 tests/Fluxera.StronglyTypedId.LiteDB.UnitTests/Fluxera.StronglyTypedId.LiteDB.UnitTests.csproj create mode 100644 tests/Fluxera.StronglyTypedId.LiteDB.UnitTests/PersonId.cs create mode 100644 tests/Fluxera.StronglyTypedId.MongoDB.UnitTests/ConverterTests.cs create mode 100644 tests/Fluxera.StronglyTypedId.MongoDB.UnitTests/Fluxera.StronglyTypedId.MongoDB.UnitTests.csproj create mode 100644 tests/Fluxera.StronglyTypedId.MongoDB.UnitTests/PersonId.cs create mode 100644 tests/Fluxera.StronglyTypedId.SystemTextJson.UnitTests/ConverterTests.cs create mode 100644 tests/Fluxera.StronglyTypedId.SystemTextJson.UnitTests/Fluxera.StronglyTypedId.SystemTextJson.UnitTests.csproj create mode 100644 tests/Fluxera.StronglyTypedId.SystemTextJson.UnitTests/PersonId.cs create mode 100644 tests/Fluxera.StronglyTypedId.UnitTests/EqualsOperatorsTests.cs create mode 100644 tests/Fluxera.StronglyTypedId.UnitTests/Fluxera.StronglyTypedId.UnitTests.csproj create mode 100644 tests/Fluxera.StronglyTypedId.UnitTests/GetHashCodeTests.cs create mode 100644 tests/Fluxera.StronglyTypedId.UnitTests/Model/CustomerId.cs diff --git a/Fluxera.StronglyTypedId.sln b/Fluxera.StronglyTypedId.sln new file mode 100644 index 0000000..9926bf4 --- /dev/null +++ b/Fluxera.StronglyTypedId.sln @@ -0,0 +1,125 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio Version 17 +VisualStudioVersion = 17.0.31919.166 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".items", ".items", "{75AC41E9-2B8B-4D76-A7DF-A18F4FB9D6D6}" + ProjectSection(SolutionItems) = preProject + .gitignore = .gitignore + azure-pipelines.yml = azure-pipelines.yml + GitVersion.yml = GitVersion.yml + icon.png = icon.png + LICENSE = LICENSE + README.md = README.md + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{DF28D730-99B3-4811-AAC7-725A73B3F668}" + ProjectSection(SolutionItems) = preProject + src\Directory.Build.props = src\Directory.Build.props + EndProjectSection +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{F18D2D58-282C-4D93-8D9A-3A76CF98F018}" + ProjectSection(SolutionItems) = preProject + tests\Directory.Build.props = tests\Directory.Build.props + EndProjectSection +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fluxera.StronglyTypedId", "src\Fluxera.StronglyTypedId\Fluxera.StronglyTypedId.csproj", "{0112DE42-7C32-4ECC-A5C1-6839C63A689C}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fluxera.StronglyTypedId.UnitTests", "tests\Fluxera.StronglyTypedId.UnitTests\Fluxera.StronglyTypedId.UnitTests.csproj", "{6F6CAF72-1D71-434B-AD09-7A9216669502}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fluxera.StronglyTypedId.JsonNet", "src\Fluxera.StronglyTypedId.JsonNet\Fluxera.StronglyTypedId.JsonNet.csproj", "{FA775AE5-1B74-4C42-94CF-03F3BD54E9F8}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fluxera.StronglyTypedId.JsonNet.UnitTests", "tests\Fluxera.StronglyTypedId.JsonNet.UnitTests\Fluxera.StronglyTypedId.JsonNet.UnitTests.csproj", "{F92B3AEE-E3CF-49FF-857B-7205A7FD08EF}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fluxera.StronglyTypedId.SystemTextJson", "src\Fluxera.StronglyTypedId.SystemTextJson\Fluxera.StronglyTypedId.SystemTextJson.csproj", "{78B29A90-2BD9-4603-B3E2-CBB71DD59BEE}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fluxera.StronglyTypedId.SystemTextJson.UnitTests", "tests\Fluxera.StronglyTypedId.SystemTextJson.UnitTests\Fluxera.StronglyTypedId.SystemTextJson.UnitTests.csproj", "{4B5548EC-9CFF-4F83-9FDF-1831D439DB6B}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fluxera.StronglyTypedId.LiteDB", "src\Fluxera.StronglyTypedId.LiteDB\Fluxera.StronglyTypedId.LiteDB.csproj", "{ACAAD773-D974-41DA-A04C-9A6B06766D82}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fluxera.StronglyTypedId.LiteDB.UnitTests", "tests\Fluxera.StronglyTypedId.LiteDB.UnitTests\Fluxera.StronglyTypedId.LiteDB.UnitTests.csproj", "{5A4B736C-AEF7-4CF0-A27A-7E8ACD88B788}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fluxera.StronglyTypedId.MongoDB", "src\Fluxera.StronglyTypedId.MongoDB\Fluxera.StronglyTypedId.MongoDB.csproj", "{8452ADA6-51E7-4E69-A83C-7EBC822E6AAB}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fluxera.StronglyTypedId.MongoDB.UnitTests", "tests\Fluxera.StronglyTypedId.MongoDB.UnitTests\Fluxera.StronglyTypedId.MongoDB.UnitTests.csproj", "{75A3F24F-590F-457B-B031-B3A41FB51C02}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fluxera.StronglyTypedId.EntityFrameworkCore", "src\Fluxera.StronglyTypedId.EntityFrameworkCore\Fluxera.StronglyTypedId.EntityFrameworkCore.csproj", "{AD7A0B26-A63A-47DC-897B-781206B084AC}" +EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests", "tests\Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests\Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests.csproj", "{1E08934F-F3FF-498A-B660-18E6C7C0958B}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {0112DE42-7C32-4ECC-A5C1-6839C63A689C}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0112DE42-7C32-4ECC-A5C1-6839C63A689C}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0112DE42-7C32-4ECC-A5C1-6839C63A689C}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0112DE42-7C32-4ECC-A5C1-6839C63A689C}.Release|Any CPU.Build.0 = Release|Any CPU + {6F6CAF72-1D71-434B-AD09-7A9216669502}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {6F6CAF72-1D71-434B-AD09-7A9216669502}.Debug|Any CPU.Build.0 = Debug|Any CPU + {6F6CAF72-1D71-434B-AD09-7A9216669502}.Release|Any CPU.ActiveCfg = Release|Any CPU + {6F6CAF72-1D71-434B-AD09-7A9216669502}.Release|Any CPU.Build.0 = Release|Any CPU + {FA775AE5-1B74-4C42-94CF-03F3BD54E9F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {FA775AE5-1B74-4C42-94CF-03F3BD54E9F8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {FA775AE5-1B74-4C42-94CF-03F3BD54E9F8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {FA775AE5-1B74-4C42-94CF-03F3BD54E9F8}.Release|Any CPU.Build.0 = Release|Any CPU + {F92B3AEE-E3CF-49FF-857B-7205A7FD08EF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {F92B3AEE-E3CF-49FF-857B-7205A7FD08EF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {F92B3AEE-E3CF-49FF-857B-7205A7FD08EF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {F92B3AEE-E3CF-49FF-857B-7205A7FD08EF}.Release|Any CPU.Build.0 = Release|Any CPU + {78B29A90-2BD9-4603-B3E2-CBB71DD59BEE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {78B29A90-2BD9-4603-B3E2-CBB71DD59BEE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {78B29A90-2BD9-4603-B3E2-CBB71DD59BEE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {78B29A90-2BD9-4603-B3E2-CBB71DD59BEE}.Release|Any CPU.Build.0 = Release|Any CPU + {4B5548EC-9CFF-4F83-9FDF-1831D439DB6B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {4B5548EC-9CFF-4F83-9FDF-1831D439DB6B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {4B5548EC-9CFF-4F83-9FDF-1831D439DB6B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {4B5548EC-9CFF-4F83-9FDF-1831D439DB6B}.Release|Any CPU.Build.0 = Release|Any CPU + {ACAAD773-D974-41DA-A04C-9A6B06766D82}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ACAAD773-D974-41DA-A04C-9A6B06766D82}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ACAAD773-D974-41DA-A04C-9A6B06766D82}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ACAAD773-D974-41DA-A04C-9A6B06766D82}.Release|Any CPU.Build.0 = Release|Any CPU + {5A4B736C-AEF7-4CF0-A27A-7E8ACD88B788}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {5A4B736C-AEF7-4CF0-A27A-7E8ACD88B788}.Debug|Any CPU.Build.0 = Debug|Any CPU + {5A4B736C-AEF7-4CF0-A27A-7E8ACD88B788}.Release|Any CPU.ActiveCfg = Release|Any CPU + {5A4B736C-AEF7-4CF0-A27A-7E8ACD88B788}.Release|Any CPU.Build.0 = Release|Any CPU + {8452ADA6-51E7-4E69-A83C-7EBC822E6AAB}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {8452ADA6-51E7-4E69-A83C-7EBC822E6AAB}.Debug|Any CPU.Build.0 = Debug|Any CPU + {8452ADA6-51E7-4E69-A83C-7EBC822E6AAB}.Release|Any CPU.ActiveCfg = Release|Any CPU + {8452ADA6-51E7-4E69-A83C-7EBC822E6AAB}.Release|Any CPU.Build.0 = Release|Any CPU + {75A3F24F-590F-457B-B031-B3A41FB51C02}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {75A3F24F-590F-457B-B031-B3A41FB51C02}.Debug|Any CPU.Build.0 = Debug|Any CPU + {75A3F24F-590F-457B-B031-B3A41FB51C02}.Release|Any CPU.ActiveCfg = Release|Any CPU + {75A3F24F-590F-457B-B031-B3A41FB51C02}.Release|Any CPU.Build.0 = Release|Any CPU + {AD7A0B26-A63A-47DC-897B-781206B084AC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {AD7A0B26-A63A-47DC-897B-781206B084AC}.Debug|Any CPU.Build.0 = Debug|Any CPU + {AD7A0B26-A63A-47DC-897B-781206B084AC}.Release|Any CPU.ActiveCfg = Release|Any CPU + {AD7A0B26-A63A-47DC-897B-781206B084AC}.Release|Any CPU.Build.0 = Release|Any CPU + {1E08934F-F3FF-498A-B660-18E6C7C0958B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1E08934F-F3FF-498A-B660-18E6C7C0958B}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1E08934F-F3FF-498A-B660-18E6C7C0958B}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1E08934F-F3FF-498A-B660-18E6C7C0958B}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(NestedProjects) = preSolution + {0112DE42-7C32-4ECC-A5C1-6839C63A689C} = {DF28D730-99B3-4811-AAC7-725A73B3F668} + {6F6CAF72-1D71-434B-AD09-7A9216669502} = {F18D2D58-282C-4D93-8D9A-3A76CF98F018} + {FA775AE5-1B74-4C42-94CF-03F3BD54E9F8} = {DF28D730-99B3-4811-AAC7-725A73B3F668} + {F92B3AEE-E3CF-49FF-857B-7205A7FD08EF} = {F18D2D58-282C-4D93-8D9A-3A76CF98F018} + {78B29A90-2BD9-4603-B3E2-CBB71DD59BEE} = {DF28D730-99B3-4811-AAC7-725A73B3F668} + {4B5548EC-9CFF-4F83-9FDF-1831D439DB6B} = {F18D2D58-282C-4D93-8D9A-3A76CF98F018} + {ACAAD773-D974-41DA-A04C-9A6B06766D82} = {DF28D730-99B3-4811-AAC7-725A73B3F668} + {5A4B736C-AEF7-4CF0-A27A-7E8ACD88B788} = {F18D2D58-282C-4D93-8D9A-3A76CF98F018} + {8452ADA6-51E7-4E69-A83C-7EBC822E6AAB} = {DF28D730-99B3-4811-AAC7-725A73B3F668} + {75A3F24F-590F-457B-B031-B3A41FB51C02} = {F18D2D58-282C-4D93-8D9A-3A76CF98F018} + {AD7A0B26-A63A-47DC-897B-781206B084AC} = {DF28D730-99B3-4811-AAC7-725A73B3F668} + {1E08934F-F3FF-498A-B660-18E6C7C0958B} = {F18D2D58-282C-4D93-8D9A-3A76CF98F018} + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {D97BF2AF-6E68-4E88-BF70-773A86E26013} + EndGlobalSection +EndGlobal diff --git a/GitVersion.yml b/GitVersion.yml new file mode 100644 index 0000000..0093d88 --- /dev/null +++ b/GitVersion.yml @@ -0,0 +1 @@ +mode: Mainline diff --git a/README.md b/README.md index 773a417..0d4c651 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,32 @@ # Fluxera.StronglyTypedId + A library that provides strongly-typed Ids without code generation. + + +## Usage + +To implement a strongly-typed ID just inherit from the ```StronglyTypedId<,>``` base class. +The base class implements all aspects of the ID, like quality and comparablility. + +```C# +public sealed class PersonId : StronglyTypedId +{ + /// + public PersonId(string value) : base(value) + { + } +} +``` + +## Serializer Support + +This library provides serializer support for the following libaries: + +- EF Core +- JSON.NET +- LiteDB +- MongoDB +- System.Text.Json + +To use the serializer support just use the ```UseStronglyTypedId``` extension method to configure the +corresponding serializer. diff --git a/azure-pipelines.yml b/azure-pipelines.yml new file mode 100644 index 0000000..5e99bad --- /dev/null +++ b/azure-pipelines.yml @@ -0,0 +1,86 @@ +# CI pipeline for a NuGet package solution. + +trigger: + branches: + include: [ '*' ] + exclude: [ 'refs/tags/*' ] + +variables: + BuildConfiguration: Release + DotNetCoreVersion: 6.0.x + +stages: +- stage: BuildAndTest + jobs: + - job: BuildAndTest + pool: + name: Default + steps: + - checkout: self + persistCredentials: 'true' + clean: true + # Install the desired .NET SDK. + - task: UseDotNet@2 + displayName: 'Acquire .NET SDK' + inputs: + packageType: 'sdk' + version: $(DotNetCoreVersion) + includePreviewVersions: false + # Build all projects. + - task: DotNetCoreCLI@2 + displayName: 'Build Projects' + inputs: + projects: '**/*.csproj' + arguments: '--configuration $(BuildConfiguration)' + # Run all available tests. + - task: DotNetCoreCLI@2 + displayName: 'Execute Tests' + inputs: + command: test + projects: '**/*Tests/*.csproj' + arguments: '--configuration $(BuildConfiguration)' + nobuild: true + # Create the NuGet packages. + - task: DotNetCoreCLI@2 + displayName: 'Pack Packages' + inputs: + command: 'pack' + packagesToPack: 'src/**/*.csproj' + nobuild: true + verbosityPack: Minimal + includesymbols: false + # Copy created NuGet packages to the builds artifacts directory. + - task: PublishBuildArtifacts@1 + displayName: 'Publish Package Artifacts' + inputs: + PathtoPublish: '$(Build.ArtifactStagingDirectory)' + ArtifactName: 'packages' + publishLocation: 'Container' + +- stage: PublishPackages + dependsOn: BuildAndTest + # Only publish packages for main branch. + condition: and(succeeded('BuildAndTest'), eq(variables['Build.SourceBranch'], 'refs/heads/main')) + jobs: + - job: PublishPackages + pool: + name: Default + steps: + # Download the created packages. + - task: DownloadBuildArtifacts@0 + displayName: 'Download NuGet Packges' + inputs: + buildType: 'current' + downloadType: 'single' + artifactName: 'packages' + downloadPath: '$(System.ArtifactsDirectory)' + # Publish the NuGet packages to the package feed. + - task: DotNetCoreCLI@2 + displayName: Push Nuget Package + inputs: + command: custom + custom: nuget + arguments: > + push $(System.ArtifactsDirectory)/**/*.nupkg + -s https://api.nuget.org/v3/index.json + -k $(NuGetApiKey) diff --git a/icon.png b/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..f35c53be15a469e815032fe194c09bb55c6ccf31 GIT binary patch literal 6680 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 KN0n + + + latest + disable + disable + true + true + + + + Fluxera Software Development GmbH + Fluxera Software Foundation + Copyright © 2014-2022 Fluxera Software Development GmbH. All rights reserved. + + + + Matthias Gernand + https://github.com/fluxera/Fluxera.Guard + https://github.com/fluxera/Fluxera.Guard + icon.png + README.md + false + false + en + git + MIT + true + + + + + + + + \ No newline at end of file diff --git a/src/Fluxera.StronglyTypedId.EntityFrameworkCore/Fluxera.StronglyTypedId.EntityFrameworkCore.csproj b/src/Fluxera.StronglyTypedId.EntityFrameworkCore/Fluxera.StronglyTypedId.EntityFrameworkCore.csproj new file mode 100644 index 0000000..a65b8dd --- /dev/null +++ b/src/Fluxera.StronglyTypedId.EntityFrameworkCore/Fluxera.StronglyTypedId.EntityFrameworkCore.csproj @@ -0,0 +1,37 @@ + + + + net6.0 + + + + Fluxera.StronglyTypedId.EntityFrameworkCore + A libary that provides serializer support for EF Core for strongly-typed IDs. + fluxera;library;ddd;value-object;ef-core + + + + + true + \ + + + true + \ + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + \ No newline at end of file diff --git a/src/Fluxera.StronglyTypedId.EntityFrameworkCore/ModelBuilderExtensions.cs b/src/Fluxera.StronglyTypedId.EntityFrameworkCore/ModelBuilderExtensions.cs new file mode 100644 index 0000000..77a69be --- /dev/null +++ b/src/Fluxera.StronglyTypedId.EntityFrameworkCore/ModelBuilderExtensions.cs @@ -0,0 +1,53 @@ +namespace Fluxera.StronglyTypedId.EntityFrameworkCore +{ + using System; + using System.Collections.Generic; + using System.Linq; + using System.Reflection; + using Fluxera.Guards; + using JetBrains.Annotations; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Metadata; + using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + + /// + /// Extension methods for the type. + /// + [PublicAPI] + public static class ModelBuilderExtensions + { + /// + /// Configure the module builder to use the . + /// + /// + public static void UseStronglyTypedId(this ModelBuilder modelBuilder) + { + Guard.Against.Null(modelBuilder, nameof(modelBuilder)); + + foreach(IMutableEntityType entityType in modelBuilder.Model.GetEntityTypes()) + { + IEnumerable properties = entityType + .ClrType + .GetProperties() + .Where(type => type.PropertyType.IsStronglyTypedId()); + + foreach(PropertyInfo property in properties) + { + Type enumerationType = property.PropertyType; + Type valueType = enumerationType.GetValueType(); + + Type converterTypeTemplate = typeof(StronglyTypedIdConverter<,>); + + Type converterType = converterTypeTemplate.MakeGenericType(enumerationType, valueType); + + ValueConverter converter = (ValueConverter)Activator.CreateInstance(converterType); + + modelBuilder + .Entity(entityType.ClrType) + .Property(property.Name) + .HasConversion(converter); + } + } + } + } +} diff --git a/src/Fluxera.StronglyTypedId.EntityFrameworkCore/StronglyTypedIdConverter.cs b/src/Fluxera.StronglyTypedId.EntityFrameworkCore/StronglyTypedIdConverter.cs new file mode 100644 index 0000000..665e6bc --- /dev/null +++ b/src/Fluxera.StronglyTypedId.EntityFrameworkCore/StronglyTypedIdConverter.cs @@ -0,0 +1,39 @@ +namespace Fluxera.StronglyTypedId.EntityFrameworkCore +{ + using System; + using System.Reflection; + using JetBrains.Annotations; + using Microsoft.EntityFrameworkCore.Storage.ValueConversion; + + /// + [PublicAPI] + public sealed class StronglyTypedIdConverter : ValueConverter + where TStronglyTypedId : StronglyTypedId + where TValue : IComparable + { + /// + /// Initializes a new instance of the type. + /// + public StronglyTypedIdConverter() + : base(valueObject => Serialize(valueObject), value => Deserialize(value)) + { + } + + private static TValue Serialize(TStronglyTypedId valueObject) + { + TValue value = valueObject.Value; + return value; + } + + private static TStronglyTypedId Deserialize(TValue value) + { + if(value is null) + { + return null; + } + + object instance = Activator.CreateInstance(typeof(TStronglyTypedId), BindingFlags.Public | BindingFlags.Instance, null, new object[] { value }, null); + return (TStronglyTypedId)instance; + } + } +} diff --git a/src/Fluxera.StronglyTypedId.JsonNet/CompositeContractResolver.cs b/src/Fluxera.StronglyTypedId.JsonNet/CompositeContractResolver.cs new file mode 100644 index 0000000..5974ab3 --- /dev/null +++ b/src/Fluxera.StronglyTypedId.JsonNet/CompositeContractResolver.cs @@ -0,0 +1,56 @@ +namespace Fluxera.StronglyTypedId.JsonNet +{ + using System; + using System.Collections; + using System.Collections.Generic; + using System.Linq; + using Fluxera.Guards; + using JetBrains.Annotations; + using Newtonsoft.Json.Serialization; + + /// + /// A that allows to have multiple other resolver instances added. + /// + [PublicAPI] + public sealed class CompositeContractResolver : IContractResolver, IEnumerable + { + private readonly IList contractResolvers = new List(); + private readonly DefaultContractResolver defaultContractResolver = new DefaultContractResolver(); + + /// + public JsonContract ResolveContract(Type type) + { + return this.contractResolvers + .Select(x => x.ResolveContract(type)) + .FirstOrDefault(); + } + + /// + public IEnumerator GetEnumerator() + { + return this.contractResolvers.GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return this.GetEnumerator(); + } + + /// + /// Add a resolver instance. + /// + /// + public void Add(IContractResolver contractResolver) + { + Guard.Against.Null(contractResolver); + + if(this.contractResolvers.Contains(this.defaultContractResolver)) + { + this.contractResolvers.Remove(this.defaultContractResolver); + } + + this.contractResolvers.Add(contractResolver); + this.contractResolvers.Add(this.defaultContractResolver); + } + } +} diff --git a/src/Fluxera.StronglyTypedId.JsonNet/Fluxera.StronglyTypedId.JsonNet.csproj b/src/Fluxera.StronglyTypedId.JsonNet/Fluxera.StronglyTypedId.JsonNet.csproj new file mode 100644 index 0000000..3d46032 --- /dev/null +++ b/src/Fluxera.StronglyTypedId.JsonNet/Fluxera.StronglyTypedId.JsonNet.csproj @@ -0,0 +1,37 @@ + + + + netstandard2.1 + + + + Fluxera.StronglyTypedId.JsonNet + A libary that provides serializer support for JSON.NET for strongly-typed IDs. + fluxera;library;ddd;value-object;json + + + + + true + \ + + + true + \ + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + diff --git a/src/Fluxera.StronglyTypedId.JsonNet/JsonSerializerSettingsExtensions.cs b/src/Fluxera.StronglyTypedId.JsonNet/JsonSerializerSettingsExtensions.cs new file mode 100644 index 0000000..2a4be86 --- /dev/null +++ b/src/Fluxera.StronglyTypedId.JsonNet/JsonSerializerSettingsExtensions.cs @@ -0,0 +1,24 @@ +namespace Fluxera.StronglyTypedId.JsonNet +{ + using JetBrains.Annotations; + using Newtonsoft.Json; + + /// + /// Extension methods for the type. + /// + [PublicAPI] + public static class JsonSerializerSettingsExtensions + { + /// + /// Configure the serializer to use the . + /// + /// + public static void UseStronglyTypedId(this JsonSerializerSettings settings) + { + settings.ContractResolver = new CompositeContractResolver + { + new StronglyTypedIdContractResolver() + }; + } + } +} diff --git a/src/Fluxera.StronglyTypedId.JsonNet/StronglyTypedIdContractResolver.cs b/src/Fluxera.StronglyTypedId.JsonNet/StronglyTypedIdContractResolver.cs new file mode 100644 index 0000000..8fa7b72 --- /dev/null +++ b/src/Fluxera.StronglyTypedId.JsonNet/StronglyTypedIdContractResolver.cs @@ -0,0 +1,27 @@ +namespace Fluxera.StronglyTypedId.JsonNet +{ + using System; + using JetBrains.Annotations; + using Newtonsoft.Json; + using Newtonsoft.Json.Serialization; + + /// + [PublicAPI] + public sealed class StronglyTypedIdContractResolver : DefaultContractResolver + { + /// + protected override JsonConverter ResolveContractConverter(Type objectType) + { + if(objectType.IsStronglyTypedId()) + { + Type valueType = objectType.GetValueType(); + Type converterTypeTemplate = typeof(StronglyTypedIdConverter<,>); + Type converterType = converterTypeTemplate.MakeGenericType(objectType, valueType); + + return (JsonConverter)Activator.CreateInstance(converterType); + } + + return base.ResolveContractConverter(objectType); + } + } +} diff --git a/src/Fluxera.StronglyTypedId.JsonNet/StronglyTypedIdConverter.cs b/src/Fluxera.StronglyTypedId.JsonNet/StronglyTypedIdConverter.cs new file mode 100644 index 0000000..d6749ab --- /dev/null +++ b/src/Fluxera.StronglyTypedId.JsonNet/StronglyTypedIdConverter.cs @@ -0,0 +1,46 @@ +namespace Fluxera.StronglyTypedId.JsonNet +{ + using System; + using System.Reflection; + using JetBrains.Annotations; + using Newtonsoft.Json; + + /// + [PublicAPI] + public sealed class StronglyTypedIdConverter : JsonConverter + where TStronglyTypedId : StronglyTypedId + where TValue : IComparable + { + /// + public override bool CanWrite => true; + + /// + public override bool CanRead => true; + + /// + public override void WriteJson(JsonWriter writer, TStronglyTypedId value, JsonSerializer serializer) + { + if(value is null) + { + writer.WriteNull(); + } + else + { + writer.WriteValue(value.Value); + } + } + + /// + public override TStronglyTypedId ReadJson(JsonReader reader, Type objectType, TStronglyTypedId existingValue, bool hasExistingValue, JsonSerializer serializer) + { + if(reader.TokenType == JsonToken.Null) + { + return null; + } + + TValue value = serializer.Deserialize(reader); + object instance = Activator.CreateInstance(objectType, BindingFlags.Public | BindingFlags.Instance, null, new object[] { value }, null); + return (TStronglyTypedId)instance; + } + } +} diff --git a/src/Fluxera.StronglyTypedId.LiteDB/BsonMapperExtensions.cs b/src/Fluxera.StronglyTypedId.LiteDB/BsonMapperExtensions.cs new file mode 100644 index 0000000..66fbdf8 --- /dev/null +++ b/src/Fluxera.StronglyTypedId.LiteDB/BsonMapperExtensions.cs @@ -0,0 +1,40 @@ +namespace Fluxera.StronglyTypedId.LiteDB +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Fluxera.Guards; + using global::LiteDB; + using JetBrains.Annotations; + + /// + /// Extension methods for the type. + /// + [PublicAPI] + public static class BsonMapperExtensions + { + /// + /// Configure the mapper to use the . + /// + /// + /// + public static BsonMapper UseStronglyTypedId(this BsonMapper mapper) + { + Guard.Against.Null(mapper); + + IEnumerable stronglyTypedIdTypes = AppDomain.CurrentDomain + .GetAssemblies() + .SelectMany(x => x.GetTypes()) + .Where(x => x.IsStronglyTypedId()); + + foreach(Type stronglyTypedIdType in stronglyTypedIdTypes) + { + mapper.RegisterType(stronglyTypedIdType, + StronglyTypedIdConverter.Serialize(stronglyTypedIdType), + StronglyTypedIdConverter.Deserialize(stronglyTypedIdType)); + } + + return mapper; + } + } +} diff --git a/src/Fluxera.StronglyTypedId.LiteDB/Fluxera.StronglyTypedId.LiteDB.csproj b/src/Fluxera.StronglyTypedId.LiteDB/Fluxera.StronglyTypedId.LiteDB.csproj new file mode 100644 index 0000000..6de1731 --- /dev/null +++ b/src/Fluxera.StronglyTypedId.LiteDB/Fluxera.StronglyTypedId.LiteDB.csproj @@ -0,0 +1,37 @@ + + + + netstandard2.1 + + + + Fluxera.StronglyTypedId.LiteDB + A libary that provides serializer support for LiteDB for strongly-typed IDs. + fluxera;library;ddd;value-object;json;litedb + + + + + true + \ + + + true + \ + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + diff --git a/src/Fluxera.StronglyTypedId.LiteDB/StronglyTypedIdConverter.cs b/src/Fluxera.StronglyTypedId.LiteDB/StronglyTypedIdConverter.cs new file mode 100644 index 0000000..6b5865b --- /dev/null +++ b/src/Fluxera.StronglyTypedId.LiteDB/StronglyTypedIdConverter.cs @@ -0,0 +1,51 @@ +namespace Fluxera.StronglyTypedId.LiteDB +{ + using System; + using System.Reflection; + using global::LiteDB; + using JetBrains.Annotations; + + /// + /// A converter for primitive value objects. + /// + [PublicAPI] + public static class StronglyTypedIdConverter + { + /// + /// Serialize the given ID instance. + /// + /// + /// + public static Func Serialize(Type stronglyTypedIdType) + { + return obj => + { + PropertyInfo property = stronglyTypedIdType.GetProperty("Value", BindingFlags.Public | BindingFlags.Instance); + object value = property?.GetValue(obj); + + BsonValue bsonValue = new BsonValue(value); + return bsonValue; + }; + } + + /// + /// Deserialize a ID instance from the given bson value. + /// + /// + /// + public static Func Deserialize(Type stronglyTypedIdType) + { + return bson => + { + if(bson.IsNull) + { + return null; + } + + object value = bson.RawValue; + object instance = Activator.CreateInstance(stronglyTypedIdType, BindingFlags.Public | BindingFlags.Instance, null, new object[] { value }, null); + return instance; + }; + } + } +} diff --git a/src/Fluxera.StronglyTypedId.MongoDB/ConventionPackExtensions.cs b/src/Fluxera.StronglyTypedId.MongoDB/ConventionPackExtensions.cs new file mode 100644 index 0000000..8b02c82 --- /dev/null +++ b/src/Fluxera.StronglyTypedId.MongoDB/ConventionPackExtensions.cs @@ -0,0 +1,24 @@ +namespace Fluxera.StronglyTypedId.MongoDB +{ + using global::MongoDB.Bson.Serialization.Conventions; + using JetBrains.Annotations; + + /// + /// Extension methods for the type. + /// + [PublicAPI] + public static class ConventionPackExtensions + { + /// + /// Configure the serializer to use the . + /// + /// + /// + public static ConventionPack UseStronglyTypedId(this ConventionPack pack) + { + pack.Add(new StronglyTypedIdConvention()); + + return pack; + } + } +} diff --git a/src/Fluxera.StronglyTypedId.MongoDB/Fluxera.StronglyTypedId.MongoDB.csproj b/src/Fluxera.StronglyTypedId.MongoDB/Fluxera.StronglyTypedId.MongoDB.csproj new file mode 100644 index 0000000..cbb6895 --- /dev/null +++ b/src/Fluxera.StronglyTypedId.MongoDB/Fluxera.StronglyTypedId.MongoDB.csproj @@ -0,0 +1,37 @@ + + + + netstandard2.1 + + + + Fluxera.StronglyTypedId.MongoDB + A libary that provides serializer support for MongoDB for strongly-typed IDs. + fluxera;library;ddd;value-object;json;mongodb + + + + + true + \ + + + true + \ + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + diff --git a/src/Fluxera.StronglyTypedId.MongoDB/StronglyTypedId.cs b/src/Fluxera.StronglyTypedId.MongoDB/StronglyTypedId.cs new file mode 100644 index 0000000..25268cc --- /dev/null +++ b/src/Fluxera.StronglyTypedId.MongoDB/StronglyTypedId.cs @@ -0,0 +1,47 @@ +namespace Fluxera.StronglyTypedId.MongoDB +{ + using System; + using System.Reflection; + using global::MongoDB.Bson; + using global::MongoDB.Bson.Serialization; + using global::MongoDB.Bson.Serialization.Serializers; + using JetBrains.Annotations; + + /// + /// A serializer that handles instances of strongly-typed IDs. + /// + /// + /// + [PublicAPI] + public sealed class StronglyTypedId : SerializerBase + where TStronglyTypedId : StronglyTypedId.StronglyTypedId + where TValue : IComparable + { + /// + public override void Serialize(BsonSerializationContext context, BsonSerializationArgs args, TStronglyTypedId value) + { + if(value is null) + { + context.Writer.WriteNull(); + } + else + { + BsonSerializer.Serialize(context.Writer, value.Value); + } + } + + /// + public override TStronglyTypedId Deserialize(BsonDeserializationContext context, BsonDeserializationArgs args) + { + if(context.Reader.CurrentBsonType == BsonType.Null) + { + context.Reader.ReadNull(); + return null; + } + + TValue value = BsonSerializer.Deserialize(context.Reader); + object instance = Activator.CreateInstance(args.NominalType, BindingFlags.Public | BindingFlags.Instance, null, new object[] { value }, null); + return (TStronglyTypedId)instance; + } + } +} diff --git a/src/Fluxera.StronglyTypedId.MongoDB/StronglyTypedIdConvention.cs b/src/Fluxera.StronglyTypedId.MongoDB/StronglyTypedIdConvention.cs new file mode 100644 index 0000000..583aa7c --- /dev/null +++ b/src/Fluxera.StronglyTypedId.MongoDB/StronglyTypedIdConvention.cs @@ -0,0 +1,31 @@ +namespace Fluxera.StronglyTypedId.MongoDB +{ + using System; + using global::MongoDB.Bson.Serialization; + using global::MongoDB.Bson.Serialization.Conventions; + using JetBrains.Annotations; + + /// + /// A convention that enables support for serializing strongly-typed IDs. + /// + [PublicAPI] + public sealed class StronglyTypedIdConvention : ConventionBase, IMemberMapConvention + { + /// + public void Apply(BsonMemberMap memberMap) + { + Type originalMemberType = memberMap.MemberType; + Type memberType = Nullable.GetUnderlyingType(originalMemberType) ?? originalMemberType; + + if(memberType.IsStronglyTypedId()) + { + Type valueType = memberType.GetValueType(); + Type serializerTypeTemplate = typeof(StronglyTypedId<,>); + Type serializerType = serializerTypeTemplate.MakeGenericType(memberType, valueType); + + IBsonSerializer enumerationSerializer = (IBsonSerializer)Activator.CreateInstance(serializerType); + memberMap.SetSerializer(enumerationSerializer); + } + } + } +} diff --git a/src/Fluxera.StronglyTypedId.SystemTextJson/Fluxera.StronglyTypedId.SystemTextJson.csproj b/src/Fluxera.StronglyTypedId.SystemTextJson/Fluxera.StronglyTypedId.SystemTextJson.csproj new file mode 100644 index 0000000..a17c396 --- /dev/null +++ b/src/Fluxera.StronglyTypedId.SystemTextJson/Fluxera.StronglyTypedId.SystemTextJson.csproj @@ -0,0 +1,37 @@ + + + + netstandard2.1 + + + + Fluxera.StronglyTypedId.SystemTextJson + A libary that provides serializer support for System.Text.Json for strongly-typed IDs. + fluxera;library;ddd;value-object;json + + + + + true + \ + + + true + \ + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + diff --git a/src/Fluxera.StronglyTypedId.SystemTextJson/JsonSerializerOptionsExtensions.cs b/src/Fluxera.StronglyTypedId.SystemTextJson/JsonSerializerOptionsExtensions.cs new file mode 100644 index 0000000..c56356e --- /dev/null +++ b/src/Fluxera.StronglyTypedId.SystemTextJson/JsonSerializerOptionsExtensions.cs @@ -0,0 +1,21 @@ +namespace Fluxera.StronglyTypedId.SystemTextJson +{ + using System.Text.Json; + using JetBrains.Annotations; + + /// + /// Extension methods for the type. + /// + [PublicAPI] + public static class JsonSerializerOptionsExtensions + { + /// + /// Configures the serializer to use the . + /// + /// + public static void UseStronglyTypedId(this JsonSerializerOptions options) + { + options.Converters.Add(new StronglyTypedIdJsonConverterFactory()); + } + } +} diff --git a/src/Fluxera.StronglyTypedId.SystemTextJson/StronglyTypedIdConverter.cs b/src/Fluxera.StronglyTypedId.SystemTextJson/StronglyTypedIdConverter.cs new file mode 100644 index 0000000..2c7ce59 --- /dev/null +++ b/src/Fluxera.StronglyTypedId.SystemTextJson/StronglyTypedIdConverter.cs @@ -0,0 +1,41 @@ +namespace Fluxera.StronglyTypedId.SystemTextJson +{ + using System; + using System.Reflection; + using System.Text.Json; + using System.Text.Json.Serialization; + using JetBrains.Annotations; + + /// + [PublicAPI] + public sealed class StronglyTypedIdConverter : JsonConverter + where TStronglyTypedId : StronglyTypedId + where TValue : IComparable + { + /// + public override void Write(Utf8JsonWriter writer, TStronglyTypedId value, JsonSerializerOptions options) + { + if(value is null) + { + writer.WriteNullValue(); + } + else + { + JsonSerializer.Serialize(writer, value.Value, options); + } + } + + /// + public override TStronglyTypedId Read(ref Utf8JsonReader reader, Type typeToConvert, JsonSerializerOptions options) + { + if(reader.TokenType == JsonTokenType.Null) + { + return null; + } + + TValue value = JsonSerializer.Deserialize(ref reader, options); + object instance = Activator.CreateInstance(typeToConvert, BindingFlags.Public | BindingFlags.Instance, null, new object[] { value }, null); + return (TStronglyTypedId)instance; + } + } +} diff --git a/src/Fluxera.StronglyTypedId.SystemTextJson/StronglyTypedIdJsonConverterFactory.cs b/src/Fluxera.StronglyTypedId.SystemTextJson/StronglyTypedIdJsonConverterFactory.cs new file mode 100644 index 0000000..2138922 --- /dev/null +++ b/src/Fluxera.StronglyTypedId.SystemTextJson/StronglyTypedIdJsonConverterFactory.cs @@ -0,0 +1,29 @@ +namespace Fluxera.StronglyTypedId.SystemTextJson +{ + using System; + using System.Text.Json; + using System.Text.Json.Serialization; + using JetBrains.Annotations; + + /// + [PublicAPI] + public sealed class StronglyTypedIdJsonConverterFactory : JsonConverterFactory + { + /// + public override bool CanConvert(Type typeToConvert) + { + bool isPrimitiveValueObject = typeToConvert.IsStronglyTypedId(); + return isPrimitiveValueObject; + } + + /// + public override JsonConverter CreateConverter(Type typeToConvert, JsonSerializerOptions options) + { + Type valueType = typeToConvert.GetValueType(); + Type converterTypeTemplate = typeof(StronglyTypedIdConverter<,>); + Type converterType = converterTypeTemplate.MakeGenericType(typeToConvert, valueType); + + return (JsonConverter)Activator.CreateInstance(converterType); + } + } +} diff --git a/src/Fluxera.StronglyTypedId/Fluxera.StronglyTypedId.csproj b/src/Fluxera.StronglyTypedId/Fluxera.StronglyTypedId.csproj new file mode 100644 index 0000000..b6b4b74 --- /dev/null +++ b/src/Fluxera.StronglyTypedId/Fluxera.StronglyTypedId.csproj @@ -0,0 +1,33 @@ + + + + netstandard2.1 + + + + Fluxera.StronglyTypedId + A strongly-typed ID implementation. + fluxera;library;ddd;strongly-typed;identifier;id + + + + + true + \ + + + true + \ + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + \ No newline at end of file diff --git a/src/Fluxera.StronglyTypedId/IStronglyTypedId.cs b/src/Fluxera.StronglyTypedId/IStronglyTypedId.cs new file mode 100644 index 0000000..c9c801b --- /dev/null +++ b/src/Fluxera.StronglyTypedId/IStronglyTypedId.cs @@ -0,0 +1,19 @@ +namespace Fluxera.StronglyTypedId +{ + using System; + using JetBrains.Annotations; + + /// + /// A contract for strongly-typed IDs. + /// + /// + /// + [PublicAPI] + public interface IStronglyTypedId : IComparable, IEquatable + { + /// + /// Gets the underlying value of the strongly-typed ID. + /// + public TKey Value { get; } + } +} diff --git a/src/Fluxera.StronglyTypedId/PropertyAccessor.cs b/src/Fluxera.StronglyTypedId/PropertyAccessor.cs new file mode 100644 index 0000000..55fa542 --- /dev/null +++ b/src/Fluxera.StronglyTypedId/PropertyAccessor.cs @@ -0,0 +1,61 @@ +namespace Fluxera.StronglyTypedId +{ + using System; + using System.Collections.Concurrent; + using System.Linq; + using System.Reflection; + using Fluxera.Guards; + using JetBrains.Annotations; + + [PublicAPI] + internal sealed class PropertyAccessor + { + private static readonly ConcurrentDictionary PropertyAccessorsMap = new ConcurrentDictionary(); + + private static readonly MethodInfo CallInnerDelegateMethod = typeof(PropertyAccessor).GetMethod(nameof(CallInnerDelegate), BindingFlags.NonPublic | BindingFlags.Static)!; + + private PropertyAccessor(string propertyName, Func getterFunc) + { + Guard.Against.NullOrWhiteSpace(propertyName, nameof(propertyName)); + Guard.Against.Null(getterFunc, nameof(getterFunc)); + + this.PropertyName = propertyName; + this.GetterFunc = getterFunc; + } + + public string PropertyName { get; } + + private Func GetterFunc { get; } + + public object Invoke(object target) + { + return this.GetterFunc.Invoke(target); + } + + public static PropertyAccessor[] GetPropertyAccessors(Type type) + { + return PropertyAccessorsMap + .GetOrAdd(type, _ => type + .GetProperties() + .Select(property => + { + MethodInfo getMethod = property.GetMethod; + Type declaringType = property.DeclaringType; + Type propertyType = property.PropertyType; + + Type getMethodDelegateType = typeof(Func<,>).MakeGenericType(declaringType, propertyType); + Delegate getMethodDelegate = getMethod.CreateDelegate(getMethodDelegateType); + MethodInfo callInnerGenericMethodWithTypes = CallInnerDelegateMethod.MakeGenericMethod(declaringType, propertyType); + Func getter = (Func)callInnerGenericMethodWithTypes.Invoke(null, new object[] { getMethodDelegate }); + + return new PropertyAccessor(property.Name, getter); + }).ToArray()); + } + + // Called via reflection. + private static Func CallInnerDelegate(Func func) + { + return instance => func.Invoke((T)instance); + } + } +} diff --git a/src/Fluxera.StronglyTypedId/StronglyTypedId.cs b/src/Fluxera.StronglyTypedId/StronglyTypedId.cs new file mode 100644 index 0000000..9b1a9f2 --- /dev/null +++ b/src/Fluxera.StronglyTypedId/StronglyTypedId.cs @@ -0,0 +1,150 @@ +namespace Fluxera.StronglyTypedId +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Fluxera.Guards; + using Fluxera.Utilities.Extensions; + using JetBrains.Annotations; + + /// + /// A base class for any strongly-typed ID. + /// + /// The type of the strongly-typed ID. + /// The type of the IDs value. + [PublicAPI] + public abstract class StronglyTypedId : IStronglyTypedId + where TStronglyTypedId : StronglyTypedId + where TValue : IComparable + { + /// + /// To ensure hashcode uniqueness, a carefully selected random number multiplier + /// is used within the calculation. + /// + /// + /// See http://computinglife.wordpress.com/2008/11/20/why-do-hash-functions-use-prime-numbers/ + /// + private const int HashMultiplier = 37; + + static StronglyTypedId() + { + Type valueType = typeof(TValue); + bool isIdentifierType = valueType.IsNumeric() || valueType == typeof(string) || valueType == typeof(Guid); + + Guard.Against.False(isIdentifierType, nameof(Value), "The value of a strongly-typed ID must be a numeric, string or Guid type."); + } + + /// + /// Initializes a new instance of the type. + /// + /// + protected StronglyTypedId(TValue value) + { + Guard.Against.Null(value); + + this.Value = value; + } + + /// + /// Gets or sets value if the strongly-typed ID. + /// + public TValue Value { get; private set; } + + /// + public bool Equals(TStronglyTypedId other) + { + return this.Equals(other as object); + } + + /// + public int CompareTo(TStronglyTypedId other) + { + return (this.Value, other.Value) switch + { + (null, null) => 0, + (null, _) => -1, + (_, null) => 1, + (_, _) => this.Value.CompareTo(other.Value) + }; + } + + /// + /// Checks if the given IDs are equal. + /// + public static bool operator ==(StronglyTypedId left, StronglyTypedId right) + { + if(left is null) + { + return right is null; + } + + return left.Equals(right); + } + + /// + /// Checks if the given IDs are not equal. + /// + public static bool operator !=(StronglyTypedId left, StronglyTypedId right) + { + return !(left == right); + } + + /// + public sealed override bool Equals(object obj) + { + if(obj is null) + { + return false; + } + + if(object.ReferenceEquals(this, obj)) + { + return true; + } + + StronglyTypedId other = obj as StronglyTypedId; + return other != null + && this.GetType() == other.GetType() + && this.GetEqualityComponents().SequenceEqual(other.GetEqualityComponents()); + } + + /// + public sealed override int GetHashCode() + { + unchecked + { + // It is possible for two objects to return the same hash code based on + // identically valued properties, even if they are of different types, + // so we include the value object type in the hash calculation. + int hashCode = this.GetType().GetHashCode(); + + foreach(object component in this.GetEqualityComponents()) + { + if(component != null) + { + hashCode = hashCode * HashMultiplier ^ component.GetHashCode(); + } + } + + return hashCode; + } + } + + /// + public sealed override string ToString() + { + return this.Value.ToString(); + } + + /// + /// Gets all components of the value object that are used for equality.
+ /// The default implementation get all properties via reflection. One + /// can at any time override this behavior with a manual or custom implementation. + ///
+ /// The components to use for equality. + private IEnumerable GetEqualityComponents() + { + yield return this.Value; + } + } +} diff --git a/src/Fluxera.StronglyTypedId/StronglyTypedIdExtensions.cs b/src/Fluxera.StronglyTypedId/StronglyTypedIdExtensions.cs new file mode 100644 index 0000000..ee1b93b --- /dev/null +++ b/src/Fluxera.StronglyTypedId/StronglyTypedIdExtensions.cs @@ -0,0 +1,60 @@ +namespace Fluxera.StronglyTypedId +{ + using System; + using JetBrains.Annotations; + + /// + /// Extension methods for the type. + /// + [PublicAPI] + public static class StronglyTypedIdExtensions + { + /// + /// Checks the given type if it is an . + /// + /// + /// True, if the type is an enumeration, false otherwise. + public static bool IsStronglyTypedId(this Type type) + { + if(type is null || type.IsAbstract || type.IsGenericTypeDefinition) + { + return false; + } + + do + { + if(type.IsGenericType && type.GetGenericTypeDefinition() == typeof(StronglyTypedId<,>)) + { + return true; + } + + type = type.BaseType; + } + while(type is not null); + + return false; + } + + /// + /// Gets the type of the generic value parameter from the base type. + /// + /// + /// The type of the value. + public static Type GetValueType(this Type type) + { + do + { + if(type != null && type.IsGenericType && type.GetGenericTypeDefinition() == typeof(StronglyTypedId<,>)) + { + Type valueType = type.GetGenericArguments()[1]; + return valueType; + } + + type = type?.BaseType; + } + while(type is not null); + + return null!; + } + } +} diff --git a/tests/.gitkeep b/tests/.gitkeep new file mode 100644 index 0000000..5f28270 --- /dev/null +++ b/tests/.gitkeep @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/Directory.Build.props b/tests/Directory.Build.props new file mode 100644 index 0000000..8532963 --- /dev/null +++ b/tests/Directory.Build.props @@ -0,0 +1,17 @@ + + + + latest + disable + disable + false + false + + + + Fluxera Software Development GmbH + Fluxera Software Foundation + Copyright © 2014-2022 Fluxera Software Development GmbH. All rights reserved. + + + \ No newline at end of file diff --git a/tests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests/DbContextFactory.cs b/tests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests/DbContextFactory.cs new file mode 100644 index 0000000..780c57a --- /dev/null +++ b/tests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests/DbContextFactory.cs @@ -0,0 +1,21 @@ +namespace Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests +{ + using System.Linq; + + public static class DbContextFactory + { + public static TestDbContext Generate(int seedCount) + { + PersonFactory.Initialize(); + + TestDbContext context = new TestDbContext + { + SeedData = PersonFactory.Generate(seedCount).ToArray(), + }; + + context.Database.EnsureDeleted(); + context.Database.EnsureCreated(); + return context; + } + } +} diff --git a/tests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests.csproj b/tests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests.csproj new file mode 100644 index 0000000..17222e6 --- /dev/null +++ b/tests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests.csproj @@ -0,0 +1,24 @@ + + + + net6.0 + + false + + + + + + + + + + + + + + + + + + diff --git a/tests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests/ModelBuilderExtensionsTests.cs b/tests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests/ModelBuilderExtensionsTests.cs new file mode 100644 index 0000000..52911cd --- /dev/null +++ b/tests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests/ModelBuilderExtensionsTests.cs @@ -0,0 +1,21 @@ +namespace Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests +{ + using System.Collections.Generic; + using System.Linq; + using FluentAssertions; + using NUnit.Framework; + + [TestFixture] + public class ModelBuilderExtensionsTests + { + [Test] + public void ShouldUseNameConverter() + { + int seedCount = 1; + using TestDbContext context = DbContextFactory.Generate(seedCount); + List people = context.Set().ToList(); + + people.Should().BeEquivalentTo(context.SeedData); + } + } +} diff --git a/tests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests/NoModelCacheKeyFactory.cs b/tests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests/NoModelCacheKeyFactory.cs new file mode 100644 index 0000000..c2a68d8 --- /dev/null +++ b/tests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests/NoModelCacheKeyFactory.cs @@ -0,0 +1,17 @@ +namespace Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests +{ + using System; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Infrastructure; + + /// + /// Creates a unique cache key every time to prevent caching. + /// + public class NoModelCacheKeyFactory : IModelCacheKeyFactory + { + public object Create(DbContext context) + { + return Guid.NewGuid(); + } + } +} diff --git a/tests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests/Person.cs b/tests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests/Person.cs new file mode 100644 index 0000000..4880f59 --- /dev/null +++ b/tests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests/Person.cs @@ -0,0 +1,12 @@ +namespace Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests +{ + using System.ComponentModel.DataAnnotations; + + public class Person + { + [Key] + public string Id { get; set; } + + public PersonId PersonId { get; set; } + } +} diff --git a/tests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests/PersonFactory.cs b/tests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests/PersonFactory.cs new file mode 100644 index 0000000..717356c --- /dev/null +++ b/tests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests/PersonFactory.cs @@ -0,0 +1,22 @@ +namespace Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests +{ + using System; + using System.Collections.Generic; + using Bogus; + + public static class PersonFactory + { + public static IList Generate(int count) + { + return new Faker() + .RuleFor(e => e.Id, (f, e) => f.Random.Guid().ToString()) + .RuleFor(e => e.PersonId, (f, e) => new PersonId("12345")) + .Generate(count); + } + + public static void Initialize() + { + Randomizer.Seed = new Random(62392); + } + } +} diff --git a/tests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests/PersonId.cs b/tests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests/PersonId.cs new file mode 100644 index 0000000..7f60170 --- /dev/null +++ b/tests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests/PersonId.cs @@ -0,0 +1,10 @@ +namespace Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests +{ + public sealed class PersonId : StronglyTypedId + { + /// + public PersonId(string value) : base(value) + { + } + } +} diff --git a/tests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests/TestDbContext.cs b/tests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests/TestDbContext.cs new file mode 100644 index 0000000..ff08522 --- /dev/null +++ b/tests/Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests/TestDbContext.cs @@ -0,0 +1,67 @@ +namespace Fluxera.StronglyTypedId.EntityFrameworkCore.UnitTests +{ + using System; + using System.Collections.Generic; + using System.Linq; + using Microsoft.EntityFrameworkCore; + using Microsoft.EntityFrameworkCore.Infrastructure; + using Microsoft.EntityFrameworkCore.Metadata.Builders; + using Microsoft.Extensions.Logging; + + public class TestDbContext : DbContext + { + public bool IsLoggingSensitiveData { get; set; } + + public ILoggerFactory LoggerFactory { get; set; } + + public DbSet People { get; set; } + + public IEnumerable SeedData { get; set; } + + protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) + { + if(optionsBuilder == null) + { + throw new ArgumentNullException(nameof(optionsBuilder)); + } + + optionsBuilder.UseInMemoryDatabase("TestDatabase"); + + if(this.SeedData != null) + { + optionsBuilder.ReplaceService(); + } + + optionsBuilder.UseLoggerFactory(this.LoggerFactory); + if(this.IsLoggingSensitiveData) + { + optionsBuilder.EnableSensitiveDataLogging(); + } + + base.OnConfiguring(optionsBuilder); + } + + protected override void OnModelCreating(ModelBuilder modelBuilder) + { + if(modelBuilder == null) + { + throw new ArgumentNullException(nameof(modelBuilder)); + } + + if(this.SeedData != null) + { + IEnumerable types = this.SeedData.Select(x => x.GetType()).Distinct(); + foreach(Type type in types) + { + EntityTypeBuilder entityBuilder = modelBuilder.Entity(type); + object[] data = this.SeedData.Where(x => x.GetType() == type).ToArray(); + entityBuilder.HasData(data); + } + } + + modelBuilder.UseStronglyTypedId(); + + base.OnModelCreating(modelBuilder); + } + } +} diff --git a/tests/Fluxera.StronglyTypedId.JsonNet.UnitTests/ConverterTests.cs b/tests/Fluxera.StronglyTypedId.JsonNet.UnitTests/ConverterTests.cs new file mode 100644 index 0000000..ac8d044 --- /dev/null +++ b/tests/Fluxera.StronglyTypedId.JsonNet.UnitTests/ConverterTests.cs @@ -0,0 +1,52 @@ +namespace Fluxera.StronglyTypedId.JsonNet.UnitTests +{ + using FluentAssertions; + using Newtonsoft.Json; + using NUnit.Framework; + + [TestFixture] + public class ConverterTests + { + [SetUp] + public void SetUp() + { + JsonConvert.DefaultSettings = () => + { + JsonSerializerSettings settings = new JsonSerializerSettings + { + Formatting = Formatting.None + }; + settings.UseStronglyTypedId(); + return settings; + }; + } + + public class TestClass + { + public PersonId PersonId { get; set; } + } + + private static readonly TestClass TestInstance = new TestClass + { + PersonId = new PersonId("12345") + }; + + private static readonly string JsonString = @"{""PersonId"":""12345""}"; + + [Test] + public void ShouldDeserialize() + { + TestClass obj = JsonConvert.DeserializeObject(JsonString); + + ((object)obj.PersonId).Should().Be(new PersonId("12345")); + } + + [Test] + public void ShouldSerialize() + { + string json = JsonConvert.SerializeObject(TestInstance, Formatting.None); + + json.Should().Be(JsonString); + } + } +} diff --git a/tests/Fluxera.StronglyTypedId.JsonNet.UnitTests/Fluxera.StronglyTypedId.JsonNet.UnitTests.csproj b/tests/Fluxera.StronglyTypedId.JsonNet.UnitTests/Fluxera.StronglyTypedId.JsonNet.UnitTests.csproj new file mode 100644 index 0000000..6686bc6 --- /dev/null +++ b/tests/Fluxera.StronglyTypedId.JsonNet.UnitTests/Fluxera.StronglyTypedId.JsonNet.UnitTests.csproj @@ -0,0 +1,21 @@ + + + + net6.0 + false + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/tests/Fluxera.StronglyTypedId.JsonNet.UnitTests/PersonId.cs b/tests/Fluxera.StronglyTypedId.JsonNet.UnitTests/PersonId.cs new file mode 100644 index 0000000..1f3247c --- /dev/null +++ b/tests/Fluxera.StronglyTypedId.JsonNet.UnitTests/PersonId.cs @@ -0,0 +1,10 @@ +namespace Fluxera.StronglyTypedId.JsonNet.UnitTests +{ + public sealed class PersonId : StronglyTypedId + { + /// + public PersonId(string value) : base(value) + { + } + } +} diff --git a/tests/Fluxera.StronglyTypedId.LiteDB.UnitTests/ConverterTests.cs b/tests/Fluxera.StronglyTypedId.LiteDB.UnitTests/ConverterTests.cs new file mode 100644 index 0000000..2a65fc1 --- /dev/null +++ b/tests/Fluxera.StronglyTypedId.LiteDB.UnitTests/ConverterTests.cs @@ -0,0 +1,45 @@ +namespace Fluxera.StronglyTypedId.LiteDB.UnitTests +{ + using FluentAssertions; + using global::LiteDB; + using NUnit.Framework; + + [TestFixture] + public class ConverterTests + { + static ConverterTests() + { + BsonMapper.Global.UseStronglyTypedId(); + } + + public class TestClass + { + public PersonId PersonId { get; set; } + } + + private static readonly TestClass TestInstance = new TestClass + { + PersonId = new PersonId("12345") + }; + + private static readonly string JsonString = @"{""PersonId"":""12345""}"; + + [Test] + public void ShouldDeserialize() + { + BsonDocument doc = (BsonDocument)JsonSerializer.Deserialize(JsonString); + TestClass obj = BsonMapper.Global.ToObject(doc); + + obj.PersonId.Should().Be(new PersonId("12345")); + } + + [Test] + public void ShouldSerialize() + { + BsonDocument doc = BsonMapper.Global.ToDocument(TestInstance); + string json = JsonSerializer.Serialize(doc); + + json.Should().Be(JsonString); + } + } +} diff --git a/tests/Fluxera.StronglyTypedId.LiteDB.UnitTests/Fluxera.StronglyTypedId.LiteDB.UnitTests.csproj b/tests/Fluxera.StronglyTypedId.LiteDB.UnitTests/Fluxera.StronglyTypedId.LiteDB.UnitTests.csproj new file mode 100644 index 0000000..465079f --- /dev/null +++ b/tests/Fluxera.StronglyTypedId.LiteDB.UnitTests/Fluxera.StronglyTypedId.LiteDB.UnitTests.csproj @@ -0,0 +1,22 @@ + + + + net6.0 + + false + + + + + + + + + + + + + + + + diff --git a/tests/Fluxera.StronglyTypedId.LiteDB.UnitTests/PersonId.cs b/tests/Fluxera.StronglyTypedId.LiteDB.UnitTests/PersonId.cs new file mode 100644 index 0000000..890ca22 --- /dev/null +++ b/tests/Fluxera.StronglyTypedId.LiteDB.UnitTests/PersonId.cs @@ -0,0 +1,10 @@ +namespace Fluxera.StronglyTypedId.LiteDB.UnitTests +{ + public sealed class PersonId : StronglyTypedId + { + /// + public PersonId(string value) : base(value) + { + } + } +} diff --git a/tests/Fluxera.StronglyTypedId.MongoDB.UnitTests/ConverterTests.cs b/tests/Fluxera.StronglyTypedId.MongoDB.UnitTests/ConverterTests.cs new file mode 100644 index 0000000..9303cb0 --- /dev/null +++ b/tests/Fluxera.StronglyTypedId.MongoDB.UnitTests/ConverterTests.cs @@ -0,0 +1,47 @@ +namespace Fluxera.StronglyTypedId.MongoDB.UnitTests +{ + using FluentAssertions; + using global::MongoDB.Bson; + using global::MongoDB.Bson.Serialization; + using global::MongoDB.Bson.Serialization.Conventions; + using NUnit.Framework; + + [TestFixture] + public class ConverterTests + { + static ConverterTests() + { + ConventionPack pack = new ConventionPack(); + pack.UseStronglyTypedId(); + ConventionRegistry.Register("ConventionPack", pack, t => true); + } + + public class TestClass + { + public PersonId PersonId { get; set; } + } + + private static readonly TestClass TestInstance = new TestClass + { + PersonId = new PersonId("12345") + }; + + private static readonly string JsonString = @"{ ""PersonId"" : ""12345"" }"; + + [Test] + public void ShouldDeserialize() + { + TestClass obj = BsonSerializer.Deserialize(JsonString); + + obj.PersonId.Should().Be(new PersonId("12345")); + } + + [Test] + public void ShouldSerialize() + { + string json = TestInstance.ToJson(); + + json.Should().Be(JsonString); + } + } +} diff --git a/tests/Fluxera.StronglyTypedId.MongoDB.UnitTests/Fluxera.StronglyTypedId.MongoDB.UnitTests.csproj b/tests/Fluxera.StronglyTypedId.MongoDB.UnitTests/Fluxera.StronglyTypedId.MongoDB.UnitTests.csproj new file mode 100644 index 0000000..c249b8f --- /dev/null +++ b/tests/Fluxera.StronglyTypedId.MongoDB.UnitTests/Fluxera.StronglyTypedId.MongoDB.UnitTests.csproj @@ -0,0 +1,22 @@ + + + + net6.0 + + false + + + + + + + + + + + + + + + + diff --git a/tests/Fluxera.StronglyTypedId.MongoDB.UnitTests/PersonId.cs b/tests/Fluxera.StronglyTypedId.MongoDB.UnitTests/PersonId.cs new file mode 100644 index 0000000..b37efbf --- /dev/null +++ b/tests/Fluxera.StronglyTypedId.MongoDB.UnitTests/PersonId.cs @@ -0,0 +1,10 @@ +namespace Fluxera.StronglyTypedId.MongoDB.UnitTests +{ + public sealed class PersonId : StronglyTypedId.StronglyTypedId + { + /// + public PersonId(string value) : base(value) + { + } + } +} diff --git a/tests/Fluxera.StronglyTypedId.SystemTextJson.UnitTests/ConverterTests.cs b/tests/Fluxera.StronglyTypedId.SystemTextJson.UnitTests/ConverterTests.cs new file mode 100644 index 0000000..efd40ef --- /dev/null +++ b/tests/Fluxera.StronglyTypedId.SystemTextJson.UnitTests/ConverterTests.cs @@ -0,0 +1,46 @@ +namespace Fluxera.StronglyTypedId.SystemTextJson.UnitTests +{ + using System.Text.Json; + using FluentAssertions; + using NUnit.Framework; + + [TestFixture] + public class ConverterTests + { + private static readonly JsonSerializerOptions options; + + static ConverterTests() + { + options = new JsonSerializerOptions(); + options.UseStronglyTypedId(); + } + + public class TestClass + { + public PersonId PersonId { get; set; } + } + + private static readonly TestClass TestInstance = new TestClass + { + PersonId = new PersonId("12345") + }; + + private static readonly string JsonString = @"{""PersonId"":""12345""}"; + + [Test] + public void ShouldDeserialize() + { + TestClass obj = JsonSerializer.Deserialize(JsonString, options); + + obj.PersonId.Should().Be(new PersonId("12345")); + } + + [Test] + public void ShouldSerialize() + { + string json = JsonSerializer.Serialize(TestInstance, options); + + json.Should().Be(JsonString); + } + } +} diff --git a/tests/Fluxera.StronglyTypedId.SystemTextJson.UnitTests/Fluxera.StronglyTypedId.SystemTextJson.UnitTests.csproj b/tests/Fluxera.StronglyTypedId.SystemTextJson.UnitTests/Fluxera.StronglyTypedId.SystemTextJson.UnitTests.csproj new file mode 100644 index 0000000..d02ccac --- /dev/null +++ b/tests/Fluxera.StronglyTypedId.SystemTextJson.UnitTests/Fluxera.StronglyTypedId.SystemTextJson.UnitTests.csproj @@ -0,0 +1,22 @@ + + + + net6.0 + + false + + + + + + + + + + + + + + + + diff --git a/tests/Fluxera.StronglyTypedId.SystemTextJson.UnitTests/PersonId.cs b/tests/Fluxera.StronglyTypedId.SystemTextJson.UnitTests/PersonId.cs new file mode 100644 index 0000000..96798bb --- /dev/null +++ b/tests/Fluxera.StronglyTypedId.SystemTextJson.UnitTests/PersonId.cs @@ -0,0 +1,10 @@ +namespace Fluxera.StronglyTypedId.SystemTextJson.UnitTests +{ + public sealed class PersonId : StronglyTypedId + { + /// + public PersonId(string value) : base(value) + { + } + } +} diff --git a/tests/Fluxera.StronglyTypedId.UnitTests/EqualsOperatorsTests.cs b/tests/Fluxera.StronglyTypedId.UnitTests/EqualsOperatorsTests.cs new file mode 100644 index 0000000..d58da52 --- /dev/null +++ b/tests/Fluxera.StronglyTypedId.UnitTests/EqualsOperatorsTests.cs @@ -0,0 +1,73 @@ +namespace Fluxera.StronglyTypedId.UnitTests +{ + using System; + using System.Collections.Generic; + using FluentAssertions; + using Fluxera.StronglyTypedId.UnitTests.Model; + using NUnit.Framework; + + [TestFixture] + public class EqualsOperatorsTests + { + private static IEnumerable OperatorTestData = new List + { + new object[] + { + null!, + null!, + true + }, + new object[] + { + new CustomerId("12345"), + new CustomerId("12345"), + true + }, + new object[] + { + new CustomerId("12345"), + new CustomerId("12346"), + false + }, + new object[] + { + new CustomerId("12345"), + null!, + false + } + }; + + private static IEnumerable OperatorPrimitiveTestData = new List + { + new object[] + { + new CustomerId("12345"), + new CustomerId("12345"), + true + } + }; + + [Test] + [TestCaseSource(nameof(OperatorPrimitiveTestData))] + public void EqualOperatorPrimitiveShouldReturnExpectedValue(CustomerId first, CustomerId second, bool expected) + { + bool result = first == second; + result.Should().Be(expected); + } + + [Test] + [TestCaseSource(nameof(OperatorTestData))] + public void EqualOperatorShouldReturnExpectedValue(CustomerId first, CustomerId second, bool expected) + { + bool result = first == second; + result.Should().Be(expected); + } + + [Test] + public void ShouldNotAllowNullValue() + { + Action action = () => new CustomerId(null); + action.Should().Throw(); + } + } +} diff --git a/tests/Fluxera.StronglyTypedId.UnitTests/Fluxera.StronglyTypedId.UnitTests.csproj b/tests/Fluxera.StronglyTypedId.UnitTests/Fluxera.StronglyTypedId.UnitTests.csproj new file mode 100644 index 0000000..4fe6b5e --- /dev/null +++ b/tests/Fluxera.StronglyTypedId.UnitTests/Fluxera.StronglyTypedId.UnitTests.csproj @@ -0,0 +1,29 @@ + + + + net6.0 + + false + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + + diff --git a/tests/Fluxera.StronglyTypedId.UnitTests/GetHashCodeTests.cs b/tests/Fluxera.StronglyTypedId.UnitTests/GetHashCodeTests.cs new file mode 100644 index 0000000..bb7a4b2 --- /dev/null +++ b/tests/Fluxera.StronglyTypedId.UnitTests/GetHashCodeTests.cs @@ -0,0 +1,50 @@ +namespace Fluxera.StronglyTypedId.UnitTests +{ + using System; + using System.Collections.Generic; + using FluentAssertions; + using Fluxera.StronglyTypedId.UnitTests.Model; + using NUnit.Framework; + + [TestFixture] + public class GetHashCodeTests + { + private static IEnumerable PrimitiveTestData = new List + { + new object[] + { + new CustomerId("12345"), + new CustomerId("12345"), + true + }, + }; + + private static IEnumerable TestData = new List + { + new object[] + { + new CustomerId("12345"), + new CustomerId("12345"), + true + }, + + new object[] + { + new CustomerId("12345"), + new CustomerId("12346"), + false + }, + }; + + [Test] + [TestCaseSource(nameof(TestData))] + [TestCaseSource(nameof(PrimitiveTestData))] + public void GetHashCodeShouldReturnExpectedValue(object first, object second, bool expected) + { + Console.WriteLine($"{first.GetHashCode()} : {second.GetHashCode()}"); + + bool result = first.GetHashCode() == second.GetHashCode(); + result.Should().Be(expected); + } + } +} diff --git a/tests/Fluxera.StronglyTypedId.UnitTests/Model/CustomerId.cs b/tests/Fluxera.StronglyTypedId.UnitTests/Model/CustomerId.cs new file mode 100644 index 0000000..599e2a8 --- /dev/null +++ b/tests/Fluxera.StronglyTypedId.UnitTests/Model/CustomerId.cs @@ -0,0 +1,10 @@ +namespace Fluxera.StronglyTypedId.UnitTests.Model +{ + public class CustomerId : StronglyTypedId + { + /// + public CustomerId(string value) : base(value) + { + } + } +}