Skip to content

Commit 81f4fc5

Browse files
DotNet2WebTino Klijn
andauthored
Feature/wait what its possible to skip validation and do that on your own (#36)
* Skipping validation on request * Extra tests * Extra coverage * update the documentation --------- Co-authored-by: Tino Klijn <tino@dotnet2web.nl>
1 parent 6ed15c3 commit 81f4fc5

File tree

4 files changed

+215
-74
lines changed

4 files changed

+215
-74
lines changed

CsvCore.Specs/CsvCoreReaderSpecs.cs

Lines changed: 97 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -202,7 +202,7 @@ public void Should_Read_Provided_Csv_File_When_Missing_A_Property_In_The_Header(
202202
FileHelper.DeleteTestFile(filePath);
203203
}
204204

205-
[Fact(Skip = "This test fails in the complete run, but runs succeessfully in isolation")]
205+
[Fact]
206206
public void Should_Read_Provided_Csv_File_With_Header_Using_The_Region_Delimiter_Settings()
207207
{
208208
// Arrange
@@ -334,7 +334,7 @@ public void Should_Throw_MissingFileException_When_Trying_To_Validate_The_Input_
334334
public void Should_Validate_The_Input_That_Only_Contains_Valid_Data()
335335
{
336336
// Arrange
337-
var filePath = Path.Combine( Directory.GetCurrentDirectory(), new Faker().System.FileName(CsvExtension));
337+
var filePath = Path.Combine(Directory.GetCurrentDirectory(), new Faker().System.FileName(CsvExtension));
338338
var delimiter = char.Parse(CultureInfo.CurrentCulture.TextInfo.ListSeparator);
339339

340340
var persons = new Faker<CsvContentModel>()
@@ -509,7 +509,8 @@ public void Should_Read_Provided_Csv_File_Without_Header_And_Still_Set_The_Data_
509509
}
510510

511511
[Fact]
512-
public void Should_Read_Provided_Csv_File_Without_Header_And_Still_Set_The_Data_On_The_Correct_Properties_Even_With_A_ZeroBasedModel()
512+
public void
513+
Should_Read_Provided_Csv_File_Without_Header_And_Still_Set_The_Data_On_The_Correct_Properties_Even_With_A_ZeroBasedModel()
513514
{
514515
// Arrange
515516
var directory = Directory.GetCurrentDirectory();
@@ -684,10 +685,12 @@ public void Should_Write_An_ErrorFile_Without_Using_The_WriteErrorsAt_Method()
684685
}
685686

686687
[Theory]
687-
[InlineData(@"C:\Temp\Errors")]
688-
[InlineData("")]
688+
[InlineData(@"C:\Temp\Errors", false)]
689+
[InlineData("", false)]
690+
[InlineData(@"C:\Temp\Errors", true)]
691+
[InlineData("", true)]
689692
public void Should_Validate_The_Input_When_Reading_The_Csv_File_And_Write_An_ErrorFile_To_The_Provided_Location(
690-
string errorLocation)
693+
string errorLocation, bool withoutHeader)
691694
{
692695
// Arrange
693696
var csvCoreReader = new CsvCoreReader();
@@ -712,8 +715,15 @@ public void Should_Validate_The_Input_When_Reading_The_Csv_File_And_Write_An_Err
712715
invalid.BirthDate = "01-01-2023T00:00:00";
713716
persons.Add(invalid);
714717

715-
new CsvCoreWriter()
716-
.UseDelimiter(delimiter)
718+
var csvCoreWriter = new CsvCoreWriter();
719+
720+
if (withoutHeader)
721+
{
722+
csvCoreWriter.WithoutHeader();
723+
csvCoreReader.WithoutHeader();
724+
}
725+
726+
csvCoreWriter.UseDelimiter(delimiter)
717727
.Write(filePath, persons);
718728

719729
// Act
@@ -746,7 +756,7 @@ public void Should_Validate_The_Input_When_Reading_The_Csv_File_And_Write_An_Err
746756
public void Should_Read_Provided_Csv_File_With_Header_When_A_DateTime_Is_Available_In_The_Data_And_Model()
747757
{
748758
// Arrange
749-
var dateFormat ="yyyyMMddTHHmmss";
759+
var dateFormat = "yyyyMMddTHHmmss";
750760

751761
var csvCoreReader = new CsvCoreReader();
752762

@@ -798,4 +808,82 @@ public void Should_Read_Provided_Csv_File_With_Header_When_A_DateTime_Is_Availab
798808
// Cleanup
799809
FileHelper.DeleteTestFile(filePath);
800810
}
811+
812+
[Theory]
813+
[InlineData(" ", false)]
814+
[InlineData("", false)]
815+
[InlineData(null, false)]
816+
[InlineData(" ", true)]
817+
[InlineData("", true)]
818+
[InlineData(null, true)]
819+
public void Should_Generate_A_Full_Model_With_Invalid_Records_When_Reading_The_Csv_File_Using_SkipValidation(
820+
string invalidBirthDate, bool withoutHeader)
821+
{
822+
// Arrange
823+
var csvCoreReader = new CsvCoreReader();
824+
var delimiter = char.Parse(CultureInfo.CurrentCulture.TextInfo.ListSeparator);
825+
826+
var directory = Directory.GetCurrentDirectory();
827+
var filePath = Path.Combine(directory, new Faker().System.FileName(CsvExtension));
828+
829+
var persons = new Faker<CsvContentModel>()
830+
.RuleFor(person => person.Name, faker => faker.Person.FirstName)
831+
.RuleFor(person => person.Surname, faker => faker.Person.LastName)
832+
.RuleFor(person => person.BirthDate, faker => faker.Person.DateOfBirth.ToShortDateString())
833+
.RuleFor(person => person.Email, faker => faker.Internet.Email())
834+
.Generate(5);
835+
836+
var invalid1 = new Faker<CsvContentModel>()
837+
.RuleFor(person => person.Name, faker => faker.Person.FirstName)
838+
.RuleFor(person => person.Surname, faker => faker.Person.LastName)
839+
.RuleFor(person => person.BirthDate, _ => invalidBirthDate)
840+
.RuleFor(person => person.Email, faker => faker.Internet.Email())
841+
.Generate();
842+
843+
var invalid2 = new Faker<CsvContentModel>()
844+
.RuleFor(person => person.Name, _ => null)
845+
.RuleFor(person => person.Surname, faker => faker.Person.LastName)
846+
.RuleFor(person => person.BirthDate, faker => faker.Person.DateOfBirth.ToShortDateString())
847+
.RuleFor(person => person.Email, _ => null)
848+
.Generate();
849+
850+
var anotherSetValidData = new Faker<CsvContentModel>()
851+
.RuleFor(person => person.Name, faker => faker.Person.FirstName)
852+
.RuleFor(person => person.Surname, faker => faker.Person.LastName)
853+
.RuleFor(person => person.BirthDate, faker => faker.Person.DateOfBirth.ToShortDateString())
854+
.RuleFor(person => person.Email, faker => faker.Internet.Email())
855+
.Generate(5);
856+
857+
persons.Add(invalid1);
858+
persons.Add(invalid2);
859+
persons.AddRange(anotherSetValidData);
860+
861+
var csvCoreWriter = new CsvCoreWriter();
862+
863+
if (withoutHeader)
864+
{
865+
csvCoreWriter.WithoutHeader();
866+
}
867+
868+
csvCoreWriter.UseDelimiter(delimiter).Write(filePath, persons);
869+
870+
if (withoutHeader)
871+
{
872+
csvCoreReader.WithoutHeader();
873+
}
874+
875+
// Act
876+
var result = csvCoreReader
877+
.SkipValidation()
878+
.Read<PersonModel>(filePath).ToList();
879+
880+
// Assert
881+
result.Should().NotBeEmpty();
882+
result.Count.Should().Be(12);
883+
884+
result[5].BirthDate.Should().Be(DateOnly.MinValue);
885+
886+
// Cleanup
887+
FileHelper.DeleteTestFile(filePath);
888+
}
801889
}

CsvCore/Documentation/CsvCoreReader.md

Lines changed: 19 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -86,40 +86,38 @@ When you have the model all setup use the `CsvCoreReader` to read the csv file,
8686

8787
Ofcourse you will receive csv files that are not valid, so we added some validations to the reader.
8888

89-
If you don't want to validate the csv file before reading it, just read the file like described above.
90-
9189
We will validate the data before adding them to the result models, any record that cant be parsed correctly will be added to errors.csv file.
9290

9391
The file will be stored at the location your application will be run. The filename will be the same as the original file, but we just add `_errors` to it.
9492

95-
If you need those error files to be written somewhere else simply use the `.WriteErrorsAt("AnyPath")` method on the reader
93+
If you need those error files to be written somewhere else simply use the `.SetErrorPath("AnyPath")` method on the reader
9694

9795
### Example
9896

9997
```csharp
10098
var result = csvCoreReader
10199
.SetErrorPath(@"C:\Temp\Errors") // your errors will be stored in here, ofcourse you would put this in a configuration file ;)
102-
.Read<PersonModel>(filePath);
100+
.Read<ResultModel>(Path.Combine("AnyPath", "YourFile.csv"));
103101

104102
or
105103

106104
var result = csvCoreReader
107105
.SetErrorPath() // your errors will be stored in the same location as the application is run.
108-
.Read<PersonModel>(filePath);
106+
.Read<ResultModel>(Path.Combine("AnyPath", "YourFile.csv"));
109107

110108
or
111109

112110
var result = csvCoreReader
113-
.Read<PersonModel>(filePath); // your errors will be stored in the same location as the application is run.
111+
.Read<ResultModel>(Path.Combine("AnyPath", "YourFile.csv")); // your errors will be stored in the same location as the application is run.
114112
```
115113

116-
If you want to validate the csv file before reading it, you can use the `IsValid` method.
114+
If you want to validate the csv file before reading it, you can use the `IsValid` method
117115

118116
### Example
119117

120118
```csharp
121119
var result = csvCoreReader
122-
.IsValid<PersonModel>(filePath);
120+
.IsValid<ResultModel>(filePath);
123121
```
124122

125123
The `IsValid` method will return a `List<ValidationModel>` containing:
@@ -129,18 +127,19 @@ The `IsValid` method will return a `List<ValidationModel>` containing:
129127

130128
This could be handy in numerous ways.
131129

132-
### Date/Time formats
133-
134-
This is going to be a slipery slope, but we will try to make it as easy as possible.
135-
DateTime and/or DateOnly properties are a bit tricky, because we have to parse the data in the csv file to a DateTime or DateOnly object.
136-
137-
We gave it a try, and we think we did a good job.
130+
And how about skipping the validation. If you want to skip the validation, you can use the `SkipValidation` method.
131+
### Example
138132

139-
If you have a date/time format that is not the default one, you can set the format using the `SetDateTimeFormat` method.
140133
```csharp
141-
var result = csvCoreReader
142-
.SetDateTimeFormat("dd/MM/yyyy") // Set the date format to dd/MM/yyyy
143-
.Read<PersonModel>(filePath);
134+
var result = csvCoreReader
135+
.SkipValidation()
136+
.Read<ResultModel>(Path.Combine("AnyPath", "YourFile.csv"));
144137
```
145-
Be aware these options are always tricky so if you encounter any issues with it, please report an issue at GitHub with as much information as possible.
146-
A unit test example would be awesome!
138+
139+
This will skip the validation and read the csv file without validating the data.
140+
The result will be a list of `ResultModel` objects, but the data will not be validated.
141+
142+
**A litte note about the validation:**
143+
- _If you have a non-nullable dateonly / datetime property in your model, and the csv file contains a null value, the reader will set these properties to their MinValues._
144+
145+
This way you can do whatever you want with the result.

CsvCore/Extensions/DateTypeExtensions.cs

Lines changed: 49 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -12,26 +12,48 @@ public static class DateTypeExtensions
1212
public static bool ConvertToDateTypes<T>(this string dateTime, string? dateFormat, PropertyInfo property, T target)
1313
where T : class
1414
{
15-
if (property.PropertyType == typeof(DateOnly))
16-
{
17-
if (string.IsNullOrEmpty(dateFormat))
18-
{
19-
if (Regex.IsMatch(dateTime, DateOnlyPattern))
20-
{
21-
property.SetValue(target, DateOnly.Parse(dateTime, CultureInfo.CurrentCulture));
15+
return SetDateOnly(dateTime, dateFormat, property, target) ||
16+
SetDateTime(dateTime, dateFormat, property, target);
17+
}
2218

23-
return true;
24-
}
25-
}
19+
private static bool SetDateOnly<T>(string dateTime, string? dateFormat, PropertyInfo property, T target) where T : class
20+
{
21+
if (property.PropertyType != typeof(DateOnly))
22+
{
23+
return false;
24+
}
2625

27-
if (dateTime.ValidateDateOnly(dateFormat))
26+
if (string.IsNullOrEmpty(dateFormat))
27+
{
28+
if (Regex.IsMatch(dateTime, DateOnlyPattern))
2829
{
29-
property.SetValue(target, DateOnly.ParseExact(dateTime, dateFormat!, CultureInfo.CurrentCulture));
30+
property.SetValue(target, DateOnly.Parse(dateTime, CultureInfo.CurrentCulture));
3031

3132
return true;
3233
}
3334
}
3435

36+
if (dateTime.ValidateDateOnly(dateFormat))
37+
{
38+
property.SetValue(target, DateOnly.ParseExact(dateTime, dateFormat!, CultureInfo.CurrentCulture));
39+
40+
return true;
41+
}
42+
43+
if (property.PropertyType.IsGenericType &&
44+
property.PropertyType.GetGenericTypeDefinition() == typeof(DateOnly?) &&
45+
string.IsNullOrWhiteSpace(dateTime))
46+
{
47+
property.SetValue(target, null);
48+
return true;
49+
}
50+
51+
property.SetValue(target, DateOnly.MinValue);
52+
return true;
53+
}
54+
55+
private static bool SetDateTime<T>(string dateTime, string? dateFormat, PropertyInfo property, T target) where T : class
56+
{
3557
if (property.PropertyType != typeof(DateTime))
3658
{
3759
return false;
@@ -47,16 +69,27 @@ public static bool ConvertToDateTypes<T>(this string dateTime, string? dateForma
4769
}
4870
}
4971

50-
if (!dateTime.ValidateDateTime(dateFormat))
72+
if (dateTime.ValidateDateTime(dateFormat))
5173
{
52-
return false;
74+
property.SetValue(target, DateTime.ParseExact(dateTime, dateFormat!, CultureInfo.CurrentCulture));
75+
76+
return true;
77+
}
78+
79+
if (property.PropertyType.IsGenericType &&
80+
property.PropertyType.GetGenericTypeDefinition() == typeof(DateTime?) &&
81+
string.IsNullOrWhiteSpace(dateTime))
82+
{
83+
property.SetValue(target, null);
84+
return true;
5385
}
5486

55-
property.SetValue(target, DateTime.ParseExact(dateTime, dateFormat!, CultureInfo.CurrentCulture));
87+
property.SetValue(target, DateTime.MinValue);
5688
return true;
89+
5790
}
5891

59-
public static bool ValidateDateTime(this string dateTime, string? dateFormat)
92+
private static bool ValidateDateTime(this string dateTime, string? dateFormat)
6093
{
6194
return string.IsNullOrEmpty(dateFormat)
6295
? DateTime.TryParse(dateTime, out _)

0 commit comments

Comments
 (0)