Skip to content
This repository was archived by the owner on Jul 28, 2025. It is now read-only.

Commit ed3244d

Browse files
Merge branch 'main' into feat/DTOSS-9158-file-validation-parsing-failures
2 parents 7472d74 + 9713bf5 commit ed3244d

9 files changed

Lines changed: 437 additions & 6 deletions

File tree

Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
using System.Text.RegularExpressions;
2+
using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Models;
3+
4+
namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation;
5+
6+
public partial class FileValidator : IFileValidator
7+
{
8+
private readonly HeaderFieldRegexValidator _headerExtractIdValidator = new(
9+
x => x.ExtractId, "Extract ID", ExtractIdRegex(),
10+
ErrorCodes.MissingExtractId, ErrorCodes.InvalidExtractId);
11+
12+
private readonly HeaderFieldRegexValidator _headerIdRecordCountValidator = new(
13+
x => x.RecordCount, "Record count", RecordCountRegex(),
14+
ErrorCodes.MissingRecordCount, ErrorCodes.InvalidRecordCount);
15+
16+
public IEnumerable<ValidationError> Validate(ParsedFile file)
17+
{
18+
return ValidateHeaderPresence(file)
19+
.Concat(ValidateTrailerPresence(file))
20+
.Concat(ValidateExtractId(file))
21+
.Concat(ValidateRecordCount(file));
22+
}
23+
24+
private static IEnumerable<ValidationError> ValidateHeaderPresence(ParsedFile file)
25+
{
26+
if (file.FileHeader == null)
27+
{
28+
yield return new ValidationError
29+
{
30+
Code = ErrorCodes.MissingHeader,
31+
Error = "Header is missing",
32+
Scope = ValidationErrorScope.File
33+
};
34+
}
35+
}
36+
37+
private static IEnumerable<ValidationError> ValidateTrailerPresence(ParsedFile file)
38+
{
39+
if (file.FileTrailer == null)
40+
{
41+
yield return new ValidationError
42+
{
43+
Code = ErrorCodes.MissingTrailer,
44+
Error = "Trailer is missing",
45+
Scope = ValidationErrorScope.File
46+
};
47+
}
48+
}
49+
50+
private IEnumerable<ValidationError> ValidateExtractId(ParsedFile file)
51+
{
52+
if (file.FileHeader == null) yield break;
53+
54+
foreach (var error in _headerExtractIdValidator.Validate(file))
55+
{
56+
yield return error;
57+
}
58+
59+
if (file.FileTrailer != null && file.FileHeader.ExtractId != file.FileTrailer.ExtractId)
60+
{
61+
yield return new ValidationError
62+
{
63+
Field = "Extract ID",
64+
Code = ErrorCodes.InconsistentExtractId,
65+
Error = "Extract ID does not match value in header",
66+
Scope = ValidationErrorScope.Trailer
67+
};
68+
}
69+
}
70+
71+
private IEnumerable<ValidationError> ValidateRecordCount(ParsedFile file)
72+
{
73+
if (file.FileHeader == null) yield break;
74+
75+
var headerRecordCountErrors = _headerIdRecordCountValidator.Validate(file).ToList();
76+
77+
foreach (var error in headerRecordCountErrors)
78+
{
79+
yield return error;
80+
}
81+
82+
if (file.FileTrailer != null && file.FileHeader.RecordCount != file.FileTrailer.RecordCount)
83+
{
84+
yield return new ValidationError
85+
{
86+
Field = "Record count",
87+
Code = ErrorCodes.InconsistentRecordCount,
88+
Error = "Record count does not match value in header",
89+
Scope = ValidationErrorScope.Trailer
90+
};
91+
}
92+
else if (headerRecordCountErrors.Count == 0 &&
93+
file.DataRecords.Count != int.Parse(file.FileHeader.RecordCount!))
94+
{
95+
yield return new ValidationError
96+
{
97+
Code = ErrorCodes.UnexpectedRecordCount,
98+
Error = "Record count does not match value in header and trailer",
99+
Scope = ValidationErrorScope.File
100+
};
101+
}
102+
}
103+
104+
[GeneratedRegex(@"^\d{8}$")]
105+
private static partial Regex ExtractIdRegex();
106+
107+
[GeneratedRegex(@"^(?!000000)\d{6}$")]
108+
private static partial Regex RecordCountRegex();
109+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
using System.Linq.Expressions;
2+
using System.Text.RegularExpressions;
3+
using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Models;
4+
5+
namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation;
6+
7+
public class HeaderFieldRegexValidator(
8+
Expression<Func<FileHeaderRecord, string?>> fieldSelector,
9+
string fieldName,
10+
Regex pattern,
11+
string errorCodeMissing,
12+
string errorCodeInvalidFormat)
13+
: IFileValidator
14+
{
15+
public IEnumerable<ValidationError> Validate(ParsedFile file)
16+
{
17+
var header = file.FileHeader!;
18+
var value = fieldSelector.Compile().Invoke(header);
19+
20+
if (value == null)
21+
{
22+
yield return new ValidationError
23+
{
24+
Scope = ValidationErrorScope.Header,
25+
Field = fieldName,
26+
Error = $"{fieldName} is missing",
27+
Code = errorCodeMissing,
28+
};
29+
yield break;
30+
}
31+
32+
if (!pattern.IsMatch(value))
33+
{
34+
yield return new ValidationError
35+
{
36+
Scope = ValidationErrorScope.Header,
37+
Field = fieldName,
38+
Error = $"{fieldName} is in an invalid format",
39+
Code = errorCodeInvalidFormat,
40+
};
41+
}
42+
}
43+
}

src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/IFileValidator.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation;
44

5-
// TODO - create a whole bunch of implementations of this to perform the validation against NBSS Appointment events files
65
public interface IFileValidator
76
{
87
IEnumerable<ValidationError> Validate(ParsedFile file);

src/ServiceLayer.Mesh/FileTypes/NbssAppointmentEvents/Validation/ValidatorRegistry.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using System.Runtime.InteropServices.JavaScript;
21
using System.Text.RegularExpressions;
32

43
namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation;
@@ -69,7 +68,7 @@ public static IEnumerable<IRecordValidator> GetAllRecordValidators()
6968

7069
public static IEnumerable<IFileValidator> GetAllFileValidators()
7170
{
72-
return [];
71+
return [new FileValidator()];
7372
}
7473

7574
[GeneratedRegex(@"^[BCU]$", RegexOptions.Compiled)]

tests/ServiceLayer.Mesh.Tests/FileTypes/NbssAppointmentEvents/TestDataBuilder.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ public static ParsedFile BuildValidParsedFile(int numberOfRecords = 3)
1212
ExtractId = "00000107",
1313
TransferStartDate = "20250519",
1414
TransferStartTime = "153806",
15-
RecordCount = numberOfRecords.ToString()
15+
RecordCount = numberOfRecords.ToString("D6")
1616
};
1717

1818
var trailer = new FileTrailerRecord
@@ -21,7 +21,7 @@ public static ParsedFile BuildValidParsedFile(int numberOfRecords = 3)
2121
ExtractId = "00000107",
2222
TransferEndDate = "20250519",
2323
TransferEndTime = "153957",
24-
RecordCount = numberOfRecords.ToString()
24+
RecordCount = numberOfRecords.ToString("D6")
2525
};
2626

2727
var dataRecords = Enumerable.Range(1, numberOfRecords)

0 commit comments

Comments
 (0)