Skip to content

Commit

Permalink
Merge branch 'feature-1198' of github.com:micheleissa/CsvHelper into …
Browse files Browse the repository at this point in the history
…micheleissa-feature-1198
  • Loading branch information
JoshClose committed Oct 11, 2022
2 parents 6ce93f4 + 9e12cd9 commit 9a089f8
Show file tree
Hide file tree
Showing 6 changed files with 109 additions and 32 deletions.
21 changes: 18 additions & 3 deletions src/CsvHelper/Configuration/MemberMap.cs
Original file line number Diff line number Diff line change
Expand Up @@ -212,16 +212,31 @@ public virtual MemberMap Optional()
/// </summary>
/// <param name="validateExpression"></param>
public virtual MemberMap Validate(Validate validateExpression)
{
return Validate(validateExpression, args => $"Field '{args.Field}' is not valid.");
}

/// <summary>
/// Specifies an expression to be used to validate a field when reading along with specified exception message.
/// </summary>
/// <param name="validateExpression"></param>
/// <param name="validateMessageExpression"></param>
public virtual MemberMap Validate(Validate validateExpression, ValidateMessage validateMessageExpression)
{
var fieldParameter = Expression.Parameter(typeof(ValidateArgs), "field");
var methodExpression = Expression.Call(
var validateCallExpression = Expression.Call(
Expression.Constant(validateExpression.Target),
validateExpression.Method,
fieldParameter
);
var lambdaExpression = Expression.Lambda<Validate>(methodExpression, fieldParameter);
var messageCallExpression = Expression.Call(
Expression.Constant(validateMessageExpression.Target),
validateMessageExpression.Method,
fieldParameter
);

Data.ValidateExpression = lambdaExpression;
Data.ValidateExpression = Expression.Lambda<Validate>(validateCallExpression, fieldParameter);
Data.ValidateMessageExpression = Expression.Lambda<ValidateMessage>(messageCallExpression, fieldParameter);

return this;
}
Expand Down
7 changes: 6 additions & 1 deletion src/CsvHelper/Configuration/MemberMapData.cs
Original file line number Diff line number Diff line change
Expand Up @@ -145,12 +145,17 @@ public virtual Type Type
/// </summary>
public virtual Expression ValidateExpression { get; set; }

/// <summary>
/// Gets or sets the expression used to get the validation message when validation fails.
/// </summary>
public virtual Expression ValidateMessageExpression { get; set; }

/// <summary>
/// Gets or sets a value indicating if a field is optional.
/// </summary>
public virtual bool IsOptional { get; set; }

/// <summary>
/// <summary>
/// Initializes a new instance of the <see cref="MemberMapData"/> class.
/// </summary>
/// <param name="member">The member.</param>
Expand Down
39 changes: 27 additions & 12 deletions src/CsvHelper/Configuration/MemberMap`1.cs
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public MemberMap(MemberInfo? member)
/// The first name will be used.
/// </summary>
/// <param name="names">The possible names of the CSV field.</param>
public virtual new MemberMap<TClass, TMember> Name(params string[] names)
public new virtual MemberMap<TClass, TMember> Name(params string[] names)
{
if (names == null || names.Length == 0)
{
Expand All @@ -55,7 +55,7 @@ public MemberMap(MemberInfo? member)
/// are multiple names that are the same.
/// </summary>
/// <param name="index">The index of the name.</param>
public virtual new MemberMap<TClass, TMember> NameIndex(int index)
public new virtual MemberMap<TClass, TMember> NameIndex(int index)
{
Data.NameIndex = index;

Expand All @@ -70,7 +70,7 @@ public MemberMap(MemberInfo? member)
/// </summary>
/// <param name="index">The index of the CSV field.</param>
/// <param name="indexEnd">The end index used when mapping to an <see cref="IEnumerable"/> member.</param>
public virtual new MemberMap<TClass, TMember> Index(int index, int indexEnd = -1)
public new virtual MemberMap<TClass, TMember> Index(int index, int indexEnd = -1)
{
Data.Index = index;
Data.IsIndexSet = true;
Expand All @@ -86,7 +86,7 @@ public MemberMap(MemberInfo? member)
/// this method will not ignore all the child members down the
/// tree that have already been mapped.
/// </summary>
public virtual new MemberMap<TClass, TMember> Ignore()
public new virtual MemberMap<TClass, TMember> Ignore()
{
Data.Ignore = true;

Expand All @@ -101,7 +101,7 @@ public MemberMap(MemberInfo? member)
/// tree that have already been mapped.
/// </summary>
/// <param name="ignore">True to ignore, otherwise false.</param>
public virtual new MemberMap<TClass, TMember> Ignore(bool ignore)
public new virtual MemberMap<TClass, TMember> Ignore(bool ignore)
{
Data.Ignore = ignore;

Expand Down Expand Up @@ -159,7 +159,7 @@ public virtual MemberMap<TClass, TMember> Constant(TMember? constantValue)
/// when converting the member to and from a CSV field.
/// </summary>
/// <param name="typeConverter">The TypeConverter to use.</param>
public virtual new MemberMap<TClass, TMember> TypeConverter(ITypeConverter typeConverter)
public new virtual MemberMap<TClass, TMember> TypeConverter(ITypeConverter typeConverter)
{
Data.TypeConverter = typeConverter;

Expand All @@ -172,7 +172,7 @@ public virtual MemberMap<TClass, TMember> Constant(TMember? constantValue)
/// </summary>
/// <typeparam name="TConverter">The <see cref="System.Type"/> of the
/// <see cref="TypeConverter"/> to use.</typeparam>
public virtual new MemberMap<TClass, TMember> TypeConverter<TConverter>() where TConverter : ITypeConverter
public new virtual MemberMap<TClass, TMember> TypeConverter<TConverter>() where TConverter : ITypeConverter
{
TypeConverter(ObjectResolver.Current.Resolve<TConverter>());

Expand Down Expand Up @@ -226,7 +226,7 @@ public virtual MemberMap<TClass, TMember> Convert(ConvertToString<TClass> conver
/// <summary>
/// Ignore the member when reading if no matching field name can be found.
/// </summary>
public virtual new MemberMap<TClass, TMember> Optional()
public new virtual MemberMap<TClass, TMember> Optional()
{
Data.IsOptional = true;

Expand All @@ -237,17 +237,32 @@ public virtual MemberMap<TClass, TMember> Convert(ConvertToString<TClass> conver
/// Specifies an expression to be used to validate a field when reading.
/// </summary>
/// <param name="validateExpression"></param>
public virtual new MemberMap<TClass, TMember> Validate(Validate validateExpression)
public new virtual MemberMap<TClass, TMember> Validate(Validate validateExpression)
{
return Validate(validateExpression, args => $"Field '{args.Field}' is not valid.");
}

/// <summary>
/// Specifies an expression to be used to validate a field when reading along with specified exception message.
/// </summary>
/// <param name="validateExpression"></param>
/// <param name="validateMessageExpression"></param>
public new virtual MemberMap<TClass, TMember> Validate(Validate validateExpression, ValidateMessage validateMessageExpression)
{
var fieldParameter = Expression.Parameter(typeof(ValidateArgs), "args");
var methodExpression = Expression.Call(
var validateCallExpression = Expression.Call(
Expression.Constant(validateExpression.Target),
validateExpression.Method,
fieldParameter
);
var lambdaExpression = Expression.Lambda<Validate>(methodExpression, fieldParameter);
var messageCallExpression = Expression.Call(
Expression.Constant(validateMessageExpression.Target),
validateMessageExpression.Method,
fieldParameter
);

Data.ValidateExpression = lambdaExpression;
Data.ValidateExpression = Expression.Lambda<Validate>(validateCallExpression, fieldParameter);
Data.ValidateMessageExpression = Expression.Lambda<ValidateMessage>(messageCallExpression, fieldParameter);

return this;
}
Expand Down
16 changes: 15 additions & 1 deletion src/CsvHelper/Delegates/Validate.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,13 @@ namespace CsvHelper
/// <returns><c>true</c> if the field is valid, otherwise <c>false</c>.</returns>
public delegate bool Validate(ValidateArgs args);

/// <summary>
/// Function that gets the exception message when validation fails.
/// </summary>
/// <param name="args">The args.</param>
/// <returns>The exception message.</returns>
public delegate string ValidateMessage(ValidateArgs args);

/// <summary>
/// Validate args.
/// </summary>
Expand All @@ -27,13 +34,20 @@ public readonly struct ValidateArgs
/// </summary>
public readonly string Field;

/// <summary>
/// The row.
/// </summary>
public readonly IReaderRow Row;

/// <summary>
/// Creates a new instance of ValidateArgs.
/// </summary>
/// <param name="field">The field.</param>
public ValidateArgs(string field)
/// <param name="row">The row.</param>
public ValidateArgs(string field, IReaderRow row)
{
Field = field;
Row = row;
}
}
}
9 changes: 5 additions & 4 deletions src/CsvHelper/Expressions/ExpressionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -253,11 +253,12 @@ public virtual void CreateMemberAssignmentsForMapping(ClassMap mapping, List<Mem
// Validate the field.
if (memberMap.Data.ValidateExpression != null)
{
var constructor = typeof(ValidateArgs).GetConstructor(new Type[] { typeof(string) });
var args = Expression.New(constructor, fieldExpression);
var constructor = typeof(ValidateArgs).GetConstructor(new Type[] { typeof(string), typeof(IReaderRow) });
var args = Expression.New(constructor, fieldExpression, Expression.Constant(reader));
var validateExpression = Expression.IsFalse(Expression.Invoke(memberMap.Data.ValidateExpression, args));
var validationExceptionConstructor = typeof(FieldValidationException).GetConstructors().OrderBy(c => c.GetParameters().Length).First();
var newValidationExceptionExpression = Expression.New(validationExceptionConstructor, Expression.Constant(reader.Context), fieldExpression);
var validationExceptionConstructor = typeof(FieldValidationException).GetConstructor(new Type[] { typeof(CsvContext), typeof(string), typeof(string) });
var messageExpression = Expression.Invoke(memberMap.Data.ValidateMessageExpression, args);
var newValidationExceptionExpression = Expression.New(validationExceptionConstructor, Expression.Constant(reader.Context), fieldExpression, messageExpression);
var throwExpression = Expression.Throw(newValidationExceptionExpression);
fieldExpression = Expression.Block(
// If the validate method returns false, throw an exception.
Expand Down
49 changes: 38 additions & 11 deletions tests/CsvHelper.Tests/Reading/ValidateTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@
// See LICENSE.txt for details or visit http://www.opensource.org/licenses/ms-pl.html for MS-PL and http://opensource.org/licenses/Apache-2.0 for Apache 2.0.
// https://github.com/JoshClose/CsvHelper
using CsvHelper.Configuration;
using Xunit;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
using Xunit;

namespace CsvHelper.Tests.Reading
{

public class ValidateTests
{
[Fact]
Expand Down Expand Up @@ -108,6 +108,24 @@ public void CustomExceptionTest()
}
}

[Fact]
public void ValidateMessageTest()
{
var config = new CsvConfiguration(CultureInfo.InvariantCulture)
{
};
var s = new TestStringBuilder(config.NewLine);
s.AppendLine("Id,Name");
s.AppendLine("1,one");
using (var reader = new StringReader(s))
using (var csv = new CsvReader(reader, config))
{
csv.Context.RegisterClassMap<ValidationMessageMap>();
var exception = Assert.Throws<FieldValidationException>(() => csv.GetRecords<Test>().ToList());
Assert.StartsWith("Field 'one' was not foo.", exception.Message);
}
}

private class Test
{
public int Id { get; set; }
Expand Down Expand Up @@ -142,15 +160,15 @@ public LogInsteadMap(StringBuilder logger)
{
Map(m => m.Id);
Map(m => m.Name).Validate(args =>
{
var isValid = !string.IsNullOrEmpty(args.Field);
if (!isValid)
{
logger.AppendLine($"Field '{args.Field}' is not valid!");
}

return true;
});
{
var isValid = !string.IsNullOrEmpty(args.Field);
if (!isValid)
{
logger.AppendLine($"Field '{args.Field}' is not valid!");
}

return true;
});
}
}

Expand All @@ -166,5 +184,14 @@ public CustomExceptionMap()
private class CustomException : CsvHelperException
{
}

private class ValidationMessageMap : ClassMap<Test>
{
public ValidationMessageMap()
{
Map(m => m.Id);
Map(m => m.Name).Validate(args => args.Field == "foo", args => $"Field '{args.Field}' was not foo.");
}
}
}
}

0 comments on commit 9a089f8

Please sign in to comment.