Skip to content

Commit

Permalink
Merge pull request #52 from josdeweger/feature/CustomParser
Browse files Browse the repository at this point in the history
Feature/custom parser
  • Loading branch information
jacobduijzer authored Dec 28, 2018
2 parents 8c392df + 3952f7c commit 6b3e3c8
Show file tree
Hide file tree
Showing 13 changed files with 204 additions and 72 deletions.
2 changes: 1 addition & 1 deletion src/Common.props
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<Project>
<PropertyGroup>
<Version>2.2.0</Version>
<Version>2.3.0</Version>
</PropertyGroup>
</Project>
20 changes: 12 additions & 8 deletions src/SheetToObjects.Lib/FluentConfiguration/ColumnMapping.cs
Original file line number Diff line number Diff line change
@@ -1,26 +1,30 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using SheetToObjects.Lib.Validation;

namespace SheetToObjects.Lib.FluentConfiguration
{
public abstract class ColumnMapping
{
public abstract string DisplayName { get; }

public bool IsRequired
{
get { return ParsingRules.Exists(r => r is RequiredRule); }
}

public bool IsRequired => ParsingRules.Exists(r => r is RequiredRule);
public int ColumnIndex { get; protected set; }
public string PropertyName { get; }
public string Format { get; }
public List<IParsingRule> ParsingRules { get; }
public List<IRule> Rules { get; }
public object DefaultValue { get; }
public readonly Func<string, object> CustomValueParser;

protected ColumnMapping(string propertyName, string format, List<IParsingRule> parsingRules, List<IRule> rules, object defaultValue)
protected ColumnMapping(
string propertyName,
string format,
List<IParsingRule> parsingRules,
List<IRule> rules,
object defaultValue,
Func<string, object> customValueParser)
{
CustomValueParser = customValueParser;
PropertyName = propertyName;
Format = format;
ParsingRules = parsingRules ?? new List<IParsingRule>();
Expand Down
15 changes: 11 additions & 4 deletions src/SheetToObjects.Lib/FluentConfiguration/ColumnMappingBuilder.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ public class ColumnMappingBuilder<T>
private bool _isRequiredInHeaderRow;
private readonly List<IParsingRule> _parsingRules = new List<IParsingRule>();
private readonly List<IRule> _rules = new List<IRule>();
private Func<string, object> _customValueParser;

/// <summary>
/// Map to column by header (other options are to map by column index or column letter)
Expand Down Expand Up @@ -101,6 +102,12 @@ public ColumnMappingBuilder<T> UsingFormat(string format)
return this;
}

public ColumnMappingBuilder<T> ParseValueUsing(Func<string, object> customValueParser)
{
_customValueParser = customValueParser;
return this;
}

/// <summary>
/// Specify the model property this column needs to map to. The type is inferred from the type on the model
/// </summary>
Expand Down Expand Up @@ -145,13 +152,13 @@ public ColumnMapping MapTo(PropertyInfo property)
_propertyName = property.Name;

if(_header.IsNotNullOrWhiteSpace())
return new NameColumnMapping(_header, _propertyName, _format, _parsingRules, _rules, _defaultValue,_isRequiredInHeaderRow);
return new NameColumnMapping(_header, _propertyName, _format, _parsingRules, _rules, _defaultValue,_isRequiredInHeaderRow, _customValueParser);
if(_columnLetter.IsNotNullOrWhiteSpace())
return new LetterColumnMapping(_columnLetter, _propertyName, _format, _parsingRules, _rules, _defaultValue);
return new LetterColumnMapping(_columnLetter, _propertyName, _format, _parsingRules, _rules, _defaultValue, _customValueParser);
if(_columnIndex >= 0)
return new IndexColumnMapping(_columnIndex, _propertyName, _format, _parsingRules, _rules, _defaultValue);
return new IndexColumnMapping(_columnIndex, _propertyName, _format, _parsingRules, _rules, _defaultValue, _customValueParser);

return new PropertyColumnMapping(_propertyName, _format, _parsingRules, _rules, _defaultValue,_isRequiredInHeaderRow);
return new PropertyColumnMapping(_propertyName, _format, _parsingRules, _rules, _defaultValue,_isRequiredInHeaderRow, _customValueParser);
}
}
}
14 changes: 11 additions & 3 deletions src/SheetToObjects.Lib/FluentConfiguration/IndexColumnMapping.cs
Original file line number Diff line number Diff line change
@@ -1,12 +1,20 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using SheetToObjects.Lib.Validation;

namespace SheetToObjects.Lib.FluentConfiguration
{
internal class IndexColumnMapping : ColumnMapping
{
public IndexColumnMapping(int columnIndex, string propertyName, string format, List<IParsingRule> parsingRules, List<IRule> rules, object defaultValue)
: base(propertyName, format, parsingRules, rules, defaultValue)
public IndexColumnMapping(
int columnIndex,
string propertyName,
string format,
List<IParsingRule> parsingRules,
List<IRule> rules,
object defaultValue,
Func<string, object> customValueParser)
: base(propertyName, format, parsingRules, rules, defaultValue, customValueParser)
{
ColumnIndex = columnIndex;
}
Expand Down
14 changes: 11 additions & 3 deletions src/SheetToObjects.Lib/FluentConfiguration/LetterColumnMapping.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using SheetToObjects.Core;
using SheetToObjects.Lib.Validation;

Expand All @@ -8,8 +9,15 @@ internal class LetterColumnMapping : ColumnMapping
{
public string ColumnName { get; }

public LetterColumnMapping(string columnLetter, string propertyName, string format, List<IParsingRule> parsingRules, List<IRule> rules, object defaultValue)
: base(propertyName, format, parsingRules, rules, defaultValue)
public LetterColumnMapping(
string columnLetter,
string propertyName,
string format,
List<IParsingRule> parsingRules,
List<IRule> rules,
object defaultValue,
Func<string, object> customValueParser)
: base(propertyName, format, parsingRules, rules, defaultValue, customValueParser)
{
ColumnName = columnLetter;
ColumnIndex = columnLetter.ConvertExcelColumnNameToIndex();
Expand Down
15 changes: 12 additions & 3 deletions src/SheetToObjects.Lib/FluentConfiguration/NameColumnMapping.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using SheetToObjects.Lib.Validation;

namespace SheetToObjects.Lib.FluentConfiguration
Expand All @@ -9,8 +10,16 @@ internal class NameColumnMapping : ColumnMapping, IUseHeaderRow

public bool IsRequiredInHeaderRow { get; }

public NameColumnMapping(string columnName, string propertyName, string format, List<IParsingRule> parsingRules, List<IRule> rules, object defaultValue, bool isRequiredInHeaderRow)
: base(propertyName, format, parsingRules, rules, defaultValue)
public NameColumnMapping(
string columnName,
string propertyName,
string format,
List<IParsingRule> parsingRules,
List<IRule> rules,
object defaultValue,
bool isRequiredInHeaderRow,
Func<string, object> customValueParser)
: base(propertyName, format, parsingRules, rules, defaultValue, customValueParser)
{
ColumnName = columnName;
ColumnIndex = -1;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
using System.Collections.Generic;
using System;
using System.Collections.Generic;
using SheetToObjects.Lib.Validation;

namespace SheetToObjects.Lib.FluentConfiguration
Expand All @@ -8,8 +9,15 @@ internal class PropertyColumnMapping : ColumnMapping, IUseHeaderRow
public string ColumnName { get; }
public bool IsRequiredInHeaderRow { get; }

public PropertyColumnMapping(string propertyName, string format, List<IParsingRule> parsingRules, List<IRule> rules, object defaultValue, bool isRequiredInHeaderRow)
: base(propertyName, format, parsingRules, rules, defaultValue)
public PropertyColumnMapping(
string propertyName,
string format,
List<IParsingRule> parsingRules,
List<IRule> rules,
object defaultValue,
bool isRequiredInHeaderRow,
Func<string, object> customValueParser)
: base(propertyName, format, parsingRules, rules, defaultValue, customValueParser)
{
ColumnName = propertyName;
ColumnIndex = -1;
Expand Down
7 changes: 2 additions & 5 deletions src/SheetToObjects.Lib/IMapValue.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
using System;
using CSharpFunctionalExtensions;
using SheetToObjects.Lib.FluentConfiguration;
using SheetToObjects.Lib.Validation;

namespace SheetToObjects.Lib
Expand All @@ -11,10 +12,6 @@ Result<object, IValidationError> Map(
Type propertyType,
int columnIndex,
int rowIndex,
string displayName,
string propertyName,
string format,
bool isRequired,
object defaultValue);
ColumnMapping columnMapping);
}
}
6 changes: 1 addition & 5 deletions src/SheetToObjects.Lib/RowMapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -48,11 +48,7 @@ public Result<ParsedModelResult<TModel>, List<IValidationError>> Map<TModel>(Row
property.PropertyType,
columnMapping.ColumnIndex,
row.RowIndex,
columnMapping.DisplayName,
columnMapping.PropertyName,
columnMapping.Format,
columnMapping.IsRequired,
columnMapping.DefaultValue)
columnMapping)
.OnSuccess(value =>
{
if (value.ToString().IsNotNullOrEmpty())
Expand Down
45 changes: 31 additions & 14 deletions src/SheetToObjects.Lib/ValueMapper.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using System;
using CSharpFunctionalExtensions;
using SheetToObjects.Core;
using SheetToObjects.Lib.FluentConfiguration;
using SheetToObjects.Lib.Parsing;
using SheetToObjects.Lib.Validation;

Expand All @@ -19,47 +21,62 @@ public Result<object, IValidationError> Map(
Type propertyType,
int columnIndex,
int rowIndex,
string displayName,
string propertyName,
string format,
bool isRequired,
object defaultValue)
ColumnMapping columnMapping)
{
if (string.IsNullOrEmpty(value))
{
return HandleEmptyValue(isRequired, columnIndex, rowIndex, displayName, propertyName, defaultValue);
return HandleEmptyValue(columnIndex, rowIndex, columnMapping);
}

var parsingResult = _valueParser.Parse(propertyType, value, format);
if (columnMapping.CustomValueParser.IsNotNull())
{
try
{
var parsedValue = columnMapping.CustomValueParser(value);
return Result.Ok<object, IValidationError>(parsedValue);
}
catch (Exception)
{
var parsingValidationError = ParsingValidationError.CouldNotParseValue(
columnIndex,
rowIndex,
columnMapping.DisplayName,
columnMapping.PropertyName);

return Result.Fail<object, IValidationError>(parsingValidationError);
}
}

var parsingResult = _valueParser.Parse(propertyType, value, columnMapping.Format);

if (!parsingResult.IsSuccess)
{
var validationError = ParsingValidationError.CouldNotParseValue(
columnIndex,
rowIndex,
displayName,
propertyName);
columnMapping.DisplayName,
columnMapping.PropertyName);

return Result.Fail<object, IValidationError>(validationError);
}

return Result.Ok<object, IValidationError>(parsingResult.Value);
}

private static Result<object, IValidationError> HandleEmptyValue(bool isRequired, int columnIndex, int rowIndex, string displayName, string propertyName, object defaultValue)
private static Result<object, IValidationError> HandleEmptyValue(int columnIndex, int rowIndex, ColumnMapping columnMapping)
{
if (isRequired)
if (columnMapping.IsRequired)
{
var cellValueRequiredError = RuleValidationError.CellValueRequired(
columnIndex,
rowIndex,
displayName,
propertyName);
columnMapping.DisplayName,
columnMapping.PropertyName);

return Result.Fail<object, IValidationError>(cellValueRequiredError);
}

return Result.Ok<object, IValidationError>(defaultValue ?? string.Empty);
return Result.Ok<object, IValidationError>(columnMapping.DefaultValue ?? string.Empty);
}
}
}
77 changes: 77 additions & 0 deletions src/SheetToObjects.Specs/Lib/SheetMapperSpecs.cs
Original file line number Diff line number Diff line change
Expand Up @@ -356,6 +356,83 @@ public void GivenParsingShouldBeStoppedOnFirstEmptyRow_WhenSecondRowIsEmptyAndTh
result.IsSuccess.Should().BeTrue();
result.ParsedModels.Should().HaveCount(1);
}

