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

Commit 9191037

Browse files
authored
DTOSS-9177: Abort validation if too many errors are returned (#42)
1 parent 741cbfe commit 9191037

File tree

9 files changed

+301
-31
lines changed

9 files changed

+301
-31
lines changed

src/ServiceLayer.Mesh/Configuration/AppConfiguration.cs

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -6,32 +6,21 @@ public class AppConfiguration :
66
IFileTransformQueueClientConfiguration,
77
IFileTransformFunctionConfiguration,
88
IFileRetryFunctionConfiguration,
9-
IMeshHandshakeFunctionConfiguration
9+
IMeshHandshakeFunctionConfiguration,
10+
IValidationRunnerConfiguration
1011
{
1112
public string NbssMeshMailboxId => GetRequired("NbssMailboxId");
1213

1314
public string FileExtractQueueName => GetRequired("FileExtractQueueName");
1415

1516
public string FileTransformQueueName => GetRequired("FileTransformQueueName");
1617

17-
public int StaleHours => GetRequiredInt("StaleHours");
18+
public int MaximumValidationErrors => GetOptionalInt("MaximumValidationErrors", 100);
1819

19-
private static string GetRequired(string key)
20-
{
21-
var value = EnvironmentVariables.GetRequired(key);
20+
public int StaleHours => GetOptionalInt("StaleHours", 12);
2221

23-
return value;
24-
}
25-
26-
private static int GetRequiredInt(string key)
27-
{
28-
var value = GetRequired(key);
29-
30-
if (!int.TryParse(value, out var intValue))
31-
{
32-
throw new InvalidOperationException($"Environment variable '{key}' is not a valid integer");
33-
}
34-
35-
return intValue;
36-
}
22+
private static string GetRequired(string key) =>
23+
EnvironmentVariables.GetRequired(key);
24+
private static int GetOptionalInt(string key, int defaultValue) =>
25+
EnvironmentVariables.GetOptionalInt(key, defaultValue);
3726
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
namespace ServiceLayer.Mesh.Configuration;
2+
3+
public interface IValidationRunnerConfiguration
4+
{
5+
int MaximumValidationErrors { get; }
6+
}

src/ServiceLayer.Mesh/Configuration/ServiceCollectionExtensions.cs

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,16 @@ internal static class ServiceCollectionExtensions
66
{
77
internal static IServiceCollection AddApplicationConfiguration(this IServiceCollection services)
88
{
9-
services.AddTransient<IFileDiscoveryFunctionConfiguration, AppConfiguration>();
10-
services.AddTransient<IFileExtractQueueClientConfiguration, AppConfiguration>();
11-
services.AddTransient<IFileTransformQueueClientConfiguration, AppConfiguration>();
12-
services.AddTransient<IMeshHandshakeFunctionConfiguration, AppConfiguration>();
13-
services.AddTransient<IFileRetryFunctionConfiguration, AppConfiguration>();
14-
services.AddTransient<IFileTransformFunctionConfiguration, AppConfiguration>();
9+
var implementationType = typeof(AppConfiguration);
10+
11+
var interfaces = implementationType
12+
.GetInterfaces()
13+
.Where(i => i.Namespace == implementationType.Namespace);
14+
15+
foreach (var serviceType in interfaces)
16+
{
17+
services.AddTransient(serviceType, implementationType);
18+
}
1519

1620
return services;
1721
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,4 +71,5 @@ public static class ErrorCodes
7171
public const string MissingActionTimestamp = "NBSSAPPT067";
7272
public const string InvalidActionTimestamp = "NBSSAPPT068";
7373
public const string UnknownRecordTypeIdentifier = "NBSSAPPT069";
74+
public const string ValidationAborted = "NBSSAPPT999";
7475
}

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

Lines changed: 49 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
1+
using ServiceLayer.Mesh.Configuration;
12
using ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Models;
23

34
namespace ServiceLayer.Mesh.FileTypes.NbssAppointmentEvents.Validation;
45

56
public class ValidationRunner(
7+
IValidationRunnerConfiguration configuration,
68
IEnumerable<IFileValidator> fileValidators,
79
IEnumerable<IRecordValidator> recordValidators)
810
: IValidationRunner
@@ -11,18 +13,61 @@ public IList<ValidationError> Validate(ParsedFile file)
1113
{
1214
var errors = new List<ValidationError>();
1315

16+
RunFileValidators(file, errors);
17+
if (errors.Count >= configuration.MaximumValidationErrors)
18+
{
19+
return FinalizeEarly(errors);
20+
}
21+
22+
RunRecordValidators(file, errors);
23+
if (errors.Count >= configuration.MaximumValidationErrors)
24+
{
25+
return FinalizeEarly(errors);
26+
}
27+
28+
return errors;
29+
}
30+
31+
private void RunFileValidators(ParsedFile file, List<ValidationError> errors)
32+
{
1433
foreach (var validator in fileValidators)
1534
{
16-
errors.AddRange(validator.Validate(file));
35+
var results = validator.Validate(file);
36+
AddErrorsWithCap(results, errors);
37+
if (errors.Count >= configuration.MaximumValidationErrors) return;
1738
}
39+
}
1840

19-
foreach (var dataRecord in file.DataRecords)
41+
private void RunRecordValidators(ParsedFile file, List<ValidationError> errors)
42+
{
43+
foreach (var record in file.DataRecords)
2044
{
21-
foreach (var recordValidator in recordValidators)
45+
foreach (var validator in recordValidators)
2246
{
23-
errors.AddRange(recordValidator.Validate(dataRecord));
47+
var results = validator.Validate(record);
48+
AddErrorsWithCap(results, errors);
49+
if (errors.Count >= configuration.MaximumValidationErrors) return;
2450
}
2551
}
52+
}
53+
54+
private void AddErrorsWithCap(IEnumerable<ValidationError> newErrors, List<ValidationError> existingErrors)
55+
{
56+
foreach (var error in newErrors)
57+
{
58+
if (existingErrors.Count >= configuration.MaximumValidationErrors) break;
59+
existingErrors.Add(error);
60+
}
61+
}
62+
63+
private List<ValidationError> FinalizeEarly(List<ValidationError> errors)
64+
{
65+
errors.Add(new ValidationError
66+
{
67+
Code = ErrorCodes.ValidationAborted,
68+
Error = $"Validation aborted after {configuration.MaximumValidationErrors} errors encountered",
69+
Scope = ValidationErrorScope.File
70+
});
2671

2772
return errors;
2873
}

src/ServiceLayer.Mesh/ValidationError.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ public class ValidationError
44
{
55
public int? RowNumber { get; set; }
66

7-
public required string Field { get; set; }
7+
public string? Field { get; set; }
88

99
public required string Code { get; set; }
1010

src/ServiceLayer.Shared/EnvironmentVariables.cs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,4 +19,33 @@ public static string GetRequired(string key)
1919

2020
return value;
2121
}
22+
23+
public static int GetRequiredInt(string key)
24+
{
25+
var value = GetRequired(key);
26+
27+
if (!int.TryParse(value, out var intValue))
28+
{
29+
throw new InvalidOperationException($"Environment variable '{key}' is not a valid integer");
30+
}
31+
32+
return intValue;
33+
}
34+
35+
public static int GetOptionalInt(string key, int defaultValue)
36+
{
37+
var value = Environment.GetEnvironmentVariable(key);
38+
39+
if (string.IsNullOrWhiteSpace(value))
40+
{
41+
return defaultValue;
42+
}
43+
44+
if (!int.TryParse(value, out var intValue))
45+
{
46+
throw new InvalidOperationException($"Environment variable '{key}' is not a valid integer");
47+
}
48+
49+
return intValue;
50+
}
2251
}

0 commit comments

Comments
 (0)