diff --git a/EqualityInformationApi.Tests/V1/Factories/SnsFactoryTests.cs b/EqualityInformationApi.Tests/V1/Factories/SnsFactoryTests.cs index 68d2272..67ebcad 100644 --- a/EqualityInformationApi.Tests/V1/Factories/SnsFactoryTests.cs +++ b/EqualityInformationApi.Tests/V1/Factories/SnsFactoryTests.cs @@ -4,6 +4,7 @@ using EqualityInformationApi.V1.Infrastructure; using EqualityInformationApi.V1.Infrastructure.Constants; using FluentAssertions; +using Hackney.Core.DynamoDb.EntityUpdater; using Hackney.Core.JWT; using Hackney.Core.Sns; using System; diff --git a/EqualityInformationApi.Tests/V1/Gateways/DynamoDbGatewayTests.cs b/EqualityInformationApi.Tests/V1/Gateways/DynamoDbGatewayTests.cs index 5a897d8..ce684e1 100644 --- a/EqualityInformationApi.Tests/V1/Gateways/DynamoDbGatewayTests.cs +++ b/EqualityInformationApi.Tests/V1/Gateways/DynamoDbGatewayTests.cs @@ -8,6 +8,8 @@ using EqualityInformationApi.V1.Infrastructure.Exceptions; using FluentAssertions; using Force.DeepCloner; +using Hackney.Core.DynamoDb.EntityUpdater; +using Hackney.Core.DynamoDb.EntityUpdater.Interfaces; using Hackney.Core.Testing.DynamoDb; using Microsoft.Extensions.Logging; using Moq; diff --git a/EqualityInformationApi.Tests/V1/Infrastructure/StringExtensionsTests.cs b/EqualityInformationApi.Tests/V1/Infrastructure/StringExtensionsTests.cs deleted file mode 100644 index 7345f2b..0000000 --- a/EqualityInformationApi.Tests/V1/Infrastructure/StringExtensionsTests.cs +++ /dev/null @@ -1,22 +0,0 @@ -using FluentAssertions; -using Xunit; - -namespace Hackney.Core.Tests -{ - public class StringExtensionsTests - { - [Theory] - [InlineData(null, null)] - [InlineData("", "")] - [InlineData("c", "c")] - [InlineData("C", "c")] - [InlineData("camel", "camel")] - [InlineData("Camel", "camel")] - [InlineData("oneHumpCamel", "oneHumpCamel")] - [InlineData("OneHumpCamel", "oneHumpCamel")] - public void ToCamelCaseTests(string input, string expectedOutput) - { - input.ToCamelCase().Should().Be(expectedOutput); - } - } -} diff --git a/EqualityInformationApi.Tests/V1/UseCase/PatchUseCaseTests.cs b/EqualityInformationApi.Tests/V1/UseCase/PatchUseCaseTests.cs index c71f890..95ce8c3 100644 --- a/EqualityInformationApi.Tests/V1/UseCase/PatchUseCaseTests.cs +++ b/EqualityInformationApi.Tests/V1/UseCase/PatchUseCaseTests.cs @@ -5,6 +5,7 @@ using EqualityInformationApi.V1.Infrastructure; using EqualityInformationApi.V1.UseCase; using FluentAssertions; +using Hackney.Core.DynamoDb.EntityUpdater; using Hackney.Core.JWT; using Hackney.Core.Sns; using Moq; diff --git a/EqualityInformationApi/EqualityInformationApi.csproj b/EqualityInformationApi/EqualityInformationApi.csproj index 58c3c60..712f9b7 100644 --- a/EqualityInformationApi/EqualityInformationApi.csproj +++ b/EqualityInformationApi/EqualityInformationApi.csproj @@ -22,11 +22,11 @@ - + - + diff --git a/EqualityInformationApi/Startup.cs b/EqualityInformationApi/Startup.cs index 1828349..05ce356 100644 --- a/EqualityInformationApi/Startup.cs +++ b/EqualityInformationApi/Startup.cs @@ -41,6 +41,8 @@ using System.Text.Json.Serialization; using HeaderConstants = EqualityInformationApi.V1.Infrastructure.HeaderConstants; using CoreHeaderConstants = Hackney.Core.Middleware.HeaderConstants; +using Hackney.Core.DynamoDb.EntityUpdater; +using Hackney.Core.DynamoDb.EntityUpdater.Interfaces; namespace EqualityInformationApi { diff --git a/EqualityInformationApi/V1/Factories/ISnsFactory.cs b/EqualityInformationApi/V1/Factories/ISnsFactory.cs index 220ea08..79e9420 100644 --- a/EqualityInformationApi/V1/Factories/ISnsFactory.cs +++ b/EqualityInformationApi/V1/Factories/ISnsFactory.cs @@ -1,5 +1,6 @@ using EqualityInformationApi.V1.Domain; using EqualityInformationApi.V1.Infrastructure; +using Hackney.Core.DynamoDb.EntityUpdater; using Hackney.Core.JWT; using Hackney.Core.Sns; diff --git a/EqualityInformationApi/V1/Factories/SnsFactory.cs b/EqualityInformationApi/V1/Factories/SnsFactory.cs index f378dc9..e4d4a5e 100644 --- a/EqualityInformationApi/V1/Factories/SnsFactory.cs +++ b/EqualityInformationApi/V1/Factories/SnsFactory.cs @@ -1,6 +1,7 @@ using EqualityInformationApi.V1.Domain; using EqualityInformationApi.V1.Infrastructure; using EqualityInformationApi.V1.Infrastructure.Constants; +using Hackney.Core.DynamoDb.EntityUpdater; using Hackney.Core.JWT; using Hackney.Core.Sns; using System; diff --git a/EqualityInformationApi/V1/Gateways/EqualityInformationGateway.cs b/EqualityInformationApi/V1/Gateways/EqualityInformationGateway.cs index ee3b306..965c208 100644 --- a/EqualityInformationApi/V1/Gateways/EqualityInformationGateway.cs +++ b/EqualityInformationApi/V1/Gateways/EqualityInformationGateway.cs @@ -4,6 +4,8 @@ using EqualityInformationApi.V1.Factories; using EqualityInformationApi.V1.Infrastructure; using EqualityInformationApi.V1.Infrastructure.Exceptions; +using Hackney.Core.DynamoDb.EntityUpdater; +using Hackney.Core.DynamoDb.EntityUpdater.Interfaces; using Hackney.Core.Logging; using Microsoft.Extensions.Logging; using System; diff --git a/EqualityInformationApi/V1/Gateways/IEqualityInformationGateway.cs b/EqualityInformationApi/V1/Gateways/IEqualityInformationGateway.cs index 792a970..d9e35d0 100644 --- a/EqualityInformationApi/V1/Gateways/IEqualityInformationGateway.cs +++ b/EqualityInformationApi/V1/Gateways/IEqualityInformationGateway.cs @@ -1,6 +1,7 @@ using EqualityInformationApi.V1.Boundary.Request; using EqualityInformationApi.V1.Domain; using EqualityInformationApi.V1.Infrastructure; +using Hackney.Core.DynamoDb.EntityUpdater; using System; using System.Threading.Tasks; diff --git a/EqualityInformationApi/V1/Infrastructure/BodyRewind.cs b/EqualityInformationApi/V1/Infrastructure/BodyRewind.cs deleted file mode 100644 index 0d27428..0000000 --- a/EqualityInformationApi/V1/Infrastructure/BodyRewind.cs +++ /dev/null @@ -1,45 +0,0 @@ -using Microsoft.AspNetCore.Builder; -using Microsoft.AspNetCore.Http; -using System; -using System.Diagnostics.CodeAnalysis; -using System.Threading.Tasks; - -namespace Hackney.Core.Middleware -{ - // TODO - move to common nuget package - - [ExcludeFromCodeCoverage] - public sealed class BodyRewindMiddleware - { - private readonly RequestDelegate _next; - - public BodyRewindMiddleware(RequestDelegate next) - { - _next = next; - } - - public async Task Invoke(HttpContext context) - { - context.Request.EnableBuffering(); - await _next(context).ConfigureAwait(false); - } - } - - [ExcludeFromCodeCoverage] - public static class BodyRewindExtensions - { - /// - /// Enables the HttpRequset body to be access from within a controller method. - /// Without this the request body is rendered unaccessible by other middleware steps. - /// - /// An App builder - /// An App builder - public static IApplicationBuilder EnableRequestBodyRewind(this IApplicationBuilder app) - { - if (app == null) throw new ArgumentNullException(nameof(app)); - - return app.UseMiddleware(); - } - - } -} diff --git a/EqualityInformationApi/V1/Infrastructure/EntityUpdater.cs b/EqualityInformationApi/V1/Infrastructure/EntityUpdater.cs deleted file mode 100644 index 657c68f..0000000 --- a/EqualityInformationApi/V1/Infrastructure/EntityUpdater.cs +++ /dev/null @@ -1,131 +0,0 @@ -using Hackney.Core; -using Microsoft.Extensions.Logging; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text.Json; -using System.Text.Json.Serialization; - -namespace EqualityInformationApi.V1.Infrastructure -{ - // TODO - move to common nuget package - - public class EntityUpdater : IEntityUpdater - { - private readonly ILogger _logger; - - public EntityUpdater(ILogger logger) - { - _logger = logger; - } - - private static JsonSerializerOptions CreateJsonOptions() - { - var options = new JsonSerializerOptions - { - PropertyNamingPolicy = JsonNamingPolicy.CamelCase, - WriteIndented = true - }; - options.Converters.Add(new JsonStringEnumConverter()); - return options; - } - - private static bool HasValueChanged(object existingValue, object updateValue) - { - if (updateValue is null && existingValue is null) return false; - if (updateValue is null && (existingValue != null)) return true; - return !updateValue.Equals(existingValue); - } - - /// - /// Updates the supplied entity with the updated property values described in the request object / json - /// Defaults the ignoreUnchangedProperties input value to true. - /// - /// The entity type - /// The type of the update request object - /// The entity to update - /// The raw update request json from which the request object was deserialized - /// The update request object - /// A response object - public UpdateEntityResult UpdateEntity(TEntity entityToUpdate, - string updateJson, - TUpdateObject updateObject) - where TEntity : class - where TUpdateObject : class - { - return UpdateEntity(entityToUpdate, updateJson, updateObject, true); - } - - /// - /// Updates the supplied entity with the updated property values described in the request object / json. - /// * This method expects both a request object and the raw request json so that the appropriate request object validation - /// can be executed by the MVC pipeline. - /// * The inclusion of the request object also means that each updated property value has been deserialised correctly. - /// * The raw request json should contain ONLY the properties to be updated. - /// * The property names in the json / request object MUST MATCH the corresponing properties on the entity type (assuming the json uses camel casing). - /// * For nested objects, those classes must override the Equals() mewthod so that the algorithm will correctly determine if a suboject has changed. - /// - /// The entity type - /// The type of the update request object - /// The entity to update - /// The raw update request json from which the request object was deserialized - /// The update request object - /// Whether or not to ignore property values set in the update request - /// but that are actually the same as current entity value. - /// A response object - public UpdateEntityResult UpdateEntity(TEntity entityToUpdate, - string updateJson, - TUpdateObject updateObject, - bool ignoreUnchangedProperties) - where TEntity : class - where TUpdateObject : class - { - if (entityToUpdate is null) throw new ArgumentNullException(nameof(entityToUpdate)); - if (updateObject is null) throw new ArgumentNullException(nameof(updateObject)); - - var result = new UpdateEntityResult() { UpdatedEntity = entityToUpdate }; - if (string.IsNullOrEmpty(updateJson)) return result; - - var updateDic = JsonSerializer.Deserialize>(updateJson, CreateJsonOptions()); - var entityType = typeof(TEntity); - var updateObjectType = typeof(TUpdateObject); - - var allEntityProperties = entityType.GetProperties(); - foreach (var propName in updateDic.Keys) - { - var prop = allEntityProperties.FirstOrDefault(x => x.Name.ToCamelCase() == propName); - if (prop is null) - { - // Received a property on the request Json that's not on the entity at all - // So we log a warning, ignore it and carry on. - _logger.LogWarning($"Entity object (type: {entityType.Name}) does not contain a property called {propName}. Ignoring {propName} value..."); - result.IgnoredProperties.Add(propName); - continue; - } - - var requestObjectProperty = updateObjectType.GetProperty(prop.Name); - if (requestObjectProperty is null) - { - // Received a property on the request Json we weren't expecting (it's not on the request object) - // So we log a warning, ignore it and carry on. - _logger.LogWarning($"Request object (type: {updateObjectType.Name}) does not contain a property called {prop.Name} that is on the entity type ({entityType.Name}). Ignoring {prop.Name} value..."); - result.IgnoredProperties.Add(propName); - continue; - } - - var updateValue = requestObjectProperty.GetValue(updateObject); - var existingValue = prop.GetValue(entityToUpdate); - - // For sub-objects this Equals() check will only work if the Equals() method is overridden - if (!ignoreUnchangedProperties || HasValueChanged(existingValue, updateValue)) - { - result.OldValues.Add(propName, existingValue); - result.NewValues.Add(propName, updateValue); - prop.SetValue(entityToUpdate, updateValue); - } - } - - return result; - } - } -} diff --git a/EqualityInformationApi/V1/Infrastructure/IEntityUpdater.cs b/EqualityInformationApi/V1/Infrastructure/IEntityUpdater.cs deleted file mode 100644 index 10765d6..0000000 --- a/EqualityInformationApi/V1/Infrastructure/IEntityUpdater.cs +++ /dev/null @@ -1,49 +0,0 @@ -namespace EqualityInformationApi.V1.Infrastructure -{ - // TODO - move to common nuget package - - /// - /// Interface describing generic methods for updating an instance of an entity from the suypplied request object and raw request json - /// - public interface IEntityUpdater - { - /// - /// Updates the supplied entity with the updated property values described in the request object / json - /// - /// The entity type - /// The type of the update request object - /// The entity to update - /// The raw update request json from which the request object was deserialized - /// The update request object - /// A response object - UpdateEntityResult UpdateEntity( - TEntity entityToUpdate, - string updateJson, - TUpdateObject updateObject) - where TEntity : class - where TUpdateObject : class; - - /// - /// Updates the supplied entity with the updated property values described in the request object / json. - /// * This method expects both a request object and the raw request json so that the appropriate request object validation - /// can be executed by the MVC pipeline. - /// * The raw request json should contain ONLY the properties to be updated. - /// * The property names in the json / request object MUST MATCH the corresponing properties on the entity type (assuming the json uses camel casing). - /// - /// The entity type - /// The type of the update request object - /// The entity to update - /// The raw update request json from which the request object was deserialized - /// The update request object - /// Whether or not to ignore property values set in the update request - /// but that are actually the same as current entity value. - /// A response object - UpdateEntityResult UpdateEntity( - TEntity entityToUpdate, - string updateJson, - TUpdateObject updateObject, - bool ignoreUnchangedProperties) - where TEntity : class - where TUpdateObject : class; - } -} diff --git a/EqualityInformationApi/V1/Infrastructure/StringExtensions.cs b/EqualityInformationApi/V1/Infrastructure/StringExtensions.cs deleted file mode 100644 index f394b38..0000000 --- a/EqualityInformationApi/V1/Infrastructure/StringExtensions.cs +++ /dev/null @@ -1,22 +0,0 @@ -namespace Hackney.Core -{ - // TODO - move to common nuget package - - public static class StringExtensions - { - /// - /// Converts the string to camel case (i.e. the first character is lowercase) - /// - /// The string to change - /// A copied of the string with the first chacater in lowercase. A null or empty string returns what was supplied. - /// - public static string ToCamelCase(this string str) - { - if (string.IsNullOrEmpty(str)) return str; - if (str.Length == 1) return str.ToLowerInvariant(); - - // else if (str.Length > 1) - return char.ToLowerInvariant(str[0]) + str.Substring(1); - } - } -} diff --git a/EqualityInformationApi/V1/Infrastructure/UpdateEntityResult.cs b/EqualityInformationApi/V1/Infrastructure/UpdateEntityResult.cs deleted file mode 100644 index 9b4f40f..0000000 --- a/EqualityInformationApi/V1/Infrastructure/UpdateEntityResult.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Collections.Generic; - -namespace EqualityInformationApi.V1.Infrastructure -{ - public class UpdateEntityResult where T : class - { - /// - /// The updated entity - /// - public T UpdatedEntity { get; set; } - - /// - /// A simple dictionary listing the previous value(s) of any entity properties actually updated by the call. - /// The size and keys of this dictionary will match that of the NewValues property. - /// - public Dictionary OldValues { get; set; } = new Dictionary(); - - /// - /// A simple dictionary listing the new value(s) of any entity properties actually updated by the call. - /// The size and keys of this dictionary will match that of the OldValues property. - /// - public Dictionary NewValues { get; set; } = new Dictionary(); - - /// - /// A collection of properties that were in the orignal request json but that were ignored because either - /// * They did not exist on the request object, or - /// * They did not exist on the entity at all. - /// - public List IgnoredProperties { get; set; } = new List(); - } -}