Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Added ValidateAndAdapt Methods for Property Validation #641

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
48 changes: 48 additions & 0 deletions src/Mapster.Tests/WhenRequiresPropsValidation.cs
Original file line number Diff line number Diff line change
@@ -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<Product, ProductNestedDTO>();

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<Exception>(() => product.ValidateAndAdapt<Product, ProductDTO>());

ex.Message.ShouldContain(notExistingPropName);
ex.Message.ShouldContain(nameof(Product));
}
}
}
53 changes: 53 additions & 0 deletions src/Mapster.Tests/WhenRequiresPropsValidationWithAdapterConfig.cs
Original file line number Diff line number Diff line change
@@ -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<Product, ProductDTO>.NewConfig()
.Map(dest => dest.CreatedUserName, src => $"{src.CreatedUser.Name} {src.CreatedUser.Surname}");

var dto = product.ValidateAndAdapt<Product, ProductDTO>(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<Product, ProductDTO>.NewConfig();

ProductDTO productDtoRef;
var notExistingPropName = nameof(productDtoRef.CreatedUserName);

var ex = Should.Throw<Exception>(() => product.ValidateAndAdapt<Product, ProductDTO>(adapterSettings.Config));

ex.Message.ShouldContain(notExistingPropName);
ex.Message.ShouldContain(nameof(Product));
}
}
}
57 changes: 57 additions & 0 deletions src/Mapster/TypeAdapter.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reflection;
using Mapster.Models;

namespace Mapster
{
Expand Down Expand Up @@ -170,6 +173,60 @@ public static TDestination Adapt<TSource, TDestination>(this TSource source, TDe
return del.DynamicInvoke(source, destination);
}
}

/// <summary>
/// Validate properties and Adapt the source object to the destination type.
/// </summary>
/// <typeparam name="TSource">Source type.</typeparam>
/// <typeparam name="TDestination">Destination type.</typeparam>
/// <param name="source">Source object to adapt.</param>
/// <returns>Adapted destination type.</returns>
public static TDestination ValidateAndAdapt<TSource, TDestination>(this TSource source)
{
var sourceType = typeof(TSource);
var selectorType = typeof(TDestination);

var sourceProperties = new HashSet<string>(sourceType.GetProperties().Select(p => p.Name));
var selectorProperties = new HashSet<string>(selectorType.GetProperties().Select(p=> p.Name));

foreach (var selectorProperty in selectorProperties)
{
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<TDestination>();
}

/// <summary>
/// Validate properties with configuration and Adapt the source object to the destination type.
/// </summary>
/// <typeparam name="TSource">Source type.</typeparam>
/// <typeparam name="TDestination">Destination type.</typeparam>
/// <param name="source">Source object to adapt.</param>
/// <param name="config">Configuration</param>
/// <returns>Adapted destination type.</returns>
public static TDestination ValidateAndAdapt<TSource, TDestination>(this TSource source, TypeAdapterConfig config)
{
var sourceType = typeof(TSource);
var selectorType = typeof(TDestination);

var sourceProperties = new HashSet<string>(sourceType.GetProperties().Select(p => p.Name));
var selectorProperties = new HashSet<string>(selectorType.GetProperties().Select(p=> p.Name));

// Get the rule map for the current types
var ruleMap = config.RuleMap;
var typeTuple = new TypeTuple(sourceType, selectorType);
ruleMap.TryGetValue(typeTuple, out var rule);

foreach (var selectorProperty in selectorProperties)
{
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 {sourceType.Name} and is not configured in Mapster");
}
return source.Adapt<TDestination>(config);
}
}

[System.Diagnostics.CodeAnalysis.SuppressMessage("Minor Code Smell", "S1104:Fields should not have public accessibility", Justification = "<Pending>")]
Expand Down
Loading