diff --git a/configuration.json b/configuration.json
index 093433bd8..1189db046 100644
--- a/configuration.json
+++ b/configuration.json
@@ -5,7 +5,7 @@
"MigrationTools": {
"Version": "16.0",
"Endpoints": {
- "Source2": {
+ "Source": {
"EndpointType": "TfsTeamProjectEndpoint",
"Collection": "https://dev.azure.com/nkdagility-preview/",
"Project": "migrationSource1",
@@ -25,7 +25,7 @@
"IterationPath": "Iteration"
}
},
- "Target2": {
+ "Target": {
"EndpointType": "TfsTeamProjectEndpoint",
"Collection": "https://dev.azure.com/nkdagility-preview/",
"Project": "migrationTest5",
@@ -147,8 +147,8 @@
"PauseAfterEachWorkItem": false,
"AttachRevisionHistory": false,
"GenerateMigrationComment": true,
- "SourceName": "Source2",
- "TargetName": "Target2",
+ "SourceName": "Source",
+ "TargetName": "Target",
"WorkItemIDs": [ 12 ],
"MaxGracefulFailures": 0,
"SkipRevisionWithInvalidIterationPath": false,
diff --git a/docs/Reference/Generated/MigrationTools.xml b/docs/Reference/Generated/MigrationTools.xml
index 3d94762e5..5536d08e4 100644
--- a/docs/Reference/Generated/MigrationTools.xml
+++ b/docs/Reference/Generated/MigrationTools.xml
@@ -258,37 +258,37 @@
- => @"topic/better-validation-message"
+ => @"topic/validation-refactor"
- => @"cfd2ecbc"
+ => @"a9e012b0"
- => @"cfd2ecbc75ebef85edf99afce6ae914752457888"
+ => @"a9e012b05801421a57a109dc86b8827c02524855"
- => @"2024-09-14T07:08:51+01:00"
+ => @"2024-09-15T23:57:44+01:00"
- => @"2"
+ => @"6"
- => @"v16.0.3-Preview.2-2-gcfd2ecbc"
+ => @"v16.0.3-Preview.3-6-ga9e012b0"
- => @"v16.0.3-Preview.2"
+ => @"v16.0.3-Preview.3"
@@ -318,17 +318,17 @@
- => @"5"
+ => @"9"
- => @"Preview.2"
+ => @"Preview.3"
- => @"-Preview.2"
+ => @"-Preview.3"
diff --git a/src/MigrationTools.Clients.TfsObjectModel.Tests/Processors/TfsWorkItemMigrationProcessorTests.cs b/src/MigrationTools.Clients.TfsObjectModel.Tests/Processors/TfsWorkItemMigrationProcessorTests.cs
index a7279755b..47eddf5bd 100644
--- a/src/MigrationTools.Clients.TfsObjectModel.Tests/Processors/TfsWorkItemMigrationProcessorTests.cs
+++ b/src/MigrationTools.Clients.TfsObjectModel.Tests/Processors/TfsWorkItemMigrationProcessorTests.cs
@@ -3,6 +3,7 @@
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using Microsoft.Extensions.Options;
using Microsoft.VisualStudio.TestTools.UnitTesting;
namespace MigrationTools.Processors.Tests
@@ -25,7 +26,10 @@ public void OptionsValidator_Valid()
var validator = new TfsWorkItemMigrationProcessorOptionsValidator();
var x = new TfsWorkItemMigrationProcessorOptions();
x.WIQLQuery = "SELECT [System.Id] FROM WorkItems WHERE [System.TeamProject] = @TeamProject";
- Assert.IsTrue(validator.Validate(null, x).Succeeded);
+ x.SourceName = "source";
+ x.TargetName = "target";
+ ValidateOptionsResult result = validator.Validate(null, x);
+ Assert.IsTrue(result.Succeeded);
}
}
diff --git a/src/MigrationTools.Clients.TfsObjectModel.Tests/ServiceProviderHelper.cs b/src/MigrationTools.Clients.TfsObjectModel.Tests/ServiceProviderHelper.cs
index dd5c2e1a7..8cdd285ed 100644
--- a/src/MigrationTools.Clients.TfsObjectModel.Tests/ServiceProviderHelper.cs
+++ b/src/MigrationTools.Clients.TfsObjectModel.Tests/ServiceProviderHelper.cs
@@ -58,7 +58,7 @@ private static TfsTeamSettingsEndpointOptions GetTfsTeamEndPointOptions(string p
{
Collection = new Uri("https://dev.azure.com/nkdagility-preview/"),
Project = project,
- Authentication = new TfsAuthenticationOptions()
+ Authentication = new Endpoints.Infrastructure.TfsAuthenticationOptions()
{
AuthenticationMode = AuthenticationMode.AccessToken,
AccessToken = TestingConstants.AccessToken,
@@ -82,7 +82,7 @@ private static TfsEndpointOptions GetTfsEndPointOptions(string project)
{
Collection = new Uri("https://dev.azure.com/nkdagility-preview/"),
Project = project,
- Authentication = new TfsAuthenticationOptions()
+ Authentication = new Endpoints.Infrastructure.TfsAuthenticationOptions()
{
AuthenticationMode = AuthenticationMode.AccessToken,
AccessToken = TestingConstants.AccessToken,
diff --git a/src/MigrationTools.Clients.TfsObjectModel/EndPoints/Infrastructure/TfsTeamProjectAuthentication.cs b/src/MigrationTools.Clients.TfsObjectModel/EndPoints/Infrastructure/TfsTeamProjectAuthentication.cs
index a096c424d..a198afa8b 100644
--- a/src/MigrationTools.Clients.TfsObjectModel/EndPoints/Infrastructure/TfsTeamProjectAuthentication.cs
+++ b/src/MigrationTools.Clients.TfsObjectModel/EndPoints/Infrastructure/TfsTeamProjectAuthentication.cs
@@ -3,6 +3,7 @@
using System.Linq;
using System.Text;
using System.Threading.Tasks;
+using Microsoft.Extensions.Options;
using MigrationTools.Options;
using MigrationTools.Options.Infrastructure;
using Newtonsoft.Json;
@@ -11,7 +12,7 @@
namespace MigrationTools.Endpoints.Infrastructure
{
- public class TfsAuthenticationOptions
+ public class TfsAuthenticationOptions : IValidateOptions
{
[JsonConverter(typeof(StringEnumConverter))]
public AuthenticationMode AuthenticationMode { get; set; }
@@ -20,5 +21,44 @@ public class TfsAuthenticationOptions
[JsonConverter(typeof(DefaultOnlyConverter), "** removed as a secret ***")]
public string AccessToken { get; set; }
+
+ public ValidateOptionsResult Validate(string name, TfsAuthenticationOptions options)
+ {
+ var errors = new List();
+ // Validate Authentication properties based on AuthenticationMode
+ switch (options.AuthenticationMode)
+ {
+ case AuthenticationMode.AccessToken:
+ if (string.IsNullOrWhiteSpace(options.AccessToken))
+ {
+ errors.Add("The AccessToken must not be null or empty when AuthenticationMode is set to 'AccessToken'. You must provide a PAT to use 'AccessToken' as the authentication mode. You can set this through the config at 'MigrationTools:Endpoints:{name}:Authentication:AccessToken', or you can set an environment variable of 'MigrationTools__Endpoints__{name}__Authentication__AccessToken'. Check the docs on https://nkdagility.com/learn/azure-devops-migration-tools/Reference/Endpoints/TfsTeamProjectEndpoint/");
+ }
+ break;
+
+ case AuthenticationMode.Windows:
+ if (options.NetworkCredentials == null)
+ {
+ errors.Add("The NetworkCredentials must be provided when AuthenticationMode is set to 'Windows'.");
+ } else
+ {
+ ValidateOptionsResult result = options.NetworkCredentials.Validate(name, options.NetworkCredentials);
+ if (!result.Succeeded)
+ {
+ errors.AddRange(result.Failures);
+ }
+ }
+ break;
+ case AuthenticationMode.Prompt:
+ break;
+ default:
+ errors.Add($"The AuthenticationMode '{options.AuthenticationMode}' is not supported.");
+ break;
+ }
+ if (errors.Any())
+ {
+ return ValidateOptionsResult.Fail(errors);
+ }
+ return ValidateOptionsResult.Success;
+ }
}
}
diff --git a/src/MigrationTools.Clients.TfsObjectModel/Endpoints/TfsEndpointOptions.cs b/src/MigrationTools.Clients.TfsObjectModel/Endpoints/TfsEndpointOptions.cs
index 9fdf7620e..0f36e7dc0 100644
--- a/src/MigrationTools.Clients.TfsObjectModel/Endpoints/TfsEndpointOptions.cs
+++ b/src/MigrationTools.Clients.TfsObjectModel/Endpoints/TfsEndpointOptions.cs
@@ -1,6 +1,7 @@
using System;
using System.Collections.Generic;
using System.ComponentModel.DataAnnotations;
+using System.Linq;
using Microsoft.Extensions.Options;
using MigrationTools.Endpoints.Infrastructure;
using Newtonsoft.Json;
@@ -55,7 +56,7 @@ public ValidateOptionsResult Validate(string name, TfsEndpointOptions options)
// Validate ReflectedWorkItemIdField - Must not be null or empty
if (string.IsNullOrWhiteSpace(options.ReflectedWorkItemIdField))
{
- errors.Add("The ReflectedWorkItemIdField property must not be null or empty.");
+ errors.Add("The ReflectedWorkItemIdField property must not be null or empty. Check the docs on https://nkdagility.com/learn/azure-devops-migration-tools/setup/reflectedworkitemid/");
}
// Validate LanguageMaps - Must exist
@@ -63,6 +64,14 @@ public ValidateOptionsResult Validate(string name, TfsEndpointOptions options)
{
errors.Add("The LanguageMaps property must exist.");
}
+ else
+ {
+ ValidateOptionsResult lmr= options.LanguageMaps.Validate(name, options.LanguageMaps);
+ if (lmr != ValidateOptionsResult.Success)
+ {
+ errors.AddRange(lmr.Failures);
+ }
+ }
// Validate Authentication - Must exist
if (options.Authentication == null)
@@ -71,34 +80,17 @@ public ValidateOptionsResult Validate(string name, TfsEndpointOptions options)
}
else
{
- // Validate Authentication properties based on AuthenticationMode
- switch (options.Authentication.AuthenticationMode)
+ ValidateOptionsResult lmr = options.Authentication.Validate(name, options.Authentication);
+ if (lmr != ValidateOptionsResult.Success)
{
- case AuthenticationMode.AccessToken:
- if (string.IsNullOrWhiteSpace(options.Authentication.AccessToken))
- {
- errors.Add("The AccessToken must not be null or empty when AuthenticationMode is set to 'AccessToken'.");
- }
- break;
-
- case AuthenticationMode.Windows:
- if (options.Authentication.NetworkCredentials == null)
- {
- errors.Add("The NetworkCredentials must be provided when AuthenticationMode is set to 'Windows'.");
- }
- break;
- case AuthenticationMode.Prompt:
- break;
- default:
- errors.Add($"The AuthenticationMode '{options.Authentication.AuthenticationMode}' is not supported.");
- break;
+ errors.AddRange(lmr.Failures);
}
}
// Return failure if there are errors, otherwise success
- if (errors.Count > 0)
+ if (errors.Any())
{
- return ValidateOptionsResult.Fail(string.Join(Environment.NewLine, errors));
+ return ValidateOptionsResult.Fail(errors);
}
return ValidateOptionsResult.Success;
diff --git a/src/MigrationTools.Clients.TfsObjectModel/Endpoints/TfsLanguageMapOptions.cs b/src/MigrationTools.Clients.TfsObjectModel/Endpoints/TfsLanguageMapOptions.cs
index da668c4d2..f30123453 100644
--- a/src/MigrationTools.Clients.TfsObjectModel/Endpoints/TfsLanguageMapOptions.cs
+++ b/src/MigrationTools.Clients.TfsObjectModel/Endpoints/TfsLanguageMapOptions.cs
@@ -1,8 +1,31 @@
-namespace MigrationTools.Endpoints
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Extensions.Options;
+
+namespace MigrationTools.Endpoints
{
- public class TfsLanguageMapOptions
+ public class TfsLanguageMapOptions : IValidateOptions
{
public string AreaPath { get; set; }
public string IterationPath { get; set; }
+
+ public ValidateOptionsResult Validate(string name, TfsLanguageMapOptions options)
+ {
+ var errors = new List();
+
+ if (string.IsNullOrWhiteSpace(options.AreaPath))
+ {
+ errors.Add("The AreaPath property must not be null or empty.");
+ }
+ if (string.IsNullOrWhiteSpace(options.IterationPath))
+ {
+ errors.Add("The IterationPath property must not be null or empty.");
+ }
+ if (errors.Any())
+ {
+ ValidateOptionsResult.Fail(errors);
+ }
+ return ValidateOptionsResult.Success;
+ }
}
}
\ No newline at end of file
diff --git a/src/MigrationTools.Clients.TfsObjectModel/Processors/Infra/TfsProcessor.cs b/src/MigrationTools.Clients.TfsObjectModel/Processors/Infra/TfsProcessor.cs
index 08ec52200..ef2cbd46b 100644
--- a/src/MigrationTools.Clients.TfsObjectModel/Processors/Infra/TfsProcessor.cs
+++ b/src/MigrationTools.Clients.TfsObjectModel/Processors/Infra/TfsProcessor.cs
@@ -8,6 +8,7 @@
using MigrationTools;
using MigrationTools.Clients;
using MigrationTools.Enrichers;
+using MigrationTools.Exceptions;
using MigrationTools.Tools;
namespace MigrationTools.Processors.Infrastructure
@@ -19,9 +20,31 @@ protected TfsProcessor(IOptions options, TfsCommonTools tfsCom
}
- new public TfsTeamProjectEndpoint Source => (TfsTeamProjectEndpoint)base.Source;
+ new public TfsTeamProjectEndpoint Source
+ {
+ get
+ {
+ var endpoint = base.Source as TfsTeamProjectEndpoint;
+ if (endpoint == null)
+ {
+ throw new ConfigurationValidationException(Options, ValidateOptionsResult.Fail($"The Endpoint '{Options.SourceName}' specified for `{this.GetType().Name}` is of the wrong type! {nameof(TfsTeamProjectEndpoint)} was expected."));
+ }
+ return endpoint;
+ }
+ }
- new public TfsTeamProjectEndpoint Target => (TfsTeamProjectEndpoint)base.Target;
+ new public TfsTeamProjectEndpoint Target
+ {
+ get
+ {
+ var endpoint = base.Target as TfsTeamProjectEndpoint;
+ if (endpoint == null)
+ {
+ throw new ConfigurationValidationException(Options, ValidateOptionsResult.Fail($"The Endpoint '{Options.TargetName}' specified for `{this.GetType().Name}` is of the wrong type! {nameof(TfsTeamProjectEndpoint)} was expected."));
+ }
+ return endpoint;
+ }
+ }
new public TfsCommonTools CommonTools => (TfsCommonTools)base.CommonTools;
diff --git a/src/MigrationTools.Clients.TfsObjectModel/Processors/TfsWorkItemMigrationProcessorOptions.cs b/src/MigrationTools.Clients.TfsObjectModel/Processors/TfsWorkItemMigrationProcessorOptions.cs
index 1e98fb49b..970a97665 100644
--- a/src/MigrationTools.Clients.TfsObjectModel/Processors/TfsWorkItemMigrationProcessorOptions.cs
+++ b/src/MigrationTools.Clients.TfsObjectModel/Processors/TfsWorkItemMigrationProcessorOptions.cs
@@ -6,6 +6,7 @@
using System.ComponentModel.DataAnnotations;
using Microsoft.Extensions.Options;
using System.Text.RegularExpressions;
+using System.DirectoryServices.AccountManagement;
namespace MigrationTools.Processors
{
@@ -101,58 +102,8 @@ public class TfsWorkItemMigrationProcessorOptions : ProcessorOptions, IWorkItemP
///
public bool SkipRevisionWithInvalidAreaPath { get; set; }
+
}
- public class TfsWorkItemMigrationProcessorOptionsValidator : IValidateOptions
- {
-
- public TfsWorkItemMigrationProcessorOptionsValidator()
- {
-
- }
-
- public ValidateOptionsResult Validate(string name, TfsWorkItemMigrationProcessorOptions options)
- {
- ValidateOptionsResult result = new ValidateOptionsResult();
- // Check if WIQLQuery is provided
- if (string.IsNullOrWhiteSpace(options.WIQLQuery))
- {
- return ValidateOptionsResult.Fail("The WIQLQuery must be provided.");
- }
-
- // Validate the presence of required elements in the WIQL query
- if (!ContainsTeamProjectCondition(options.WIQLQuery))
- {
- return ValidateOptionsResult.Fail("The WIQLQuery must contain the condition '[System.TeamProject] = @TeamProject'.");
- }
-
- if (!UsesWorkItemsTable(options.WIQLQuery))
- {
- return ValidateOptionsResult.Fail("The WIQLQuery must use 'WorkItems' after the 'FROM' clause and not 'WorkItemLinks'.");
- }
-
- return ValidateOptionsResult.Success;
- }
-
- // Check if the WIQL query contains the '[System.TeamProject] = @TeamProject' condition
- private bool ContainsTeamProjectCondition(string query)
- {
- // Regex to match '[System.TeamProject] = @TeamProject'
- string teamProjectPattern = @"\[System\.TeamProject\]\s*=\s*@TeamProject";
-
- return Regex.IsMatch(query, teamProjectPattern, RegexOptions.IgnoreCase);
- }
-
- // Check if the WIQL query is using 'WorkItems' and not 'WorkItemLinks'
- private bool UsesWorkItemsTable(string query)
- {
- // Regex to ensure that 'FROM WorkItems' exists and 'WorkItemLinks' does not follow the FROM clause
- string fromWorkItemsPattern = @"FROM\s+WorkItems\b";
- string fromWorkItemLinksPattern = @"FROM\s+WorkItemLinks\b";
-
- // Return true if "WorkItems" is used and "WorkItemLinks" is not
- return Regex.IsMatch(query, fromWorkItemsPattern, RegexOptions.IgnoreCase) &&
- !Regex.IsMatch(query, fromWorkItemLinksPattern, RegexOptions.IgnoreCase);
- }
- }
+
}
\ No newline at end of file
diff --git a/src/MigrationTools.Clients.TfsObjectModel/Processors/TfsWorkItemMigrationProcessorOptionsValidator.cs b/src/MigrationTools.Clients.TfsObjectModel/Processors/TfsWorkItemMigrationProcessorOptionsValidator.cs
new file mode 100644
index 000000000..430f58247
--- /dev/null
+++ b/src/MigrationTools.Clients.TfsObjectModel/Processors/TfsWorkItemMigrationProcessorOptionsValidator.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using Microsoft.Extensions.Options;
+
+namespace MigrationTools.Processors
+{
+ public class TfsWorkItemMigrationProcessorOptionsValidator : IValidateOptions
+ {
+ public ValidateOptionsResult Validate(string name, TfsWorkItemMigrationProcessorOptions options)
+ {
+ var errors = new List();
+ ValidateOptionsResult baseResult = options.Validate(name, options);
+ if (baseResult != ValidateOptionsResult.Success)
+ {
+ errors.AddRange(baseResult.Failures);
+ }
+ // Check if WIQLQuery is provided
+ if (string.IsNullOrWhiteSpace(options.WIQLQuery))
+ {
+ errors.Add($"The WIQLQuery on {name} must be provided.");
+ }
+ else
+ {
+ // Validate the presence of required elements in the WIQL query
+ if (!ContainsTeamProjectCondition(options.WIQLQuery))
+ {
+ errors.Add("The WIQLQuery on {name} must contain the condition '[System.TeamProject] = @TeamProject'.");
+ }
+
+ if (!UsesWorkItemsTable(options.WIQLQuery))
+ {
+ errors.Add("The WIQLQuery on {name} must use 'WorkItems' after the 'FROM' clause and not 'WorkItemLinks'.");
+ }
+ }
+ if (errors.Count > 0)
+ {
+ return ValidateOptionsResult.Fail(errors);
+ }
+
+ return ValidateOptionsResult.Success;
+ }
+
+ private bool ContainsTeamProjectCondition(string query)
+ {
+ // Regex to match '[System.TeamProject] = @TeamProject'
+ string teamProjectPattern = @"\[System\.TeamProject\]\s*=\s*@TeamProject";
+
+ return Regex.IsMatch(query, teamProjectPattern, RegexOptions.IgnoreCase);
+ }
+
+ // Check if the WIQL query is using 'WorkItems' and not 'WorkItemLinks'
+ private bool UsesWorkItemsTable(string query)
+ {
+ // Regex to ensure that 'FROM WorkItems' exists and 'WorkItemLinks' does not follow the FROM clause
+ string fromWorkItemsPattern = @"FROM\s+WorkItems\b";
+ string fromWorkItemLinksPattern = @"FROM\s+WorkItemLinks\b";
+
+ // Return true if "WorkItems" is used and "WorkItemLinks" is not
+ return Regex.IsMatch(query, fromWorkItemsPattern, RegexOptions.IgnoreCase) &&
+ !Regex.IsMatch(query, fromWorkItemLinksPattern, RegexOptions.IgnoreCase);
+ }
+ }
+}
diff --git a/src/MigrationTools.Clients.TfsObjectModel/ServiceCollectionExtensions.cs b/src/MigrationTools.Clients.TfsObjectModel/ServiceCollectionExtensions.cs
index e74189559..dd766e9e5 100644
--- a/src/MigrationTools.Clients.TfsObjectModel/ServiceCollectionExtensions.cs
+++ b/src/MigrationTools.Clients.TfsObjectModel/ServiceCollectionExtensions.cs
@@ -35,7 +35,7 @@ public static void AddMigrationToolServicesForClientTfs_Tools(this IServiceColle
public static void AddMigrationToolServicesForClientTfs_Processors(this IServiceCollection context)
{
context.AddSingleton();
- context.AddSingleton, TfsWorkItemMigrationProcessorOptionsValidator>();
+ context.AddTransient, TfsWorkItemMigrationProcessorOptionsValidator>();
context.AddSingleton();
context.AddSingleton();
diff --git a/src/MigrationTools/Endpoints/Infrastructure/EndpointRegistrationExtensions.cs b/src/MigrationTools/Endpoints/Infrastructure/EndpointRegistrationExtensions.cs
index e13caee4d..488d01c16 100644
--- a/src/MigrationTools/Endpoints/Infrastructure/EndpointRegistrationExtensions.cs
+++ b/src/MigrationTools/Endpoints/Infrastructure/EndpointRegistrationExtensions.cs
@@ -54,7 +54,7 @@ private static void AddEndPointSingleton(IServiceCollection services, IConfigura
if (validatorType != null)
{
var validator = Activator.CreateInstance(validatorType);
- var validationResult = InvokeValidator(validator, endpointOptionsInstance);
+ var validationResult = InvokeValidator(validator, endpointOptionsInstance, endpointName);
if (validationResult.Failed)
{
//throw new OptionsValidationException(endpointName, endpointOptionsType, validationResult.Failures);
@@ -120,10 +120,10 @@ private static Type GetValidatorTypeForOptions(Type optionsType)
}
- private static ValidateOptionsResult InvokeValidator(object validator, IEndpointOptions optionsInstance)
+ private static ValidateOptionsResult InvokeValidator(object validator, IEndpointOptions optionsInstance, string name)
{
var validateMethod = validator.GetType().GetMethod("Validate");
- var validationResult = validateMethod?.Invoke(validator, new object[] { null, optionsInstance });
+ var validationResult = validateMethod?.Invoke(validator, new object[] { name, optionsInstance });
return (ValidateOptionsResult)validationResult;
diff --git a/src/MigrationTools/Exceptions/ConfigurationValidationException.cs b/src/MigrationTools/Exceptions/ConfigurationValidationException.cs
index 061b11a1f..f4f579881 100644
--- a/src/MigrationTools/Exceptions/ConfigurationValidationException.cs
+++ b/src/MigrationTools/Exceptions/ConfigurationValidationException.cs
@@ -10,20 +10,27 @@ namespace MigrationTools.Exceptions
{
public class ConfigurationValidationException : Exception
{
- public IConfigurationSection ConfigrationSection { get; }
+ public string ConfigrationSectionPath { get; }
public IOptions OptionsInstance { get; }
public ValidateOptionsResult ValidationResult { get; }
public ConfigurationValidationException(IConfigurationSection configrationSection, IOptions optionsInstance, ValidateOptionsResult validationResult)
{
- ConfigrationSection = configrationSection;
+ ConfigrationSectionPath = configrationSection.Path;
+ OptionsInstance = optionsInstance;
+ ValidationResult = validationResult;
+ }
+
+ public ConfigurationValidationException(IOptions optionsInstance, ValidateOptionsResult validationResult)
+ {
+ ConfigrationSectionPath = optionsInstance.ConfigurationMetadata.PathToInstance;
OptionsInstance = optionsInstance;
ValidationResult = validationResult;
}
public override string ToString()
{
- return $"The configuration entry at '{ConfigrationSection.Path}' did not pass validation!\n Please check the following failures:\n -{string.Join("\n-", ValidationResult.Failures)}";
+ return $"The configuration entry at '{ConfigrationSectionPath}' did not pass validation!\n Please check the following failures:\n -{string.Join("\n-", ValidationResult.Failures)}";
}
}
diff --git a/src/MigrationTools/Options/NetworkCredentialsOptions.cs b/src/MigrationTools/Options/NetworkCredentialsOptions.cs
index 8bd265a4e..a8b3da8de 100644
--- a/src/MigrationTools/Options/NetworkCredentialsOptions.cs
+++ b/src/MigrationTools/Options/NetworkCredentialsOptions.cs
@@ -1,4 +1,7 @@
using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Extensions.Options;
using MigrationTools.Options.Infrastructure;
using Newtonsoft.Json;
@@ -11,12 +14,34 @@ public class NetworkCredentialsOptions
public NetworkCredentials Target { get; set; }
}
- public class NetworkCredentials
+ public class NetworkCredentials : IValidateOptions
{
public string Domain { get; set; }
public string UserName { get; set; }
[JsonConverter(typeof(DefaultOnlyConverter), "** removed as a secret ***")]
public string Password { get; set; }
+
+ public ValidateOptionsResult Validate(string name, NetworkCredentials options)
+ {
+ var errors = new List();
+ if (string.IsNullOrWhiteSpace(options.Domain))
+ {
+ errors.Add("The Domain must not be null or empty.");
+ }
+ if (string.IsNullOrWhiteSpace(options.UserName))
+ {
+ errors.Add("The UserName must not be null or empty.");
+ }
+ if (string.IsNullOrWhiteSpace(options.Password))
+ {
+ errors.Add("The Password must not be null or empty.");
+ }
+ if (errors.Any())
+ {
+ return ValidateOptionsResult.Fail(errors);
+ }
+ return ValidateOptionsResult.Success;
+ }
}
}
diff --git a/src/MigrationTools/Processors/Infrastructure/Processor.cs b/src/MigrationTools/Processors/Infrastructure/Processor.cs
index 7237aa459..14bafe1e6 100644
--- a/src/MigrationTools/Processors/Infrastructure/Processor.cs
+++ b/src/MigrationTools/Processors/Infrastructure/Processor.cs
@@ -65,10 +65,10 @@ public IEndpoint GetEndpoint(string name)
throw new ArgumentException("Endpoint name cannot be null or empty", nameof(name));
}
// Assuming GetRequiredKeyedService throws an exception if the service is not found
- IEndpoint endpoint = Services.GetRequiredKeyedService(name);
+ IEndpoint endpoint = Services.GetKeyedService(name);
if (endpoint == null)
{
- Log.LogCritical("Processor::GetEndpoint: The endpoint '{EndpointName}' could not be found.", name);
+ throw new ConfigurationValidationException( Options, ValidateOptionsResult.Fail($"The Endpoint '{name}' specified for `{this.GetType().Name}` was not found."));
}
return endpoint;
}
@@ -105,14 +105,12 @@ public void Execute()
{
Status = ProcessingStatus.Failed;
ProcessorActivity.SetStatus(ActivityStatusCode.Error);
- Telemetry.TrackException(ex, ProcessorActivity.Tags);
Log.LogCritical(ex, "Validation of your configuration failed:");
}
catch (ConfigurationValidationException ex)
{
Status = ProcessingStatus.Failed;
ProcessorActivity.SetStatus(ActivityStatusCode.Error);
- Telemetry.TrackException(ex, ProcessorActivity.Tags);
Log.LogCritical(ex, "Validation of your configuration failed:");
}
catch (Exception ex)
diff --git a/src/MigrationTools/Processors/Infrastructure/ProcessorOptions.cs b/src/MigrationTools/Processors/Infrastructure/ProcessorOptions.cs
index 8a8f9daa0..92ed9a478 100644
--- a/src/MigrationTools/Processors/Infrastructure/ProcessorOptions.cs
+++ b/src/MigrationTools/Processors/Infrastructure/ProcessorOptions.cs
@@ -9,7 +9,7 @@
namespace MigrationTools.Processors.Infrastructure
{
- public abstract class ProcessorOptions : IProcessorOptions
+ public abstract class ProcessorOptions : IProcessorOptions, IValidateOptions
{
[JsonIgnore]
public string OptionFor => $"{GetType().Name.Replace("Options", "")}";
@@ -54,5 +54,25 @@ public bool IsProcessorCompatible(IReadOnlyList otherProcessor
{
return true;
}
+
+ public ValidateOptionsResult Validate(string name, ProcessorOptions options)
+ {
+ var errors = new List();
+ if (string.IsNullOrWhiteSpace(options.SourceName))
+ {
+ errors.Add("The `SourceName` field on {name} must contain a value that matches one of the Endpoint names.");
+ }
+ if (string.IsNullOrWhiteSpace(options.TargetName))
+ {
+ errors.Add("The `TargetName` field on {name} must contain a value that matches one of the Endpoint names.");
+ }
+
+ if (errors.Count > 0)
+ {
+ return ValidateOptionsResult.Fail(errors);
+ }
+
+ return ValidateOptionsResult.Success;
+ }
}
}
\ No newline at end of file