diff --git a/DiscordTranslationBot.Tests/Configuration/DiscordOptionsValidatorTests.cs b/DiscordTranslationBot.Tests/Configuration/DiscordOptionsValidatorTests.cs
new file mode 100644
index 0000000..84dd12a
--- /dev/null
+++ b/DiscordTranslationBot.Tests/Configuration/DiscordOptionsValidatorTests.cs
@@ -0,0 +1,38 @@
+using DiscordTranslationBot.Configuration;
+using FluentValidation.TestHelper;
+
+namespace DiscordTranslationBot.Tests.Configuration;
+
+public sealed class DiscordOptionsValidatorTests
+{
+ private readonly DiscordOptionsValidator _sut = new();
+
+ [Fact]
+ public void Valid_Options_ValidatesWithoutErrors()
+ {
+ // Arrange
+ var options = new DiscordOptions { BotToken = "token" };
+
+ // Act
+ var result = _sut.TestValidate(options);
+
+ // Assert
+ result.ShouldNotHaveAnyValidationErrors();
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData(" ")]
+ public void Invalid_BotToken_HasValidationErrors(string? botToken)
+ {
+ // Arrange
+ var options = new DiscordOptions { BotToken = botToken! };
+
+ // Act
+ var result = _sut.TestValidate(options);
+
+ // Assert
+ result.ShouldHaveValidationErrorFor(x => x.BotToken).Only();
+ }
+}
diff --git a/DiscordTranslationBot.Tests/Configuration/TranslationProviders/TranslationProvidersOptionsValidatorTests.cs b/DiscordTranslationBot.Tests/Configuration/TranslationProviders/TranslationProvidersOptionsValidatorTests.cs
new file mode 100644
index 0000000..b82648c
--- /dev/null
+++ b/DiscordTranslationBot.Tests/Configuration/TranslationProviders/TranslationProvidersOptionsValidatorTests.cs
@@ -0,0 +1,61 @@
+using DiscordTranslationBot.Configuration.TranslationProviders;
+using FluentValidation.TestHelper;
+
+namespace DiscordTranslationBot.Tests.Configuration.TranslationProviders;
+
+public sealed class TranslationProvidersOptionsValidatorTests
+{
+ private readonly TranslationProvidersOptionsValidator _sut = new();
+
+ [Fact]
+ public void Valid_Options_ValidatesWithoutErrors()
+ {
+ // Arrange
+ var options = new TranslationProvidersOptions
+ {
+ AzureTranslator = new AzureTranslatorOptions
+ {
+ Enabled = true,
+ ApiUrl = new Uri("http://localhost", UriKind.Absolute),
+ Region = "westus",
+ SecretKey = "secret"
+ },
+ LibreTranslate = new LibreTranslateOptions { Enabled = false }
+ };
+
+ // Act
+ var result = _sut.TestValidate(options);
+
+ // Assert
+ result.ShouldNotHaveAnyValidationErrors();
+ }
+
+ [Theory]
+ [InlineData(null)]
+ [InlineData("")]
+ [InlineData(" ")]
+ public void Invalid_ProviderOptions_HasValidationErrors(string? stringValue)
+ {
+ // Arrange
+ var options = new TranslationProvidersOptions
+ {
+ AzureTranslator = new AzureTranslatorOptions
+ {
+ Enabled = true,
+ ApiUrl = null,
+ Region = stringValue,
+ SecretKey = stringValue
+ },
+ LibreTranslate = new LibreTranslateOptions { Enabled = true, ApiUrl = null }
+ };
+
+ // Act
+ var result = _sut.TestValidate(options);
+
+ // Assert
+ result.ShouldHaveValidationErrorFor(x => x.AzureTranslator.ApiUrl);
+ result.ShouldHaveValidationErrorFor(x => x.AzureTranslator.Region);
+ result.ShouldHaveValidationErrorFor(x => x.AzureTranslator.SecretKey);
+ result.ShouldHaveValidationErrorFor(x => x.LibreTranslate.ApiUrl);
+ }
+}
diff --git a/DiscordTranslationBot/Configuration/TranslationProviders/AzureTranslatorOptions.cs b/DiscordTranslationBot/Configuration/TranslationProviders/AzureTranslatorOptions.cs
index 4fdb925..4098005 100644
--- a/DiscordTranslationBot/Configuration/TranslationProviders/AzureTranslatorOptions.cs
+++ b/DiscordTranslationBot/Configuration/TranslationProviders/AzureTranslatorOptions.cs
@@ -1,15 +1,12 @@
-namespace DiscordTranslationBot.Configuration.TranslationProviders;
+using FluentValidation;
+
+namespace DiscordTranslationBot.Configuration.TranslationProviders;
///
/// Options for the Azure Translator provider.
///
public sealed class AzureTranslatorOptions : TranslationProviderOptionsBase
{
- ///
- /// The API URL for Azure Translator.
- ///
- public Uri? ApiUrl { get; init; }
-
///
/// The secret key for the Azure Translator API.
///
@@ -20,3 +17,26 @@ public sealed class AzureTranslatorOptions : TranslationProviderOptionsBase
///
public string? Region { get; init; }
}
+
+///
+/// Validator for .
+///
+public sealed class AzureTranslatorOptionsValidator : AbstractValidator
+{
+ ///
+ /// Initializes validation rules.
+ ///
+ public AzureTranslatorOptionsValidator()
+ {
+ Include(new TranslationProviderOptionsBaseValidator());
+
+ When(
+ x => x.Enabled,
+ () =>
+ {
+ RuleFor(x => x.SecretKey).NotEmpty();
+ RuleFor(x => x.Region).NotEmpty();
+ }
+ );
+ }
+}
diff --git a/DiscordTranslationBot/Configuration/TranslationProviders/LibreTranslateOptions.cs b/DiscordTranslationBot/Configuration/TranslationProviders/LibreTranslateOptions.cs
index 7df0b5f..6d05650 100644
--- a/DiscordTranslationBot/Configuration/TranslationProviders/LibreTranslateOptions.cs
+++ b/DiscordTranslationBot/Configuration/TranslationProviders/LibreTranslateOptions.cs
@@ -1,12 +1,22 @@
-namespace DiscordTranslationBot.Configuration.TranslationProviders;
+using FluentValidation;
+
+namespace DiscordTranslationBot.Configuration.TranslationProviders;
///
/// Options for the LibreTranslate provider.
///
-public sealed class LibreTranslateOptions : TranslationProviderOptionsBase
+public sealed class LibreTranslateOptions : TranslationProviderOptionsBase { }
+
+///
+/// Validator for .
+///
+public sealed class LibreTranslateOptionsValidator : AbstractValidator
{
///
- /// The API URL for LibreTranslate.
+ /// Initializes validation rules.
///
- public Uri? ApiUrl { get; init; }
+ public LibreTranslateOptionsValidator()
+ {
+ Include(new TranslationProviderOptionsBaseValidator());
+ }
}
diff --git a/DiscordTranslationBot/Configuration/TranslationProviders/TranslationProviderOptionsBase.cs b/DiscordTranslationBot/Configuration/TranslationProviders/TranslationProviderOptionsBase.cs
index 1569dfc..6596ec0 100644
--- a/DiscordTranslationBot/Configuration/TranslationProviders/TranslationProviderOptionsBase.cs
+++ b/DiscordTranslationBot/Configuration/TranslationProviders/TranslationProviderOptionsBase.cs
@@ -1,4 +1,6 @@
-namespace DiscordTranslationBot.Configuration.TranslationProviders;
+using FluentValidation;
+
+namespace DiscordTranslationBot.Configuration.TranslationProviders;
///
/// Base class for a translation provider's options.
@@ -9,4 +11,23 @@ public abstract class TranslationProviderOptionsBase
/// Flag indicating whether this provider is enabled.
///
public bool Enabled { get; init; }
+
+ ///
+ /// The API URL for Azure Translator.
+ ///
+ public Uri? ApiUrl { get; init; }
+}
+
+///
+/// Validator for .
+///
+public sealed class TranslationProviderOptionsBaseValidator : AbstractValidator
+{
+ ///
+ /// Initializes validation rules.
+ ///
+ public TranslationProviderOptionsBaseValidator()
+ {
+ When(x => x.Enabled, () => RuleFor(x => x.ApiUrl).NotNull());
+ }
}
diff --git a/DiscordTranslationBot/Configuration/TranslationProviders/TranslationProvidersOptions.cs b/DiscordTranslationBot/Configuration/TranslationProviders/TranslationProvidersOptions.cs
index 386c36f..5384a16 100644
--- a/DiscordTranslationBot/Configuration/TranslationProviders/TranslationProvidersOptions.cs
+++ b/DiscordTranslationBot/Configuration/TranslationProviders/TranslationProvidersOptions.cs
@@ -29,22 +29,11 @@ public sealed class TranslationProvidersOptions
public sealed class TranslationProvidersOptionsValidator : AbstractValidator
{
///
- /// Initializes a new instance of the class.
+ /// Initializes validation rules.
///
public TranslationProvidersOptionsValidator()
{
- // Validate Azure Translator options.
- When(
- x => x.AzureTranslator.Enabled,
- () =>
- {
- RuleFor(x => x.AzureTranslator.ApiUrl).NotNull();
- RuleFor(x => x.AzureTranslator.SecretKey).NotEmpty();
- RuleFor(x => x.AzureTranslator.Region).NotEmpty();
- }
- );
-
- // Validate Libre Translate options.
- When(x => x.LibreTranslate.Enabled, () => RuleFor(x => x.LibreTranslate.ApiUrl).NotNull());
+ RuleFor(x => x.AzureTranslator).SetValidator(new AzureTranslatorOptionsValidator());
+ RuleFor(x => x.LibreTranslate).SetValidator(new LibreTranslateOptionsValidator());
}
}
diff --git a/DiscordTranslationBot/Extensions/ServiceCollectionExtensions.cs b/DiscordTranslationBot/Extensions/ServiceCollectionExtensions.cs
index d97a21f..c39872d 100644
--- a/DiscordTranslationBot/Extensions/ServiceCollectionExtensions.cs
+++ b/DiscordTranslationBot/Extensions/ServiceCollectionExtensions.cs
@@ -26,7 +26,6 @@ IConfigurationSection configurationSection
where TValidator : class, IValidator
{
services.AddTransient, TValidator>();
-
services.AddOptions().Bind(configurationSection).ValidateWithFluentValidation().ValidateOnStart();
}