From a055be2185447ca7f03be2c18ac8433c0b3b2167 Mon Sep 17 00:00:00 2001 From: Ronny Gunawan <3048897+ronnygunawan@users.noreply.github.com> Date: Sat, 18 Nov 2023 22:01:08 +0700 Subject: [PATCH] Throw correct exception for incorrect number of columns --- CsvSerializer/CsvSerializer.csproj | 2 +- .../Internals/NativeImplGenerator.cs | 31 +++++++++++-------- CsvSerializer/SourceGenerator.cs | 4 ++- Tests/SerializerTests.cs | 24 ++++++++++++-- 4 files changed, 44 insertions(+), 17 deletions(-) diff --git a/CsvSerializer/CsvSerializer.csproj b/CsvSerializer/CsvSerializer.csproj index a9965b1..1077686 100644 --- a/CsvSerializer/CsvSerializer.csproj +++ b/CsvSerializer/CsvSerializer.csproj @@ -15,7 +15,7 @@ https://github.com/ronnygunawan/csv-serializer csv serializer deserializer parser core Fast CSV to object serializer and deserializer. - 2.0.3 + 2.0.4 true true diff --git a/CsvSerializer/Internals/NativeImplGenerator.cs b/CsvSerializer/Internals/NativeImplGenerator.cs index 7d1b873..10767a4 100644 --- a/CsvSerializer/Internals/NativeImplGenerator.cs +++ b/CsvSerializer/Internals/NativeImplGenerator.cs @@ -1,5 +1,4 @@ -using System; -using System.Collections.Generic; +using System.Collections.Generic; using System.Linq; using System.Text; using Microsoft.CodeAnalysis; @@ -92,7 +91,7 @@ private enum ParserState { InTrailingWhiteSpace } - public static void ReadNextLine(ref ReadOnlyMemory csv, ref Span> columns, char separator = ',') { + public static int ReadNextLine(ref ReadOnlyMemory csv, ref Span> columns, char separator = ',') { ReadOnlySpan span = csv.Span; int startOfLiteral = 0; int endOfLiteral = 0; @@ -106,13 +105,13 @@ public static void ReadNextLine(ref ReadOnlyMemory csv, ref Span csv, ref Span Deserialize(IFormatProvider? provider, char delimiter, bool Span> columns = new ReadOnlyMemory[{{propertySymbols.Count}}]; bool firstRow = true; while (csv.Length > 0) { - NativeStringSplitter.ReadNextLine(ref csv, ref columns, delimiter); + try { + int columnsRead = NativeStringSplitter.ReadNextLine(ref csv, ref columns, delimiter); + if (columnsRead != {{propertySymbols.Count}}) { + int endOfLine = csv.Span.IndexOf('\n'); + string line = endOfLine == -1 ? csv.ToString() : csv.Slice(0, endOfLine).ToString(); + throw new CsvFormatException(typeof({{fullTypeName}}), line, "Row must consists of {{propertySymbols.Count}} columns."); + } + } catch (IndexOutOfRangeException) { + int endOfLine = csv.Span.IndexOf('\n'); + string line = endOfLine == -1 ? csv.ToString() : csv.Slice(0, endOfLine).ToString(); + throw new CsvFormatException(typeof({{fullTypeName}}), line, "Row must consists of {{propertySymbols.Count}} columns."); + } if (firstRow) { firstRow = false; if (skipHeader) { continue; } } - if (columns.Length != {{propertySymbols.Count}}) { - int endOfLine = csv.Span.IndexOf('\n'); - string line = endOfLine == -1 ? csv.ToString() : csv.Slice(0, endOfLine).ToString(); - throw new CsvFormatException(typeof({{fullTypeName}}), line, "Row must consists of {{propertySymbols.Count}} columns."); - } {{fullTypeName}} item = Activator.CreateInstance<{{fullTypeName}}>(); if (provider is null) { {{deserializeWithoutProviderBuilder.ToString().Trim()}} diff --git a/CsvSerializer/SourceGenerator.cs b/CsvSerializer/SourceGenerator.cs index aeb51e2..3a1f35e 100644 --- a/CsvSerializer/SourceGenerator.cs +++ b/CsvSerializer/SourceGenerator.cs @@ -1,4 +1,6 @@ -using System.Diagnostics; +#if DEBUG +using System.Diagnostics; +#endif using Csv.Internals; using Microsoft.CodeAnalysis; diff --git a/Tests/SerializerTests.cs b/Tests/SerializerTests.cs index 5f49d45..02d2536 100644 --- a/Tests/SerializerTests.cs +++ b/Tests/SerializerTests.cs @@ -183,8 +183,8 @@ public void CsvCanBeDeserializedToPrivateType() { [Fact] public void HeaderNameCanBeRenamedUsingAttribute() { ModelWithColumnNames[] items = { - new ModelWithColumnNames { ItemName = "Coconut", WeightInGrams = 900 }, - new ModelWithColumnNames { ItemName = "Jackfruit", WeightInGrams = 1200 } + new() { ItemName = "Coconut", WeightInGrams = 900 }, + new() { ItemName = "Jackfruit", WeightInGrams = 1200 } }; string csv = CsvSerializer.Serialize(items, withHeaders: true); string[] lines = csv.Split("\r\n"); @@ -233,6 +233,26 @@ string Serialize(T item) { True,102,-100,-200,200,-3000,3000,-40000,40000,1E+14,1.7837193718273812E+19,989898989898,"CSV Serializer","8/23/2019 12:00:00 AM","http://localhost:5000/",OK """); } + + [Fact] + public void CannotDeserializeRowWithExtraColumns() { + string csv = """ + "Bool","Byte","SByte","Short","UShort","Int","UInt","Long","ULong","Float","Double","Decimal","String","DateTime","Uri","StatusCode" + True,102,-100,-200,200,-3000,3000,-40000,40000,1E+14,1.7837193718273812E+19,989898989898,"CSV Serializer","08/23/2019 00:00:00","http://localhost:5000/",OK,foo,bar + """; + new Action(() => CsvSerializer.Deserialize(csv, hasHeaders: true, provider: CultureInfo.InvariantCulture)) + .Should().Throw("Row must consists of 16 columns."); + } + + [Fact] + public void CannotDeserializeRowWithMissingColumns() { + string csv = """ + "Bool","Byte","SByte","Short","UShort","Int","UInt","Long","ULong","Float","Double","Decimal","String","DateTime","Uri","StatusCode" + True,102,-100,-200,200,-3000,3000,-40000,40000,1E+14,1.7837193718273812E+19,989898989898,"CSV Serializer","08/23/2019 00:00:00","http://localhost:5000/" + """; + new Action(() => CsvSerializer.Deserialize(csv, hasHeaders: true, provider: CultureInfo.InvariantCulture)) + .Should().Throw("Row must consists of 16 columns."); + } } public class ExcelModel {