[Fact]
public void GivenSheet_WhenCustomBooleanParserIsProvided_ValueIsConvertedToBoolean()
{
var sheetData = new SheetBuilder()
.AddHeaders("Bool")
.AddRow(r => r.AddCell(c => c.WithColumnIndex(0).WithRowIndex(1).WithValue("1").Build()).Build(1))
.Build();

var result = new SheetMapper()
.AddConfigFor<TestModel>(cfg => cfg
.HasHeaders()
.MapColumn(column => column
.WithHeader("Bool")
.IsRequired()
.ParseValueUsing(x => x.Equals("1") ? true : false)
.MapTo(t => t.BoolProperty)))
.Map<TestModel>(sheetData);

result.IsSuccess.Should().BeTrue();
result.ParsedModels.Should().HaveCount(1);
result.ParsedModels.First().ParsedModel.BoolProperty.Should().BeTrue();
}

[Fact]
public void GivenSheet_WhenCustomEnumParserIsProvided_ValueIsConvertedToEnum()
{
var sheetData = new SheetBuilder()
.AddHeaders("EnumProperty")
.AddRow(r => r.AddCell(c => c.WithColumnIndex(0).WithRowIndex(1).WithValue("Second").Build()).Build(1))
.Build();

var result = new SheetMapper()
.AddConfigFor<TestModel>(cfg => cfg
.HasHeaders()
.MapColumn(column => column
.WithHeader("EnumProperty")
.IsRequired()
.ParseValueUsing(x =>
{
switch (x)
{
case "First": return EnumModel.First;
case "Second": return EnumModel.Second;
case "Third": return EnumModel.Third;
default: return EnumModel.Default;
}
})
.MapTo(t => t.EnumProperty)))
.Map<TestModel>(sheetData);

result.IsSuccess.Should().BeTrue();
result.ParsedModels.Should().HaveCount(1);
result.ParsedModels.First().ParsedModel.EnumProperty.Should().Be(EnumModel.Second);
}

[Fact]
public void GivenSheet_WhenCustomEnumParserFails_ResultContainsFailure()
{
var sheetData = new SheetBuilder()
.AddHeaders("StringProperty")
.AddRow(r => r.AddCell(c => c.WithColumnIndex(0).WithRowIndex(1).WithValue("MyValue").Build()).Build(1))
.Build();

var result = new SheetMapper()
.AddConfigFor<TestModel>(cfg => cfg
.HasHeaders()
.MapColumn(column => column
.WithHeader("StringProperty")
.ParseValueUsing(x => throw new Exception("An exception occured"))
.MapTo(t => t.StringProperty)))
.Map<TestModel>(sheetData);

result.IsSuccess.Should().BeFalse();
result.ValidationErrors.Should().HaveCount(1);
result.ValidationErrors.First().ErrorMessage.Should().Be("Could not parse value");
}
}

public class ModelOne
Expand Down
Loading

0 comments on commit 6b3e3c8

Please sign in to comment.