From ad9713e3e8fd32d8dcaad41e59d119990b17ff84 Mon Sep 17 00:00:00 2001 From: Matthew Hamilton Date: Sun, 5 Aug 2018 22:03:36 -0500 Subject: [PATCH 1/4] Refactor error handling to use a context object for easier introduction of new error handling information and add line number to it. --- src/FlatFile.Core/Base/FlatFileEngine.cs | 6 +- .../Base/FlatFileErrorContext.cs | 38 ++++++++ src/FlatFile.Core/FlatFile.Core.csproj | 1 + .../DelimetedFileMultiEngine.cs | 6 +- .../Implementation/DelimitedFileEngine.cs | 2 +- .../DelimitedFileEngineFactory.cs | 39 +++++++- .../Implementation/FixedLengthFileEngine.cs | 2 +- .../FixedLengthFileEngineFactory.cs | 39 +++++++- .../FixedLengthFileMultiEngine.cs | 6 +- .../Delimited/DelimitedErrorHandlingTests.cs | 93 ++++++++++++++++++ .../FixedLengthErrorHandlingTests.cs | 95 +++++++++++++++++++ src/FlatFile.Tests/FlatFile.Tests.csproj | 11 +++ src/FlatFile.Tests/packages.config | 1 + 13 files changed, 325 insertions(+), 14 deletions(-) create mode 100644 src/FlatFile.Core/Base/FlatFileErrorContext.cs create mode 100644 src/FlatFile.Tests/Delimited/DelimitedErrorHandlingTests.cs create mode 100644 src/FlatFile.Tests/FixedLength/FixedLengthErrorHandlingTests.cs diff --git a/src/FlatFile.Core/Base/FlatFileEngine.cs b/src/FlatFile.Core/Base/FlatFileEngine.cs index 22d18a3..9734114 100644 --- a/src/FlatFile.Core/Base/FlatFileEngine.cs +++ b/src/FlatFile.Core/Base/FlatFileEngine.cs @@ -18,7 +18,7 @@ public abstract class FlatFileEngine : IFlatF /// /// The handle entry read error func /// - private readonly Func _handleEntryReadError; + private readonly Func _handleEntryReadError; /// /// Gets the line builder. @@ -42,7 +42,7 @@ public abstract class FlatFileEngine : IFlatF /// Initializes a new instance of the class. /// /// The handle entry read error. - protected FlatFileEngine(Func handleEntryReadError = null) + protected FlatFileEngine(Func handleEntryReadError = null) { _handleEntryReadError = handleEntryReadError; } @@ -85,7 +85,7 @@ protected FlatFileEngine(Func handleEntryReadError = nu throw; } - if (!_handleEntryReadError(line, ex)) + if (!_handleEntryReadError(new FlatFileErrorContext(line, lineNumber, ex))) { throw; } diff --git a/src/FlatFile.Core/Base/FlatFileErrorContext.cs b/src/FlatFile.Core/Base/FlatFileErrorContext.cs new file mode 100644 index 0000000..35f972c --- /dev/null +++ b/src/FlatFile.Core/Base/FlatFileErrorContext.cs @@ -0,0 +1,38 @@ +namespace FlatFile.Core.Base +{ + using System; + + /// + /// Provides information about a file parsing error. + /// + public struct FlatFileErrorContext + { + /// + /// Initializes a new instance of . + /// + /// The content of the line on which the error occurred. + /// The line numer at which the error occurred. + /// The error that occurred. + public FlatFileErrorContext(string line, int lineNumber, Exception exception) + { + Line = line; + LineNumber = lineNumber; + Exception = exception; + } + + /// + /// The content of the line on which the error occurred. + /// + public string Line { get; } + + /// + /// The line numer at which the error occurred. + /// + public int LineNumber { get; } + + /// + /// The error that occurred. + /// + public Exception Exception { get; } + } +} \ No newline at end of file diff --git a/src/FlatFile.Core/FlatFile.Core.csproj b/src/FlatFile.Core/FlatFile.Core.csproj index 08ab631..4c12d6d 100644 --- a/src/FlatFile.Core/FlatFile.Core.csproj +++ b/src/FlatFile.Core/FlatFile.Core.csproj @@ -98,6 +98,7 @@ + diff --git a/src/FlatFile.Delimited/Implementation/DelimetedFileMultiEngine.cs b/src/FlatFile.Delimited/Implementation/DelimetedFileMultiEngine.cs index 215d974..ff16b8c 100644 --- a/src/FlatFile.Delimited/Implementation/DelimetedFileMultiEngine.cs +++ b/src/FlatFile.Delimited/Implementation/DelimetedFileMultiEngine.cs @@ -18,7 +18,7 @@ public class DelimitedFileMultiEngine : FlatFileEngine /// The handle entry read error func /// - readonly Func handleEntryReadError; + readonly Func handleEntryReadError; /// /// The layout descriptors for this engine /// @@ -58,7 +58,7 @@ internal DelimitedFileMultiEngine( Func typeSelectorFunc, IDelimitedLineBuilderFactory lineBuilderFactory, IDelimitedLineParserFactory lineParserFactory, - Func handleEntryReadError = null) + Func handleEntryReadError = null) { if (typeSelectorFunc == null) throw new ArgumentNullException("typeSelectorFunc"); this.layoutDescriptors = layoutDescriptors.ToList(); @@ -170,7 +170,7 @@ public void Read(Stream stream) throw; } - if (!handleEntryReadError(line, ex)) + if (!handleEntryReadError(new FlatFileErrorContext(line, lineNumber, ex))) { throw; } diff --git a/src/FlatFile.Delimited/Implementation/DelimitedFileEngine.cs b/src/FlatFile.Delimited/Implementation/DelimitedFileEngine.cs index cccbd39..db777ee 100644 --- a/src/FlatFile.Delimited/Implementation/DelimitedFileEngine.cs +++ b/src/FlatFile.Delimited/Implementation/DelimitedFileEngine.cs @@ -38,7 +38,7 @@ internal DelimitedFileEngine( IDelimitedLayoutDescriptor layoutDescriptor, IDelimitedLineBuilderFactory builderFactory, IDelimitedLineParserFactory parserFactory, - Func handleEntryReadError = null) + Func handleEntryReadError = null) : base(handleEntryReadError) { _builderFactory = builderFactory; diff --git a/src/FlatFile.Delimited/Implementation/DelimitedFileEngineFactory.cs b/src/FlatFile.Delimited/Implementation/DelimitedFileEngineFactory.cs index a91ad3a..2d49dcb 100644 --- a/src/FlatFile.Delimited/Implementation/DelimitedFileEngineFactory.cs +++ b/src/FlatFile.Delimited/Implementation/DelimitedFileEngineFactory.cs @@ -51,10 +51,25 @@ public IFlatFileEngine GetEngine( descriptor, new DelimitedLineBuilderFactory(), new DelimitedLineParserFactory(), - handleEntryReadError); + ctx => handleEntryReadError(ctx.Line, ctx.Exception)); } - + /// + /// Gets the . + /// + /// The descriptor. + /// The handle entry read error func. + /// IFlatFileEngine. + public IFlatFileEngine GetEngine( + IDelimitedLayoutDescriptor descriptor, + Func handleEntryReadError) + { + return new DelimitedFileEngine( + descriptor, + new DelimitedLineBuilderFactory(), + new DelimitedLineParserFactory(), + handleEntryReadError); + } /// /// Gets the . @@ -67,6 +82,26 @@ public IFlatFileMultiEngine GetEngine( IEnumerable layoutDescriptors, Func typeSelectorFunc, Func handleEntryReadError = null) + { + return new DelimitedFileMultiEngine( + layoutDescriptors, + typeSelectorFunc, + new DelimitedLineBuilderFactory(), + lineParserFactory, + ctx => handleEntryReadError(ctx.Line, ctx.Exception)); + } + + /// + /// Gets the . + /// + /// The layout descriptors. + /// The type selector function. + /// The handle entry read error func. + /// IFlatFileMultiEngine. + public IFlatFileMultiEngine GetEngine( + IEnumerable layoutDescriptors, + Func typeSelectorFunc, + Func handleEntryReadError) { return new DelimitedFileMultiEngine( layoutDescriptors, diff --git a/src/FlatFile.FixedLength/Implementation/FixedLengthFileEngine.cs b/src/FlatFile.FixedLength/Implementation/FixedLengthFileEngine.cs index 22686c9..11770cd 100644 --- a/src/FlatFile.FixedLength/Implementation/FixedLengthFileEngine.cs +++ b/src/FlatFile.FixedLength/Implementation/FixedLengthFileEngine.cs @@ -33,7 +33,7 @@ internal FixedLengthFileEngine( ILayoutDescriptor layoutDescriptor, IFixedLengthLineBuilderFactory lineBuilderFactory, IFixedLengthLineParserFactory lineParserFactory, - Func handleEntryReadError = null) : base(handleEntryReadError) + Func handleEntryReadError = null) : base(handleEntryReadError) { this.lineBuilderFactory = lineBuilderFactory; this.lineParserFactory = lineParserFactory; diff --git a/src/FlatFile.FixedLength/Implementation/FixedLengthFileEngineFactory.cs b/src/FlatFile.FixedLength/Implementation/FixedLengthFileEngineFactory.cs index 132ee69..b3c8a16 100644 --- a/src/FlatFile.FixedLength/Implementation/FixedLengthFileEngineFactory.cs +++ b/src/FlatFile.FixedLength/Implementation/FixedLengthFileEngineFactory.cs @@ -48,7 +48,24 @@ public IFlatFileEngine GetEngine( return new FixedLengthFileEngine( descriptor, new FixedLengthLineBuilderFactory(), - lineParserFactory, + lineParserFactory, + ctx => handleEntryReadError(ctx.Line, ctx.Exception)); + } + + /// + /// Gets the . + /// + /// The descriptor. + /// The handle entry read error func. + /// IFlatFileEngine. + public IFlatFileEngine GetEngine( + ILayoutDescriptor descriptor, + Func handleEntryReadError) + { + return new FixedLengthFileEngine( + descriptor, + new FixedLengthLineBuilderFactory(), + lineParserFactory, handleEntryReadError); } @@ -63,6 +80,26 @@ public IFlatFileMultiEngine GetEngine( IEnumerable> layoutDescriptors, Func typeSelectorFunc, Func handleEntryReadError = null) + { + return new FixedLengthFileMultiEngine( + layoutDescriptors, + typeSelectorFunc, + new FixedLengthLineBuilderFactory(), + lineParserFactory, + ctx => handleEntryReadError(ctx.Line, ctx.Exception)); + } + + /// + /// Gets the . + /// + /// The layout descriptors. + /// The type selector function. + /// The handle entry read error func. + /// IFlatFileMultiEngine. + public IFlatFileMultiEngine GetEngine( + IEnumerable> layoutDescriptors, + Func typeSelectorFunc, + Func handleEntryReadError) { return new FixedLengthFileMultiEngine( layoutDescriptors, diff --git a/src/FlatFile.FixedLength/Implementation/FixedLengthFileMultiEngine.cs b/src/FlatFile.FixedLength/Implementation/FixedLengthFileMultiEngine.cs index 9226d09..0b238f0 100644 --- a/src/FlatFile.FixedLength/Implementation/FixedLengthFileMultiEngine.cs +++ b/src/FlatFile.FixedLength/Implementation/FixedLengthFileMultiEngine.cs @@ -18,7 +18,7 @@ public class FixedLengthFileMultiEngine : FlatFileEngine /// The handle entry read error func /// - readonly Func handleEntryReadError; + readonly Func handleEntryReadError; /// /// The layout descriptors for this engine /// @@ -58,7 +58,7 @@ internal FixedLengthFileMultiEngine( Func typeSelectorFunc, IFixedLengthLineBuilderFactory lineBuilderFactory, IFixedLengthLineParserFactory lineParserFactory, - Func handleEntryReadError = null) + Func handleEntryReadError = null) { if (typeSelectorFunc == null) throw new ArgumentNullException("typeSelectorFunc"); this.layoutDescriptors = layoutDescriptors.ToList(); @@ -191,7 +191,7 @@ private void ReadInternal(StreamReader reader) throw; } - if (!handleEntryReadError(line, ex)) + if (!handleEntryReadError(new FlatFileErrorContext(line, lineNumber, ex))) { throw; } diff --git a/src/FlatFile.Tests/Delimited/DelimitedErrorHandlingTests.cs b/src/FlatFile.Tests/Delimited/DelimitedErrorHandlingTests.cs new file mode 100644 index 0000000..cdc5593 --- /dev/null +++ b/src/FlatFile.Tests/Delimited/DelimitedErrorHandlingTests.cs @@ -0,0 +1,93 @@ +using FakeItEasy; +using FlatFile.Core.Base; +using FlatFile.Delimited; +using FlatFile.Delimited.Implementation; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using Xunit; + +namespace FlatFile.Tests.Delimited +{ + public class DelimitedErrorHandlingTests + { + private IDelimitedLayoutDescriptor layout; + readonly IDelimitedLineParserFactory lineParserFactory; + readonly IList errorContexts = new List(); + + const string TestData = +@"S,Test Description,00042 +S,Test Description,00043 +S,Test Description,00044"; + + public DelimitedErrorHandlingTests() + { + layout = A.Fake(); + A.CallTo(() => layout.TargetType).Returns(typeof(Record)); + + lineParserFactory = A.Fake(); + A.CallTo(() => lineParserFactory.GetParser(A.Ignored)) + .Returns(new FakeLineParser()); + + new DelimitedLineParserFactory(new Dictionary + { + [typeof(Record)] = typeof(FakeLineParser) + }); + } + + [Fact] + public void ErrorContextShouldProvideAccurateInformation() + { + var engine = new DelimitedFileEngine( + layout, + A.Fake(), + lineParserFactory, + HandleError); + + using (var stream = new MemoryStream(Encoding.Default.GetBytes(TestData))) + engine.Read(stream).ToList(); + + Assert.Equal(3, errorContexts.Count); + Assert.Equal(new[] { 1, 2, 3 }, errorContexts.Select(ctx => ctx.LineNumber)); + Assert.Equal(TestData.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries), errorContexts.Select(ctx => ctx.Line)); + Assert.All(errorContexts, ctx => Assert.Equal("Parsing failed!", ctx.Exception.Message)); + } + + [Fact] + public void MultiEngineErrorContextShouldProvideAccurateInformation() + { + var engine = new DelimitedFileMultiEngine( + new[] { layout }, + l => typeof(Record), + A.Fake(), + lineParserFactory, + HandleError); + + using (var stream = new MemoryStream(Encoding.Default.GetBytes(TestData))) + engine.Read(stream); + + Assert.Equal(3, errorContexts.Count); + Assert.Equal(new[] { 1, 2, 3 }, errorContexts.Select(ctx => ctx.LineNumber)); + Assert.Equal(TestData.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries), errorContexts.Select(ctx => ctx.Line)); + Assert.All(errorContexts, ctx => Assert.Equal("Parsing failed!", ctx.Exception.Message)); + } + + private bool HandleError(FlatFileErrorContext context) + { + errorContexts.Add(context); + return true; + } + + private class FakeLineParser : IDelimitedLineParser + { + public TEntity ParseLine(string line, TEntity entity) where TEntity : new() + { + throw new Exception("Parsing failed!"); + } + } + + private class Record { } + } +} diff --git a/src/FlatFile.Tests/FixedLength/FixedLengthErrorHandlingTests.cs b/src/FlatFile.Tests/FixedLength/FixedLengthErrorHandlingTests.cs new file mode 100644 index 0000000..d400cb0 --- /dev/null +++ b/src/FlatFile.Tests/FixedLength/FixedLengthErrorHandlingTests.cs @@ -0,0 +1,95 @@ +using FakeItEasy; +using FlatFile.Core; +using FlatFile.Core.Base; +using FlatFile.FixedLength; +using FlatFile.FixedLength.Implementation; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using Xunit; + +namespace FlatFile.Tests.FixedLength +{ + public class FixedLengthErrorHandlingTests + { + private ILayoutDescriptor layout; + readonly IFixedLengthLineParserFactory lineParserFactory; + readonly IList errorContexts = new List(); + + const string TestData = +@"STest Description 00042 +STest Description 00043 +STest Description 00044"; + + public FixedLengthErrorHandlingTests() + { + layout = A.Fake>(); + A.CallTo(() => layout.TargetType).Returns(typeof(Record)); + + lineParserFactory = new FixedLengthLineParserFactory(new Dictionary + { + [typeof(Record)] = typeof(FakeLineParser) + }); + } + + [Fact] + public void ErrorContextShouldProvideAccurateInformation() + { + var engine = new FixedLengthFileEngine( + layout, + A.Fake(), + lineParserFactory, + HandleError); + + using (var stream = new MemoryStream(Encoding.Default.GetBytes(TestData))) + engine.Read(stream).ToList(); + + Assert.Equal(3, errorContexts.Count); + Assert.Equal(new[] { 1, 2, 3 }, errorContexts.Select(ctx => ctx.LineNumber)); + Assert.Equal(TestData.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries), errorContexts.Select(ctx => ctx.Line)); + Assert.All(errorContexts, ctx => Assert.Equal("Parsing failed!", ctx.Exception.Message)); + } + + [Fact] + public void MultiEngineErrorContextShouldProvideAccurateInformation() + { + var engine = new FixedLengthFileMultiEngine( + new[] { layout }, + (l, i) => typeof(Record), + A.Fake(), + lineParserFactory, + HandleError); + + using (var stream = new MemoryStream(Encoding.Default.GetBytes(TestData))) + engine.Read(stream); + + Assert.Equal(3, errorContexts.Count); + Assert.Equal(new[] { 1, 2, 3 }, errorContexts.Select(ctx => ctx.LineNumber)); + Assert.Equal(TestData.Split(new[] { '\r', '\n' }, StringSplitOptions.RemoveEmptyEntries), errorContexts.Select(ctx => ctx.Line)); + Assert.All(errorContexts, ctx => Assert.Equal("Parsing failed!", ctx.Exception.Message)); + } + + private bool HandleError(FlatFileErrorContext context) + { + errorContexts.Add(context); + return true; + } + + private class FakeLineParser : IFixedLengthLineParser + { + public FakeLineParser(ILayoutDescriptor descriptor) + { + + } + + public TEntity ParseLine(string line, TEntity entity) where TEntity : new() + { + throw new Exception("Parsing failed!"); + } + } + + private class Record { } + } +} diff --git a/src/FlatFile.Tests/FlatFile.Tests.csproj b/src/FlatFile.Tests/FlatFile.Tests.csproj index 57f98f9..90bead5 100644 --- a/src/FlatFile.Tests/FlatFile.Tests.csproj +++ b/src/FlatFile.Tests/FlatFile.Tests.csproj @@ -1,5 +1,6 @@  + Debug @@ -13,6 +14,8 @@ 512 ..\ true + + true @@ -81,8 +84,10 @@ + + @@ -138,6 +143,12 @@ + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + +