From 74452002a79286d13da558fee62b26e141e75785 Mon Sep 17 00:00:00 2001 From: Haritha Rathnayake Date: Sun, 8 Oct 2023 16:13:39 +0530 Subject: [PATCH 1/2] Implemented TypeAdapter methods to validate destination props with source --- src/Mapster/TypeAdapter.cs | 57 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 57 insertions(+) diff --git a/src/Mapster/TypeAdapter.cs b/src/Mapster/TypeAdapter.cs index 2e1db2f1..ad2d0c1b 100644 --- a/src/Mapster/TypeAdapter.cs +++ b/src/Mapster/TypeAdapter.cs @@ -1,5 +1,8 @@ using System; +using System.Collections.Generic; +using System.Linq; using System.Reflection; +using Mapster.Models; namespace Mapster { @@ -170,6 +173,60 @@ public static TDestination Adapt(this TSource source, TDe return del.DynamicInvoke(source, destination); } } + + /// + /// Validate properties and Adapt the source object to the destination type. + /// + /// Source type. + /// Destination type. + /// Source object to adapt. + /// Adapted destination type. + public static TDestination ValidateAndAdapt(this TSource source) + { + var entityType = typeof(TSource); + var selectorType = typeof(TDestination); + + var entityProperties = new HashSet(entityType.GetProperties().Select(p => p.Name)); + var selectorProperties = new HashSet(selectorType.GetProperties().Select(p=> p.Name)); + + foreach (var selectorProperty in selectorProperties) + { + if (entityProperties.Contains(selectorProperty)) continue; + throw new Exception($"Property {selectorProperty} does not exist in {entityType.Name} and is not configured in Mapster"); + } + return source.Adapt(); + } + + /// + /// Validate properties and Adapt the source object to the destination type. + /// + /// Source type. + /// Destination type. + /// Source object to adapt. + /// Configuration + /// Adapted destination type. + public static TDestination ValidateAndAdapt(this TSource source, TypeAdapterConfig config) + { + var entityType = typeof(TSource); + var selectorType = typeof(TDestination); + + var entityProperties = new HashSet(entityType.GetProperties().Select(p => p.Name)); + var selectorProperties = new HashSet(selectorType.GetProperties().Select(p=> p.Name)); + + // Get the rule map for the current types + var ruleMap = config.RuleMap; + var typeTuple = new TypeTuple(entityType, selectorType); + ruleMap.TryGetValue(typeTuple, out var rule); + + foreach (var selectorProperty in selectorProperties) + { + if (entityProperties.Contains(selectorProperty)) continue; + // Check whether the adapter config has a config for the property + if (rule != null && rule.Settings.Resolvers.Any(r => r.DestinationMemberName.Equals(selectorProperty))) continue; + throw new Exception($"Property {selectorProperty} does not exist in {entityType.Name} and is not configured in Mapster"); + } + return source.Adapt(config); + } } [System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Code Smell", "S1104:Fields should not have public accessibility", Justification = "")] From 794f4fde57bc05f7a535bcc0e25f618fe8a24fe3 Mon Sep 17 00:00:00 2001 From: Haritha Rathnayake Date: Sun, 8 Oct 2023 19:03:42 +0530 Subject: [PATCH 2/2] Implemented testing for TypeAdapter.ValidateAndAdapt --- .../WhenRequiresPropsValidation.cs | 48 +++++++++++++++++ ...equiresPropsValidationWithAdapterConfig.cs | 53 +++++++++++++++++++ src/Mapster/TypeAdapter.cs | 20 +++---- 3 files changed, 111 insertions(+), 10 deletions(-) create mode 100644 src/Mapster.Tests/WhenRequiresPropsValidation.cs create mode 100644 src/Mapster.Tests/WhenRequiresPropsValidationWithAdapterConfig.cs diff --git a/src/Mapster.Tests/WhenRequiresPropsValidation.cs b/src/Mapster.Tests/WhenRequiresPropsValidation.cs new file mode 100644 index 00000000..0652df6d --- /dev/null +++ b/src/Mapster.Tests/WhenRequiresPropsValidation.cs @@ -0,0 +1,48 @@ +using System; +using Mapster.Tests.Classes; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Shouldly; + +namespace Mapster.Tests +{ + [TestClass] + public class WhenRequiresPropsValidation + { + [TestInitialize] + public void Setup() + { + TypeAdapterConfig.GlobalSettings.Clear(); + } + + [TestCleanup] + public void TestCleanup() + { + TypeAdapterConfig.GlobalSettings.Default.NameMatchingStrategy(NameMatchingStrategy.Exact); + } + + [TestMethod] + public void DestinationProps_Exist_In_Source() + { + var product = new Product {Id = Guid.NewGuid(), Title = "ProductA", CreatedUser = new User {Name = "UserA"}}; + + var dto = product.ValidateAndAdapt(); + + dto.ShouldNotBeNull(); + dto.Id.ShouldBe(product.Id); + } + + [TestMethod] + public void DestinationProps_Not_Exist_In_Source() + { + var product = new Product {Id = Guid.NewGuid(), Title = "ProductA", CreatedUser = new User {Name = "UserA"}}; + + ProductDTO productDtoRef; + var notExistingPropName = nameof(productDtoRef.CreatedUserName); + + var ex = Should.Throw(() => product.ValidateAndAdapt()); + + ex.Message.ShouldContain(notExistingPropName); + ex.Message.ShouldContain(nameof(Product)); + } + } +} diff --git a/src/Mapster.Tests/WhenRequiresPropsValidationWithAdapterConfig.cs b/src/Mapster.Tests/WhenRequiresPropsValidationWithAdapterConfig.cs new file mode 100644 index 00000000..eb845416 --- /dev/null +++ b/src/Mapster.Tests/WhenRequiresPropsValidationWithAdapterConfig.cs @@ -0,0 +1,53 @@ +using System; +using Mapster.Tests.Classes; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Shouldly; + +namespace Mapster.Tests +{ + [TestClass] + public class WhenRequiresPropsValidationWithAdapterConfig + { + [TestInitialize] + public void Setup() + { + TypeAdapterConfig.GlobalSettings.Clear(); + } + + [TestCleanup] + public void TestCleanup() + { + TypeAdapterConfig.GlobalSettings.Default.NameMatchingStrategy(NameMatchingStrategy.Exact); + } + + [TestMethod] + public void DestinationProps_Not_Exist_In_Source_But_Configured() + { + var product = new Product {Id = Guid.NewGuid(), Title = "ProductA", CreatedUser = new User {Name = "UserA"}}; + + var adapterSettings = TypeAdapterConfig.NewConfig() + .Map(dest => dest.CreatedUserName, src => $"{src.CreatedUser.Name} {src.CreatedUser.Surname}"); + + var dto = product.ValidateAndAdapt(adapterSettings.Config); + + dto.ShouldNotBeNull(); + dto.CreatedUserName.ShouldBe($"{product.CreatedUser.Name} {product.CreatedUser.Surname}"); + } + + [TestMethod] + public void DestinationProps_Not_Exist_In_Source_And_MisConfigured() + { + var product = new Product {Id = Guid.NewGuid(), Title = "ProductA", CreatedUser = new User {Name = "UserA"}}; + + var adapterSettings = TypeAdapterConfig.NewConfig(); + + ProductDTO productDtoRef; + var notExistingPropName = nameof(productDtoRef.CreatedUserName); + + var ex = Should.Throw(() => product.ValidateAndAdapt(adapterSettings.Config)); + + ex.Message.ShouldContain(notExistingPropName); + ex.Message.ShouldContain(nameof(Product)); + } + } +} diff --git a/src/Mapster/TypeAdapter.cs b/src/Mapster/TypeAdapter.cs index ad2d0c1b..8c284b4b 100644 --- a/src/Mapster/TypeAdapter.cs +++ b/src/Mapster/TypeAdapter.cs @@ -183,22 +183,22 @@ public static TDestination Adapt(this TSource source, TDe /// Adapted destination type. public static TDestination ValidateAndAdapt(this TSource source) { - var entityType = typeof(TSource); + var sourceType = typeof(TSource); var selectorType = typeof(TDestination); - var entityProperties = new HashSet(entityType.GetProperties().Select(p => p.Name)); + var sourceProperties = new HashSet(sourceType.GetProperties().Select(p => p.Name)); var selectorProperties = new HashSet(selectorType.GetProperties().Select(p=> p.Name)); foreach (var selectorProperty in selectorProperties) { - if (entityProperties.Contains(selectorProperty)) continue; - throw new Exception($"Property {selectorProperty} does not exist in {entityType.Name} and is not configured in Mapster"); + if (sourceProperties.Contains(selectorProperty)) continue; + throw new Exception($"Property {selectorProperty} does not exist in {sourceType.Name} and is not configured in Mapster"); } return source.Adapt(); } /// - /// Validate properties and Adapt the source object to the destination type. + /// Validate properties with configuration and Adapt the source object to the destination type. /// /// Source type. /// Destination type. @@ -207,23 +207,23 @@ public static TDestination ValidateAndAdapt(this TSource /// Adapted destination type. public static TDestination ValidateAndAdapt(this TSource source, TypeAdapterConfig config) { - var entityType = typeof(TSource); + var sourceType = typeof(TSource); var selectorType = typeof(TDestination); - var entityProperties = new HashSet(entityType.GetProperties().Select(p => p.Name)); + var sourceProperties = new HashSet(sourceType.GetProperties().Select(p => p.Name)); var selectorProperties = new HashSet(selectorType.GetProperties().Select(p=> p.Name)); // Get the rule map for the current types var ruleMap = config.RuleMap; - var typeTuple = new TypeTuple(entityType, selectorType); + var typeTuple = new TypeTuple(sourceType, selectorType); ruleMap.TryGetValue(typeTuple, out var rule); foreach (var selectorProperty in selectorProperties) { - if (entityProperties.Contains(selectorProperty)) continue; + if (sourceProperties.Contains(selectorProperty)) continue; // Check whether the adapter config has a config for the property if (rule != null && rule.Settings.Resolvers.Any(r => r.DestinationMemberName.Equals(selectorProperty))) continue; - throw new Exception($"Property {selectorProperty} does not exist in {entityType.Name} and is not configured in Mapster"); + throw new Exception($"Property {selectorProperty} does not exist in {sourceType.Name} and is not configured in Mapster"); } return source.Adapt(config); }