diff --git a/src/Common/Common.csproj b/src/Common/Common.csproj
index 4fdad984..62882e30 100644
--- a/src/Common/Common.csproj
+++ b/src/Common/Common.csproj
@@ -1,5 +1,6 @@
+
diff --git a/src/Common/Extensions/FluentValidationExtensions.cs b/src/Common/Extensions/FluentValidationExtensions.cs
new file mode 100644
index 00000000..75af48a6
--- /dev/null
+++ b/src/Common/Extensions/FluentValidationExtensions.cs
@@ -0,0 +1,43 @@
+using System;
+using System.Threading;
+using System.Threading.Tasks;
+using FluentValidation;
+using FluentValidation.Validators;
+
+namespace Common.Extensions
+{
+ public static class FluentValidationExtensions
+ {
+ // From: https://github.com/FluentValidation/FluentValidation/issues/1648
+ public static IRuleBuilderOptions SetNonNullableValidator(
+ this IRuleBuilder ruleBuilder, IValidator validator, params string[] ruleSets)
+ {
+ var adapter = new NullableChildValidatorAdaptor(validator, validator.GetType())
+ {
+ RuleSets = ruleSets
+ };
+
+ return ruleBuilder.SetAsyncValidator(adapter);
+ }
+
+ private class NullableChildValidatorAdaptor : ChildValidatorAdaptor,
+ IPropertyValidator, IAsyncPropertyValidator
+ {
+ public NullableChildValidatorAdaptor(IValidator validator, Type validatorType)
+ : base(validator, validatorType)
+ {
+ }
+
+ public override Task IsValidAsync(ValidationContext context, TProperty? value,
+ CancellationToken cancellation)
+ {
+ return base.IsValidAsync(context, value!, cancellation);
+ }
+
+ public override bool IsValid(ValidationContext context, TProperty? value)
+ {
+ return base.IsValid(context, value!);
+ }
+ }
+ }
+}
diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets
index be04764d..d3e7bec3 100644
--- a/src/Directory.Build.targets
+++ b/src/Directory.Build.targets
@@ -2,34 +2,35 @@
-
+
-
-
+
+
+
-
-
+
+
-
+
-
+
diff --git a/src/Trash.TestLibrary/Trash.TestLibrary.csproj b/src/Trash.TestLibrary/Trash.TestLibrary.csproj
deleted file mode 100644
index 18753431..00000000
--- a/src/Trash.TestLibrary/Trash.TestLibrary.csproj
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
diff --git a/src/Trash.Tests/CreateConfig/CreateConfigCommandTest.cs b/src/Trash.Tests/Command/CreateConfigCommandTest.cs
similarity index 97%
rename from src/Trash.Tests/CreateConfig/CreateConfigCommandTest.cs
rename to src/Trash.Tests/Command/CreateConfigCommandTest.cs
index ee4124df..01e65823 100644
--- a/src/Trash.Tests/CreateConfig/CreateConfigCommandTest.cs
+++ b/src/Trash.Tests/Command/CreateConfigCommandTest.cs
@@ -8,7 +8,7 @@ using Trash.Command;
// ReSharper disable MethodHasAsyncOverload
-namespace Trash.Tests.CreateConfig
+namespace Trash.Tests.Command
{
[TestFixture]
[Parallelizable(ParallelScope.All)]
diff --git a/src/Trash.Tests/Command/CliTypeActivatorTest.cs b/src/Trash.Tests/Command/Helpers/CliTypeActivatorTest.cs
similarity index 97%
rename from src/Trash.Tests/Command/CliTypeActivatorTest.cs
rename to src/Trash.Tests/Command/Helpers/CliTypeActivatorTest.cs
index 94ad046f..ebfaddbe 100644
--- a/src/Trash.Tests/Command/CliTypeActivatorTest.cs
+++ b/src/Trash.Tests/Command/Helpers/CliTypeActivatorTest.cs
@@ -5,7 +5,7 @@ using FluentAssertions;
using NUnit.Framework;
using Trash.Command.Helpers;
-namespace Trash.Tests.Command
+namespace Trash.Tests.Command.Helpers
{
[TestFixture]
[Parallelizable(ParallelScope.All)]
diff --git a/src/Trash.Tests/Config/ConfigurationLoaderTest.cs b/src/Trash.Tests/Config/ConfigurationLoaderTest.cs
index 0923ff5a..035aeb01 100644
--- a/src/Trash.Tests/Config/ConfigurationLoaderTest.cs
+++ b/src/Trash.Tests/Config/ConfigurationLoaderTest.cs
@@ -8,13 +8,14 @@ using System.Text;
using Common;
using Common.Extensions;
using FluentAssertions;
-using JetBrains.Annotations;
+using FluentValidation;
+using FluentValidation.Results;
using NSubstitute;
using NUnit.Framework;
using TestLibrary;
using Trash.Config;
using TrashLib.Config;
-using TrashLib.Sonarr;
+using TrashLib.Sonarr.Config;
using TrashLib.Sonarr.ReleaseProfile;
using YamlDotNet.Serialization.ObjectFactories;
@@ -32,43 +33,10 @@ namespace Trash.Tests.Config
[SuppressMessage("Microsoft.Design", "CA1034",
Justification = "YamlDotNet requires this type to be public so it may access it")]
- [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
- public class TestConfigValidFalse : IServiceConfiguration
+ public class TestConfig : IServiceConfiguration
{
- public const string Msg = "validate_false";
- public string BaseUrl { get; init; } = "";
- public string ApiKey { get; init; } = "";
-
- public bool IsValid(out string msg)
- {
- msg = Msg;
- return false;
- }
-
- public string BuildUrl()
- {
- throw new NotImplementedException();
- }
- }
-
- [SuppressMessage("Microsoft.Design", "CA1034",
- Justification = "YamlDotNet requires this type to be public so it may access it")]
- [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
- public class TestConfigValidTrue : IServiceConfiguration
- {
- public string BaseUrl { get; init; } = "";
- public string ApiKey { get; init; } = "";
-
- public bool IsValid(out string msg)
- {
- msg = "";
- return true;
- }
-
- public string BuildUrl()
- {
- throw new NotImplementedException();
- }
+ public string BaseUrl => "";
+ public string ApiKey => "";
}
[Test]
@@ -94,7 +62,9 @@ namespace Trash.Tests.Config
var actualActiveConfigs = new List();
provider.ActiveConfiguration = Arg.Do(a => actualActiveConfigs.Add(a));
- var loader = new ConfigurationLoader(provider, fs, new DefaultObjectFactory());
+ var validator = Substitute.For>();
+ var loader =
+ new ConfigurationLoader(provider, fs, new DefaultObjectFactory(), validator);
var fakeFiles = new List
{
@@ -118,10 +88,12 @@ namespace Trash.Tests.Config
[Test]
public void Parse_using_stream()
{
+ var validator = Substitute.For>();
var configLoader = new ConfigurationLoader(
Substitute.For(),
Substitute.For(),
- new DefaultObjectFactory());
+ new DefaultObjectFactory(),
+ validator);
var configs = configLoader.LoadFromStream(GetResourceData("Load_UsingStream_CorrectParsing.yml"), "sonarr");
@@ -156,12 +128,20 @@ namespace Trash.Tests.Config
}
[Test]
- public void Validation_failure_throws()
+ public void Throw_when_validation_fails()
{
- var configLoader = new ConfigurationLoader(
+ var validator = Substitute.For>();
+ var configLoader = new ConfigurationLoader(
Substitute.For(),
Substitute.For(),
- new DefaultObjectFactory());
+ new DefaultObjectFactory(),
+ validator);
+
+ // force the validator to return a validation error
+ validator.Validate(Arg.Any()).Returns(new ValidationResult
+ {
+ Errors = {new ValidationFailure("PropertyName", "Test Validation Failure")}
+ });
var testYml = @"
fubar:
@@ -169,17 +149,18 @@ fubar:
";
Action act = () => configLoader.LoadFromStream(new StringReader(testYml), "fubar");
- act.Should().Throw()
- .WithMessage($"*{TestConfigValidFalse.Msg}");
+ act.Should().Throw();
}
[Test]
public void Validation_success_does_not_throw()
{
- var configLoader = new ConfigurationLoader(
+ var validator = Substitute.For>();
+ var configLoader = new ConfigurationLoader(
Substitute.For(),
Substitute.For(),
- new DefaultObjectFactory());
+ new DefaultObjectFactory(),
+ validator);
var testYml = @"
fubar:
diff --git a/src/Trash.Tests/Config/ServiceConfigurationTest.cs b/src/Trash.Tests/Config/ServiceConfigurationTest.cs
deleted file mode 100644
index 9a2b1107..00000000
--- a/src/Trash.Tests/Config/ServiceConfigurationTest.cs
+++ /dev/null
@@ -1,67 +0,0 @@
-using System;
-using System.IO;
-using System.IO.Abstractions;
-using FluentAssertions;
-using JetBrains.Annotations;
-using NSubstitute;
-using NUnit.Framework;
-using Trash.Config;
-using TrashLib.Config;
-using YamlDotNet.Core;
-using YamlDotNet.Serialization.ObjectFactories;
-
-namespace Trash.Tests.Config
-{
- [TestFixture]
- [Parallelizable(ParallelScope.All)]
- public class ServiceConfigurationTest
- {
- // This test class must be public otherwise it cannot be deserialized by YamlDotNet
- [UsedImplicitly]
- private class TestServiceConfiguration : ServiceConfiguration
- {
- public const string ServiceName = "test_service";
-
- public override bool IsValid(out string msg)
- {
- throw new NotImplementedException();
- }
- }
-
- [Test]
- public void Deserialize_ApiKeyMissing_Throw()
- {
- const string yaml = @"
-test_service:
-- base_url: a
-";
- var loader = new ConfigurationLoader(
- Substitute.For(),
- Substitute.For(),
- new DefaultObjectFactory());
-
- Action act = () => loader.LoadFromStream(new StringReader(yaml), TestServiceConfiguration.ServiceName);
-
- act.Should().Throw()
- .WithMessage("*Property 'api_key' is required");
- }
-
- [Test]
- public void Deserialize_BaseUrlMissing_Throw()
- {
- const string yaml = @"
-test_service:
-- api_key: b
-";
- var loader = new ConfigurationLoader(
- Substitute.For(),
- Substitute.For(),
- new DefaultObjectFactory());
-
- Action act = () => loader.LoadFromStream(new StringReader(yaml), TestServiceConfiguration.ServiceName);
-
- act.Should().Throw()
- .WithMessage("*Property 'base_url' is required");
- }
- }
-}
diff --git a/src/Trash.Tests/Trash.Tests.csproj b/src/Trash.Tests/Trash.Tests.csproj
index 9f984b62..1492c910 100644
--- a/src/Trash.Tests/Trash.Tests.csproj
+++ b/src/Trash.Tests/Trash.Tests.csproj
@@ -5,7 +5,10 @@
-
+
+
+
+
diff --git a/src/Trash/Command/RadarrCommand.cs b/src/Trash/Command/RadarrCommand.cs
index d3c2e70a..3664c095 100644
--- a/src/Trash/Command/RadarrCommand.cs
+++ b/src/Trash/Command/RadarrCommand.cs
@@ -8,7 +8,7 @@ using Serilog;
using Serilog.Core;
using Trash.Command.Helpers;
using Trash.Config;
-using TrashLib.Radarr;
+using TrashLib.Radarr.Config;
using TrashLib.Radarr.CustomFormat;
using TrashLib.Radarr.QualityDefinition;
diff --git a/src/Trash/Command/SonarrCommand.cs b/src/Trash/Command/SonarrCommand.cs
index bf05f1f8..496a7d60 100644
--- a/src/Trash/Command/SonarrCommand.cs
+++ b/src/Trash/Command/SonarrCommand.cs
@@ -8,7 +8,7 @@ using Serilog;
using Serilog.Core;
using Trash.Command.Helpers;
using Trash.Config;
-using TrashLib.Sonarr;
+using TrashLib.Sonarr.Config;
using TrashLib.Sonarr.QualityDefinition;
using TrashLib.Sonarr.ReleaseProfile;
diff --git a/src/Trash/CompositionRoot.cs b/src/Trash/CompositionRoot.cs
index e5c315ac..41e678a7 100644
--- a/src/Trash/CompositionRoot.cs
+++ b/src/Trash/CompositionRoot.cs
@@ -3,6 +3,7 @@ using System.IO;
using System.IO.Abstractions;
using System.Reflection;
using Autofac;
+using Autofac.Core.Activators.Reflection;
using CliFx;
using Serilog;
using Serilog.Core;
@@ -47,6 +48,7 @@ namespace Trash
.As();
builder.RegisterGeneric(typeof(ConfigurationLoader<>))
+ .WithProperty(new AutowiringParameter())
.As(typeof(IConfigurationLoader<>));
// note: Do not allow consumers to resolve IServiceConfiguration directly; if this gets cached
diff --git a/src/Trash/Config/ConfigurationException.cs b/src/Trash/Config/ConfigurationException.cs
index 542e6041..aee050eb 100644
--- a/src/Trash/Config/ConfigurationException.cs
+++ b/src/Trash/Config/ConfigurationException.cs
@@ -1,18 +1,49 @@
using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using FluentValidation.Results;
namespace Trash.Config
{
public class ConfigurationException : Exception
{
- public ConfigurationException(string propertyName, Type deserializableType, string msg)
- : base($"An exception occurred while deserializing type '{deserializableType}' " +
- $"for YML property '{propertyName}': {msg}")
+ private ConfigurationException(string propertyName, Type deserializableType, IEnumerable messages)
{
PropertyName = propertyName;
DeserializableType = deserializableType;
+ ErrorMessages = messages.ToList();
}
+ public ConfigurationException(string propertyName, Type deserializableType, string message)
+ : this(propertyName, deserializableType, new[] {message})
+ {
+ }
+
+ public ConfigurationException(string propertyName, Type deserializableType,
+ IEnumerable validationFailures)
+ : this(propertyName, deserializableType, validationFailures.Select(e => e.ToString()))
+ {
+ }
+
+ public IReadOnlyCollection ErrorMessages { get; }
public string PropertyName { get; }
public Type DeserializableType { get; }
+
+ public override string Message => BuildMessage();
+
+ private string BuildMessage()
+ {
+ const string delim = "\n - ";
+ var builder = new StringBuilder(
+ $"An exception occurred while deserializing type '{DeserializableType}' for YML property '{PropertyName}'");
+ if (ErrorMessages.Count > 0)
+ {
+ builder.Append(":" + delim);
+ builder.Append(string.Join(delim, ErrorMessages));
+ }
+
+ return builder.ToString();
+ }
}
}
diff --git a/src/Trash/Config/ConfigurationLoader.cs b/src/Trash/Config/ConfigurationLoader.cs
index c2f7f338..2e64325e 100644
--- a/src/Trash/Config/ConfigurationLoader.cs
+++ b/src/Trash/Config/ConfigurationLoader.cs
@@ -3,6 +3,7 @@ using System.IO;
using System.IO.Abstractions;
using System.Linq;
using Common.YamlDotNet;
+using FluentValidation;
using TrashLib.Config;
using YamlDotNet.Core;
using YamlDotNet.Core.Events;
@@ -17,14 +18,19 @@ namespace Trash.Config
private readonly IConfigurationProvider _configProvider;
private readonly IDeserializer _deserializer;
private readonly IFileSystem _fileSystem;
+ private readonly IValidator _validator;
- public ConfigurationLoader(IConfigurationProvider configProvider, IFileSystem fileSystem,
- IObjectFactory objectFactory)
+ public ConfigurationLoader(
+ IConfigurationProvider configProvider,
+ IFileSystem fileSystem,
+ IObjectFactory objectFactory,
+ IValidator validator)
{
_configProvider = configProvider;
_fileSystem = fileSystem;
+ _validator = validator;
_deserializer = new DeserializerBuilder()
- .WithRequiredPropertyValidation()
+ .IgnoreUnmatchedProperties()
.WithNamingConvention(UnderscoredNamingConvention.Instance)
.WithTypeConverter(new YamlNullableEnumTypeConverter())
.WithObjectFactory(objectFactory)
@@ -54,9 +60,10 @@ namespace Trash.Config
{
foreach (var config in configs)
{
- if (!config.IsValid(out var msg))
+ var result = _validator.Validate(config);
+ if (result is {IsValid: false})
{
- throw new ConfigurationException(configSection, typeof(T), msg);
+ throw new ConfigurationException(configSection, typeof(T), result.Errors);
}
validConfigs.Add(config);
diff --git a/src/Trash.TestLibrary/CfTestUtils.cs b/src/TrashLib.TestLibrary/CfTestUtils.cs
similarity index 100%
rename from src/Trash.TestLibrary/CfTestUtils.cs
rename to src/TrashLib.TestLibrary/CfTestUtils.cs
diff --git a/src/TrashLib.TestLibrary/TrashLib.TestLibrary.csproj b/src/TrashLib.TestLibrary/TrashLib.TestLibrary.csproj
new file mode 100644
index 00000000..7a394e35
--- /dev/null
+++ b/src/TrashLib.TestLibrary/TrashLib.TestLibrary.csproj
@@ -0,0 +1,8 @@
+
+
+ Trash.TestLibrary
+
+
+
+
+
diff --git a/src/TrashLib.Tests/Radarr/CustomFormat/Processors/GuideProcessorTest.cs b/src/TrashLib.Tests/Radarr/CustomFormat/Processors/GuideProcessorTest.cs
index 7be17746..3d5298ba 100644
--- a/src/TrashLib.Tests/Radarr/CustomFormat/Processors/GuideProcessorTest.cs
+++ b/src/TrashLib.Tests/Radarr/CustomFormat/Processors/GuideProcessorTest.cs
@@ -8,7 +8,7 @@ using NUnit.Framework;
using Serilog;
using TestLibrary.FluentAssertions;
using Trash.TestLibrary;
-using TrashLib.Radarr;
+using TrashLib.Radarr.Config;
using TrashLib.Radarr.CustomFormat.Guide;
using TrashLib.Radarr.CustomFormat.Models;
using TrashLib.Radarr.CustomFormat.Processors;
diff --git a/src/TrashLib.Tests/Radarr/CustomFormat/Processors/GuideSteps/ConfigStepTest.cs b/src/TrashLib.Tests/Radarr/CustomFormat/Processors/GuideSteps/ConfigStepTest.cs
index 71763f96..fe844218 100644
--- a/src/TrashLib.Tests/Radarr/CustomFormat/Processors/GuideSteps/ConfigStepTest.cs
+++ b/src/TrashLib.Tests/Radarr/CustomFormat/Processors/GuideSteps/ConfigStepTest.cs
@@ -2,7 +2,7 @@ using System.Collections.Generic;
using FluentAssertions;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
-using TrashLib.Radarr;
+using TrashLib.Radarr.Config;
using TrashLib.Radarr.CustomFormat.Models;
using TrashLib.Radarr.CustomFormat.Models.Cache;
using TrashLib.Radarr.CustomFormat.Processors.GuideSteps;
diff --git a/src/TrashLib.Tests/Radarr/CustomFormat/Processors/GuideSteps/CustomFormatStepTest.cs b/src/TrashLib.Tests/Radarr/CustomFormat/Processors/GuideSteps/CustomFormatStepTest.cs
index 6c7a4eaf..bd5e2c3a 100644
--- a/src/TrashLib.Tests/Radarr/CustomFormat/Processors/GuideSteps/CustomFormatStepTest.cs
+++ b/src/TrashLib.Tests/Radarr/CustomFormat/Processors/GuideSteps/CustomFormatStepTest.cs
@@ -5,7 +5,7 @@ using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using TestLibrary.FluentAssertions;
-using TrashLib.Radarr;
+using TrashLib.Radarr.Config;
using TrashLib.Radarr.CustomFormat.Models;
using TrashLib.Radarr.CustomFormat.Models.Cache;
using TrashLib.Radarr.CustomFormat.Processors.GuideSteps;
diff --git a/src/TrashLib.Tests/Radarr/CustomFormat/Processors/GuideSteps/QualityProfileStepTest.cs b/src/TrashLib.Tests/Radarr/CustomFormat/Processors/GuideSteps/QualityProfileStepTest.cs
index 9d0baa8b..a927348c 100644
--- a/src/TrashLib.Tests/Radarr/CustomFormat/Processors/GuideSteps/QualityProfileStepTest.cs
+++ b/src/TrashLib.Tests/Radarr/CustomFormat/Processors/GuideSteps/QualityProfileStepTest.cs
@@ -3,7 +3,7 @@ using FluentAssertions;
using Newtonsoft.Json.Linq;
using NUnit.Framework;
using Trash.TestLibrary;
-using TrashLib.Radarr;
+using TrashLib.Radarr.Config;
using TrashLib.Radarr.CustomFormat.Models;
using TrashLib.Radarr.CustomFormat.Processors.GuideSteps;
diff --git a/src/TrashLib.Tests/Radarr/CustomFormat/Processors/PersistenceProcessorTest.cs b/src/TrashLib.Tests/Radarr/CustomFormat/Processors/PersistenceProcessorTest.cs
index 919fb1fb..4db56160 100644
--- a/src/TrashLib.Tests/Radarr/CustomFormat/Processors/PersistenceProcessorTest.cs
+++ b/src/TrashLib.Tests/Radarr/CustomFormat/Processors/PersistenceProcessorTest.cs
@@ -5,7 +5,7 @@ using Newtonsoft.Json.Linq;
using NSubstitute;
using NUnit.Framework;
using TrashLib.Config;
-using TrashLib.Radarr;
+using TrashLib.Radarr.Config;
using TrashLib.Radarr.CustomFormat.Api;
using TrashLib.Radarr.CustomFormat.Models;
using TrashLib.Radarr.CustomFormat.Models.Cache;
diff --git a/src/TrashLib.Tests/Radarr/RadarrConfigurationTest.cs b/src/TrashLib.Tests/Radarr/RadarrConfigurationTest.cs
index cef53d12..5352e932 100644
--- a/src/TrashLib.Tests/Radarr/RadarrConfigurationTest.cs
+++ b/src/TrashLib.Tests/Radarr/RadarrConfigurationTest.cs
@@ -1,15 +1,13 @@
-using System;
-using System.Collections;
-using System.IO;
-using System.IO.Abstractions;
+using System.Collections.Generic;
+using System.Linq;
+using Autofac;
using FluentAssertions;
-using NSubstitute;
+using FluentValidation;
using NUnit.Framework;
-using Trash.Config;
using TrashLib.Config;
using TrashLib.Radarr;
-using YamlDotNet.Core;
-using YamlDotNet.Serialization.ObjectFactories;
+using TrashLib.Radarr.Config;
+using TrashLib.Radarr.QualityDefinition;
namespace TrashLib.Tests.Radarr
{
@@ -17,105 +15,96 @@ namespace TrashLib.Tests.Radarr
[Parallelizable(ParallelScope.All)]
public class RadarrConfigurationTest
{
- public static IEnumerable GetTrashIdsOrNamesEmptyTestData()
- {
- yield return new TestCaseData(@"
-radarr:
- - api_key: abc
- base_url: xyz
- custom_formats:
- - names: [foo]
- quality_profiles:
- - name: MyProfile
-")
- .SetName("{m} (without_trash_ids)");
+ private IContainer _container = default!;
- yield return new TestCaseData(@"
-radarr:
- - api_key: abc
- base_url: xyz
- custom_formats:
- - trash_ids: [abc123]
- quality_profiles:
- - name: MyProfile
-")
- .SetName("{m} (without_names)");
+ [OneTimeSetUp]
+ public void Setup()
+ {
+ var builder = new ContainerBuilder();
+ builder.RegisterModule();
+ builder.RegisterModule();
+ _container = builder.Build();
}
- [TestCaseSource(nameof(GetTrashIdsOrNamesEmptyTestData))]
- public void Custom_format_either_names_or_trash_id_not_empty_is_ok(string testYaml)
+ private static readonly TestCaseData[] NameOrIdsTestData =
{
- var configLoader = new ConfigurationLoader(
- Substitute.For(),
- Substitute.For(), new DefaultObjectFactory());
-
- Action act = () => configLoader.LoadFromStream(new StringReader(testYaml), "radarr");
-
- act.Should().NotThrow();
- }
+ new(new List {"name"}, new List()),
+ new(new List(), new List {"trash_id"})
+ };
- [Test]
- public void Custom_format_names_and_trash_ids_lists_must_not_both_be_empty()
+ [TestCaseSource(nameof(NameOrIdsTestData))]
+ public void Custom_format_is_valid_with_one_of_either_names_or_trash_id(List namesList,
+ List trashIdsList)
{
- var testYaml = @"
-radarr:
- - api_key: abc
- base_url: xyz
- custom_formats:
- - quality_profiles:
- - name: MyProfile
-";
- var configLoader = new ConfigurationLoader(
- Substitute.For(),
- Substitute.For(), new DefaultObjectFactory());
+ var config = new RadarrConfiguration
+ {
+ ApiKey = "required value",
+ BaseUrl = "required value",
+ CustomFormats = new List
+ {
+ new() {Names = namesList, TrashIds = trashIdsList}
+ }
+ };
- Action act = () => configLoader.LoadFromStream(new StringReader(testYaml), "radarr");
+ var validator = _container.Resolve>();
+ var result = validator.Validate(config);
- act.Should().Throw()
- .WithMessage("*must contain at least one element in either 'names' or 'trash_ids'.");
+ result.IsValid.Should().BeTrue();
+ result.Errors.Should().BeEmpty();
}
[Test]
- public void Quality_definition_type_is_required()
+ public void Validation_fails_for_all_missing_required_properties()
{
- const string yaml = @"
-radarr:
-- base_url: a
- api_key: b
- quality_definition:
- preferred_ratio: 0.5
-";
- var loader = new ConfigurationLoader(
- Substitute.For(),
- Substitute.For(),
- new DefaultObjectFactory());
+ // default construct which should yield default values (invalid) for all required properties
+ var config = new RadarrConfiguration();
+ var validator = _container.Resolve>();
- Action act = () => loader.LoadFromStream(new StringReader(yaml), "radarr");
+ var result = validator.Validate(config);
- act.Should().Throw()
- .WithMessage("*'type' is required for 'quality_definition'");
+ var expectedErrorMessageSubstrings = new[]
+ {
+ "Property 'base_url' is required",
+ "Property 'api_key' is required",
+ "'custom_formats' elements must contain at least one element in either 'names' or 'trash_ids'",
+ "'name' is required for elements under 'quality_profiles'",
+ "'type' is required for 'quality_definition'"
+ };
+
+ result.IsValid.Should().BeFalse();
+ result.Errors.Select(e => e.ErrorMessage).Should()
+ .OnlyContain(x => expectedErrorMessageSubstrings.Any(x.Contains));
}
[Test]
- public void Quality_profile_name_is_required()
+ public void Validation_succeeds_when_no_missing_required_properties()
{
- const string testYaml = @"
-radarr:
- - api_key: abc
- base_url: xyz
- custom_formats:
- - names: [one, two]
- quality_profiles:
- - score: 100
-";
-
- var configLoader = new ConfigurationLoader(
- Substitute.For(),
- Substitute.For(), new DefaultObjectFactory());
+ var config = new RadarrConfiguration
+ {
+ ApiKey = "required value",
+ BaseUrl = "required value",
+ CustomFormats = new List
+ {
+ new()
+ {
+ Names = new List{"required value"},
+ QualityProfiles = new List
+ {
+ new() {Name = "required value"}
+ }
+ }
+ },
+ QualityDefinition = new QualityDefinitionConfig
+ {
+ Type = RadarrQualityDefinitionType.Movie
+ }
+ };
- Action act = () => configLoader.LoadFromStream(new StringReader(testYaml), "radarr");
+ var validator = _container.Resolve>();
+ var result = validator.Validate(config);
- act.Should().Throw();
+ result.IsValid.Should().BeTrue();
+ result.Errors.Should().BeEmpty();
}
}
}
diff --git a/src/TrashLib.Tests/Sonarr/ReleaseProfile/FilteredProfileDataTest.cs b/src/TrashLib.Tests/Sonarr/ReleaseProfile/FilteredProfileDataTest.cs
index efd58bb1..7e584b57 100644
--- a/src/TrashLib.Tests/Sonarr/ReleaseProfile/FilteredProfileDataTest.cs
+++ b/src/TrashLib.Tests/Sonarr/ReleaseProfile/FilteredProfileDataTest.cs
@@ -1,7 +1,7 @@
using System.Collections.Generic;
using FluentAssertions;
using NUnit.Framework;
-using TrashLib.Sonarr;
+using TrashLib.Sonarr.Config;
using TrashLib.Sonarr.ReleaseProfile;
namespace TrashLib.Tests.Sonarr.ReleaseProfile
diff --git a/src/TrashLib.Tests/Sonarr/ReleaseProfile/ReleaseProfileParserTest.cs b/src/TrashLib.Tests/Sonarr/ReleaseProfile/ReleaseProfileParserTest.cs
index 4a27ef0b..a2a70d88 100644
--- a/src/TrashLib.Tests/Sonarr/ReleaseProfile/ReleaseProfileParserTest.cs
+++ b/src/TrashLib.Tests/Sonarr/ReleaseProfile/ReleaseProfileParserTest.cs
@@ -6,7 +6,7 @@ using NUnit.Framework;
using Serilog;
using Serilog.Sinks.TestCorrelator;
using TestLibrary;
-using TrashLib.Sonarr;
+using TrashLib.Sonarr.Config;
using TrashLib.Sonarr.ReleaseProfile;
namespace TrashLib.Tests.Sonarr.ReleaseProfile
diff --git a/src/TrashLib.Tests/Sonarr/ReleaseProfileUpdaterTest.cs b/src/TrashLib.Tests/Sonarr/ReleaseProfileUpdaterTest.cs
index c4ed4126..db4e7384 100644
--- a/src/TrashLib.Tests/Sonarr/ReleaseProfileUpdaterTest.cs
+++ b/src/TrashLib.Tests/Sonarr/ReleaseProfileUpdaterTest.cs
@@ -1,8 +1,8 @@
using NSubstitute;
using NUnit.Framework;
using Serilog;
-using TrashLib.Sonarr;
using TrashLib.Sonarr.Api;
+using TrashLib.Sonarr.Config;
using TrashLib.Sonarr.ReleaseProfile;
namespace TrashLib.Tests.Sonarr
diff --git a/src/TrashLib.Tests/Sonarr/SonarrConfigurationTest.cs b/src/TrashLib.Tests/Sonarr/SonarrConfigurationTest.cs
index c4a84ecf..42de601f 100644
--- a/src/TrashLib.Tests/Sonarr/SonarrConfigurationTest.cs
+++ b/src/TrashLib.Tests/Sonarr/SonarrConfigurationTest.cs
@@ -1,14 +1,13 @@
-using System;
-using System.IO;
-using System.IO.Abstractions;
+using System.Collections.Generic;
+using System.Linq;
+using Autofac;
using FluentAssertions;
-using NSubstitute;
+using FluentValidation;
using NUnit.Framework;
-using Trash.Config;
using TrashLib.Config;
using TrashLib.Sonarr;
-using YamlDotNet.Core;
-using YamlDotNet.Serialization.ObjectFactories;
+using TrashLib.Sonarr.Config;
+using TrashLib.Sonarr.ReleaseProfile;
namespace TrashLib.Tests.Sonarr
{
@@ -16,25 +15,56 @@ namespace TrashLib.Tests.Sonarr
[Parallelizable(ParallelScope.All)]
public class SonarrConfigurationTest
{
+ private IContainer _container = default!;
+
+ [OneTimeSetUp]
+ public void Setup()
+ {
+ var builder = new ContainerBuilder();
+ builder.RegisterModule();
+ builder.RegisterModule();
+ _container = builder.Build();
+ }
+
[Test]
- public void Deserialize_ReleaseProfileTypeMissing_Throw()
+ public void Validation_fails_for_all_missing_required_properties()
{
- const string yaml = @"
-sonarr:
-- base_url: a
- api_key: b
- release_profiles:
- - strict_negative_scores: true
-";
- var loader = new ConfigurationLoader(
- Substitute.For(),
- Substitute.For(),
- new DefaultObjectFactory());
-
- Action act = () => loader.LoadFromStream(new StringReader(yaml), "sonarr");
-
- act.Should().Throw()
- .WithMessage("*'type' is required for 'release_profiles' elements");
+ // default construct which should yield default values (invalid) for all required properties
+ var config = new SonarrConfiguration();
+ var validator = _container.Resolve>();
+
+ var result = validator.Validate(config);
+
+ var expectedErrorMessageSubstrings = new[]
+ {
+ "Property 'base_url' is required",
+ "Property 'api_key' is required",
+ "'type' is required for 'release_profiles' elements"
+ };
+
+ result.IsValid.Should().BeFalse();
+ result.Errors.Select(e => e.ErrorMessage).Should()
+ .OnlyContain(x => expectedErrorMessageSubstrings.Any(x.Contains));
+ }
+
+ [Test]
+ public void Validation_succeeds_when_no_missing_required_properties()
+ {
+ var config = new SonarrConfiguration
+ {
+ ApiKey = "required value",
+ BaseUrl = "required value",
+ ReleaseProfiles = new List
+ {
+ new() {Type = ReleaseProfileType.Anime}
+ }
+ };
+
+ var validator = _container.Resolve>();
+ var result = validator.Validate(config);
+
+ result.IsValid.Should().BeTrue();
+ result.Errors.Should().BeEmpty();
}
}
}
diff --git a/src/TrashLib.Tests/TrashLib.Tests.csproj b/src/TrashLib.Tests/TrashLib.Tests.csproj
index 9f984b62..62202ad4 100644
--- a/src/TrashLib.Tests/TrashLib.Tests.csproj
+++ b/src/TrashLib.Tests/TrashLib.Tests.csproj
@@ -5,7 +5,7 @@
-
-
+
+
diff --git a/src/TrashLib/Config/ConfigAutofacModule.cs b/src/TrashLib/Config/ConfigAutofacModule.cs
index deadcaf4..5640d410 100644
--- a/src/TrashLib/Config/ConfigAutofacModule.cs
+++ b/src/TrashLib/Config/ConfigAutofacModule.cs
@@ -1,4 +1,7 @@
+using System.Reflection;
using Autofac;
+using FluentValidation;
+using Module = Autofac.Module;
namespace TrashLib.Config
{
@@ -9,6 +12,10 @@ namespace TrashLib.Config
builder.RegisterType()
.As()
.SingleInstance();
+
+ builder.RegisterAssemblyTypes(Assembly.GetExecutingAssembly())
+ .AsClosedTypesOf(typeof(IValidator<>))
+ .AsImplementedInterfaces();
}
}
}
diff --git a/src/TrashLib/IServerInfo.cs b/src/TrashLib/Config/IServerInfo.cs
similarity index 74%
rename from src/TrashLib/IServerInfo.cs
rename to src/TrashLib/Config/IServerInfo.cs
index ee77e831..c76b6b0d 100644
--- a/src/TrashLib/IServerInfo.cs
+++ b/src/TrashLib/Config/IServerInfo.cs
@@ -1,4 +1,4 @@
-namespace TrashLib
+namespace TrashLib.Config
{
public interface IServerInfo
{
diff --git a/src/TrashLib/Config/IServiceConfiguration.cs b/src/TrashLib/Config/IServiceConfiguration.cs
index b6599e00..6ffd131f 100644
--- a/src/TrashLib/Config/IServiceConfiguration.cs
+++ b/src/TrashLib/Config/IServiceConfiguration.cs
@@ -2,8 +2,7 @@ namespace TrashLib.Config
{
public interface IServiceConfiguration
{
- string BaseUrl { get; init; }
- string ApiKey { get; init; }
- bool IsValid(out string msg);
+ string BaseUrl { get; }
+ string ApiKey { get; }
}
}
diff --git a/src/TrashLib/ServerInfo.cs b/src/TrashLib/Config/ServerInfo.cs
similarity index 94%
rename from src/TrashLib/ServerInfo.cs
rename to src/TrashLib/Config/ServerInfo.cs
index 8fab512e..f4978ec7 100644
--- a/src/TrashLib/ServerInfo.cs
+++ b/src/TrashLib/Config/ServerInfo.cs
@@ -1,6 +1,6 @@
using Flurl;
-namespace TrashLib
+namespace TrashLib.Config
{
internal class ServerInfo : IServerInfo
{
diff --git a/src/TrashLib/Config/ServiceConfiguration.cs b/src/TrashLib/Config/ServiceConfiguration.cs
index 881fcd9b..964d19cf 100644
--- a/src/TrashLib/Config/ServiceConfiguration.cs
+++ b/src/TrashLib/Config/ServiceConfiguration.cs
@@ -1,15 +1,8 @@
-using System.ComponentModel.DataAnnotations;
-
-namespace TrashLib.Config
+namespace TrashLib.Config
{
public abstract class ServiceConfiguration : IServiceConfiguration
{
- [Required(ErrorMessage = "Property 'base_url' is required")]
public string BaseUrl { get; init; } = "";
-
- [Required(ErrorMessage = "Property 'api_key' is required")]
public string ApiKey { get; init; } = "";
-
- public abstract bool IsValid(out string msg);
}
}
diff --git a/src/TrashLib/Radarr/Config/IRadarrValidationMessages.cs b/src/TrashLib/Radarr/Config/IRadarrValidationMessages.cs
new file mode 100644
index 00000000..b200295d
--- /dev/null
+++ b/src/TrashLib/Radarr/Config/IRadarrValidationMessages.cs
@@ -0,0 +1,11 @@
+namespace TrashLib.Radarr.Config
+{
+ public interface IRadarrValidationMessages
+ {
+ string BaseUrl { get; }
+ string ApiKey { get; }
+ string CustomFormatNamesAndIds { get; }
+ string QualityProfileName { get; }
+ string QualityDefinitionType { get; }
+ }
+}
diff --git a/src/TrashLib/Radarr/RadarrConfiguration.cs b/src/TrashLib/Radarr/Config/RadarrConfiguration.cs
similarity index 60%
rename from src/TrashLib/Radarr/RadarrConfiguration.cs
rename to src/TrashLib/Radarr/Config/RadarrConfiguration.cs
index 0cb4cdc4..988ec9bd 100644
--- a/src/TrashLib/Radarr/RadarrConfiguration.cs
+++ b/src/TrashLib/Radarr/Config/RadarrConfiguration.cs
@@ -1,11 +1,9 @@
using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using System.Linq;
using JetBrains.Annotations;
using TrashLib.Config;
using TrashLib.Radarr.QualityDefinition;
-namespace TrashLib.Radarr
+namespace TrashLib.Radarr.Config
{
[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
public class RadarrConfiguration : ServiceConfiguration
@@ -13,18 +11,6 @@ namespace TrashLib.Radarr
public QualityDefinitionConfig? QualityDefinition { get; init; }
public List CustomFormats { get; init; } = new();
public bool DeleteOldCustomFormats { get; init; }
-
- public override bool IsValid(out string msg)
- {
- if (CustomFormats.Any(cf => cf.TrashIds.Count + cf.Names.Count == 0))
- {
- msg = "'custom_formats' elements must contain at least one element in either 'names' or 'trash_ids'.";
- return false;
- }
-
- msg = "";
- return true;
- }
}
[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
@@ -38,9 +24,7 @@ namespace TrashLib.Radarr
[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
public class QualityProfileConfig
{
- [Required(ErrorMessage = "'name' is required for elements under 'quality_profiles'")]
public string Name { get; init; } = "";
-
public int? Score { get; init; }
public bool ResetUnmatchedScores { get; init; }
}
@@ -48,11 +32,8 @@ namespace TrashLib.Radarr
[UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
public class QualityDefinitionConfig
{
- // -1 does not map to a valid enumerator. this is to force validation to fail if it is not set from YAML
- // all of this craziness is to avoid making the enum type nullable which will make using the property
- // frustrating.
- [EnumDataType(typeof(RadarrQualityDefinitionType),
- ErrorMessage = "'type' is required for 'quality_definition'")]
+ // -1 does not map to a valid enumerator. this is to force validation to fail if it is not set from YAML.
+ // All of this craziness is to avoid making the enum type nullable.
public RadarrQualityDefinitionType Type { get; init; } = (RadarrQualityDefinitionType) (-1);
public decimal PreferredRatio { get; set; } = 1.0m;
diff --git a/src/TrashLib/Radarr/Config/RadarrConfigurationValidator.cs b/src/TrashLib/Radarr/Config/RadarrConfigurationValidator.cs
new file mode 100644
index 00000000..21dcf3c8
--- /dev/null
+++ b/src/TrashLib/Radarr/Config/RadarrConfigurationValidator.cs
@@ -0,0 +1,52 @@
+using Common.Extensions;
+using FluentValidation;
+using JetBrains.Annotations;
+
+namespace TrashLib.Radarr.Config
+{
+ [UsedImplicitly]
+ internal class RadarrConfigurationValidator : AbstractValidator
+ {
+ public RadarrConfigurationValidator(
+ IRadarrValidationMessages messages,
+ IValidator qualityDefinitionConfigValidator,
+ IValidator customFormatConfigValidator)
+ {
+ RuleFor(x => x.BaseUrl).NotEmpty().WithMessage(messages.BaseUrl);
+ RuleFor(x => x.ApiKey).NotEmpty().WithMessage(messages.ApiKey);
+ RuleFor(x => x.QualityDefinition).SetNonNullableValidator(qualityDefinitionConfigValidator);
+ RuleForEach(x => x.CustomFormats).SetValidator(customFormatConfigValidator);
+ }
+ }
+
+ [UsedImplicitly]
+ internal class CustomFormatConfigValidator : AbstractValidator
+ {
+ public CustomFormatConfigValidator(
+ IRadarrValidationMessages messages,
+ IValidator qualityProfileConfigValidator)
+ {
+ RuleFor(x => x.Names).NotEmpty().When(x => x.TrashIds.Count == 0)
+ .WithMessage(messages.CustomFormatNamesAndIds);
+ RuleForEach(x => x.QualityProfiles).SetValidator(qualityProfileConfigValidator);
+ }
+ }
+
+ [UsedImplicitly]
+ internal class QualityProfileConfigValidator : AbstractValidator
+ {
+ public QualityProfileConfigValidator(IRadarrValidationMessages messages)
+ {
+ RuleFor(x => x.Name).NotEmpty().WithMessage(messages.QualityProfileName);
+ }
+ }
+
+ [UsedImplicitly]
+ internal class QualityDefinitionConfigValidator : AbstractValidator
+ {
+ public QualityDefinitionConfigValidator(IRadarrValidationMessages messages)
+ {
+ RuleFor(x => x.Type).IsInEnum().WithMessage(messages.QualityDefinitionType);
+ }
+ }
+}
diff --git a/src/TrashLib/Radarr/Config/RadarrValidationMessages.cs b/src/TrashLib/Radarr/Config/RadarrValidationMessages.cs
new file mode 100644
index 00000000..b6c69d14
--- /dev/null
+++ b/src/TrashLib/Radarr/Config/RadarrValidationMessages.cs
@@ -0,0 +1,20 @@
+namespace TrashLib.Radarr.Config
+{
+ internal class RadarrValidationMessages : IRadarrValidationMessages
+ {
+ public string BaseUrl =>
+ "Property 'base_url' is required";
+
+ public string ApiKey =>
+ "Property 'api_key' is required";
+
+ public string CustomFormatNamesAndIds =>
+ "'custom_formats' elements must contain at least one element in either 'names' or 'trash_ids'";
+
+ public string QualityProfileName =>
+ "'name' is required for elements under 'quality_profiles'";
+
+ public string QualityDefinitionType =>
+ "'type' is required for 'quality_definition'";
+ }
+}
diff --git a/src/TrashLib/Radarr/CustomFormat/Api/CustomFormatService.cs b/src/TrashLib/Radarr/CustomFormat/Api/CustomFormatService.cs
index 8677514c..c54aab1e 100644
--- a/src/TrashLib/Radarr/CustomFormat/Api/CustomFormatService.cs
+++ b/src/TrashLib/Radarr/CustomFormat/Api/CustomFormatService.cs
@@ -3,6 +3,7 @@ using System.Threading.Tasks;
using Flurl;
using Flurl.Http;
using Newtonsoft.Json.Linq;
+using TrashLib.Config;
using TrashLib.Radarr.CustomFormat.Models;
namespace TrashLib.Radarr.CustomFormat.Api
diff --git a/src/TrashLib/Radarr/CustomFormat/Api/QualityProfileService.cs b/src/TrashLib/Radarr/CustomFormat/Api/QualityProfileService.cs
index c5a35803..fa2e9898 100644
--- a/src/TrashLib/Radarr/CustomFormat/Api/QualityProfileService.cs
+++ b/src/TrashLib/Radarr/CustomFormat/Api/QualityProfileService.cs
@@ -3,6 +3,7 @@ using System.Threading.Tasks;
using Flurl;
using Flurl.Http;
using Newtonsoft.Json.Linq;
+using TrashLib.Config;
namespace TrashLib.Radarr.CustomFormat.Api
{
diff --git a/src/TrashLib/Radarr/CustomFormat/CustomFormatUpdater.cs b/src/TrashLib/Radarr/CustomFormat/CustomFormatUpdater.cs
index 253dffa8..babc10cb 100644
--- a/src/TrashLib/Radarr/CustomFormat/CustomFormatUpdater.cs
+++ b/src/TrashLib/Radarr/CustomFormat/CustomFormatUpdater.cs
@@ -3,6 +3,7 @@ using System.Linq;
using System.Threading.Tasks;
using Common.Extensions;
using Serilog;
+using TrashLib.Radarr.Config;
using TrashLib.Radarr.CustomFormat.Processors;
using TrashLib.Radarr.CustomFormat.Processors.PersistenceSteps;
diff --git a/src/TrashLib/Radarr/CustomFormat/ICustomFormatUpdater.cs b/src/TrashLib/Radarr/CustomFormat/ICustomFormatUpdater.cs
index 06e935b5..58fd89f0 100644
--- a/src/TrashLib/Radarr/CustomFormat/ICustomFormatUpdater.cs
+++ b/src/TrashLib/Radarr/CustomFormat/ICustomFormatUpdater.cs
@@ -1,4 +1,5 @@
using System.Threading.Tasks;
+using TrashLib.Radarr.Config;
namespace TrashLib.Radarr.CustomFormat
{
diff --git a/src/TrashLib/Radarr/CustomFormat/Models/ProcessedConfigData.cs b/src/TrashLib/Radarr/CustomFormat/Models/ProcessedConfigData.cs
index 3e0ac62d..4cc5a06e 100644
--- a/src/TrashLib/Radarr/CustomFormat/Models/ProcessedConfigData.cs
+++ b/src/TrashLib/Radarr/CustomFormat/Models/ProcessedConfigData.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using TrashLib.Radarr.Config;
namespace TrashLib.Radarr.CustomFormat.Models
{
diff --git a/src/TrashLib/Radarr/CustomFormat/Processors/GuideProcessor.cs b/src/TrashLib/Radarr/CustomFormat/Processors/GuideProcessor.cs
index f3471273..8e0b86fe 100644
--- a/src/TrashLib/Radarr/CustomFormat/Processors/GuideProcessor.cs
+++ b/src/TrashLib/Radarr/CustomFormat/Processors/GuideProcessor.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Serilog;
+using TrashLib.Radarr.Config;
using TrashLib.Radarr.CustomFormat.Guide;
using TrashLib.Radarr.CustomFormat.Models;
using TrashLib.Radarr.CustomFormat.Models.Cache;
diff --git a/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/ConfigStep.cs b/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/ConfigStep.cs
index a67d39c0..e724aa32 100644
--- a/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/ConfigStep.cs
+++ b/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/ConfigStep.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using Common.Extensions;
using MoreLinq.Extensions;
+using TrashLib.Radarr.Config;
using TrashLib.Radarr.CustomFormat.Models;
namespace TrashLib.Radarr.CustomFormat.Processors.GuideSteps
diff --git a/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/CustomFormatStep.cs b/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/CustomFormatStep.cs
index fac591fb..610073e5 100644
--- a/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/CustomFormatStep.cs
+++ b/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/CustomFormatStep.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using Common.Extensions;
using Newtonsoft.Json.Linq;
+using TrashLib.Radarr.Config;
using TrashLib.Radarr.CustomFormat.Models;
using TrashLib.Radarr.CustomFormat.Models.Cache;
diff --git a/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/IConfigStep.cs b/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/IConfigStep.cs
index dc4d57d9..774bbd54 100644
--- a/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/IConfigStep.cs
+++ b/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/IConfigStep.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using TrashLib.Radarr.Config;
using TrashLib.Radarr.CustomFormat.Models;
namespace TrashLib.Radarr.CustomFormat.Processors.GuideSteps
diff --git a/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/ICustomFormatStep.cs b/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/ICustomFormatStep.cs
index c9b07e55..5ccd1af5 100644
--- a/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/ICustomFormatStep.cs
+++ b/src/TrashLib/Radarr/CustomFormat/Processors/GuideSteps/ICustomFormatStep.cs
@@ -1,4 +1,5 @@
using System.Collections.Generic;
+using TrashLib.Radarr.Config;
using TrashLib.Radarr.CustomFormat.Models;
using TrashLib.Radarr.CustomFormat.Models.Cache;
diff --git a/src/TrashLib/Radarr/CustomFormat/Processors/IGuideProcessor.cs b/src/TrashLib/Radarr/CustomFormat/Processors/IGuideProcessor.cs
index 1656c7b2..66a5b2e5 100644
--- a/src/TrashLib/Radarr/CustomFormat/Processors/IGuideProcessor.cs
+++ b/src/TrashLib/Radarr/CustomFormat/Processors/IGuideProcessor.cs
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Threading.Tasks;
+using TrashLib.Radarr.Config;
using TrashLib.Radarr.CustomFormat.Models;
using TrashLib.Radarr.CustomFormat.Models.Cache;
diff --git a/src/TrashLib/Radarr/CustomFormat/Processors/PersistenceProcessor.cs b/src/TrashLib/Radarr/CustomFormat/Processors/PersistenceProcessor.cs
index 67714565..aaae4f7d 100644
--- a/src/TrashLib/Radarr/CustomFormat/Processors/PersistenceProcessor.cs
+++ b/src/TrashLib/Radarr/CustomFormat/Processors/PersistenceProcessor.cs
@@ -2,6 +2,7 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using TrashLib.Config;
+using TrashLib.Radarr.Config;
using TrashLib.Radarr.CustomFormat.Api;
using TrashLib.Radarr.CustomFormat.Models;
using TrashLib.Radarr.CustomFormat.Models.Cache;
diff --git a/src/TrashLib/Radarr/QualityDefinition/Api/QualityDefinitionService.cs b/src/TrashLib/Radarr/QualityDefinition/Api/QualityDefinitionService.cs
index 51b54343..f38c6e8a 100644
--- a/src/TrashLib/Radarr/QualityDefinition/Api/QualityDefinitionService.cs
+++ b/src/TrashLib/Radarr/QualityDefinition/Api/QualityDefinitionService.cs
@@ -2,6 +2,7 @@
using System.Threading.Tasks;
using Flurl;
using Flurl.Http;
+using TrashLib.Config;
using TrashLib.Radarr.QualityDefinition.Api.Objects;
namespace TrashLib.Radarr.QualityDefinition.Api
diff --git a/src/TrashLib/Radarr/QualityDefinition/IRadarrQualityDefinitionUpdater.cs b/src/TrashLib/Radarr/QualityDefinition/IRadarrQualityDefinitionUpdater.cs
index dd946c50..38942bd6 100644
--- a/src/TrashLib/Radarr/QualityDefinition/IRadarrQualityDefinitionUpdater.cs
+++ b/src/TrashLib/Radarr/QualityDefinition/IRadarrQualityDefinitionUpdater.cs
@@ -1,4 +1,5 @@
using System.Threading.Tasks;
+using TrashLib.Radarr.Config;
namespace TrashLib.Radarr.QualityDefinition
{
diff --git a/src/TrashLib/Radarr/QualityDefinition/RadarrQualityDefinitionUpdater.cs b/src/TrashLib/Radarr/QualityDefinition/RadarrQualityDefinitionUpdater.cs
index ee91deb0..0014ff2b 100644
--- a/src/TrashLib/Radarr/QualityDefinition/RadarrQualityDefinitionUpdater.cs
+++ b/src/TrashLib/Radarr/QualityDefinition/RadarrQualityDefinitionUpdater.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Serilog;
+using TrashLib.Radarr.Config;
using TrashLib.Radarr.QualityDefinition.Api;
using TrashLib.Radarr.QualityDefinition.Api.Objects;
diff --git a/src/TrashLib/Radarr/RadarrAutofacModule.cs b/src/TrashLib/Radarr/RadarrAutofacModule.cs
index e3da3f6e..026df56f 100644
--- a/src/TrashLib/Radarr/RadarrAutofacModule.cs
+++ b/src/TrashLib/Radarr/RadarrAutofacModule.cs
@@ -1,6 +1,7 @@
using Autofac;
using Autofac.Extras.AggregateService;
using TrashLib.Config;
+using TrashLib.Radarr.Config;
using TrashLib.Radarr.CustomFormat;
using TrashLib.Radarr.CustomFormat.Api;
using TrashLib.Radarr.CustomFormat.Guide;
@@ -21,6 +22,8 @@ namespace TrashLib.Radarr
builder.RegisterType().As();
builder.RegisterType().As();
+ // Configuration
+ builder.RegisterType().As();
builder.Register(c =>
{
var config = c.Resolve().ActiveConfiguration;
diff --git a/src/TrashLib/Sonarr/Api/SonarrApi.cs b/src/TrashLib/Sonarr/Api/SonarrApi.cs
index 643ab71c..d79d7c2e 100644
--- a/src/TrashLib/Sonarr/Api/SonarrApi.cs
+++ b/src/TrashLib/Sonarr/Api/SonarrApi.cs
@@ -3,6 +3,7 @@ using System.Collections.Generic;
using System.Threading.Tasks;
using Flurl;
using Flurl.Http;
+using TrashLib.Config;
using TrashLib.Sonarr.Api.Objects;
namespace TrashLib.Sonarr.Api
diff --git a/src/TrashLib/Sonarr/Config/ISonarrValidationMessages.cs b/src/TrashLib/Sonarr/Config/ISonarrValidationMessages.cs
new file mode 100644
index 00000000..d5308f53
--- /dev/null
+++ b/src/TrashLib/Sonarr/Config/ISonarrValidationMessages.cs
@@ -0,0 +1,9 @@
+namespace TrashLib.Sonarr.Config
+{
+ public interface ISonarrValidationMessages
+ {
+ string BaseUrl { get; }
+ string ApiKey { get; }
+ string ReleaseProfileType { get; }
+ }
+}
diff --git a/src/TrashLib/Sonarr/SonarrConfiguration.cs b/src/TrashLib/Sonarr/Config/SonarrConfiguration.cs
similarity index 68%
rename from src/TrashLib/Sonarr/SonarrConfiguration.cs
rename to src/TrashLib/Sonarr/Config/SonarrConfiguration.cs
index d023f91f..c966bb82 100644
--- a/src/TrashLib/Sonarr/SonarrConfiguration.cs
+++ b/src/TrashLib/Sonarr/Config/SonarrConfiguration.cs
@@ -1,33 +1,21 @@
using System.Collections.Generic;
-using System.ComponentModel.DataAnnotations;
-using JetBrains.Annotations;
using TrashLib.Config;
using TrashLib.Sonarr.QualityDefinition;
using TrashLib.Sonarr.ReleaseProfile;
-namespace TrashLib.Sonarr
+namespace TrashLib.Sonarr.Config
{
- [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
public class SonarrConfiguration : ServiceConfiguration
{
public IList ReleaseProfiles { get; set; } = new List();
public SonarrQualityDefinitionType? QualityDefinition { get; init; }
-
- public override bool IsValid(out string msg)
- {
- msg = "";
- return true;
- }
}
- [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
public class ReleaseProfileConfig
{
// -1 does not map to a valid enumerator. this is to force validation to fail if it is not set from YAML
// all of this craziness is to avoid making the enum type nullable which will make using the property
// frustrating.
- [EnumDataType(typeof(ReleaseProfileType),
- ErrorMessage = "'type' is required for 'release_profiles' elements")]
public ReleaseProfileType Type { get; init; } = (ReleaseProfileType) (-1);
public bool StrictNegativeScores { get; init; }
@@ -35,7 +23,6 @@ namespace TrashLib.Sonarr
public ICollection Tags { get; init; } = new List();
}
- [UsedImplicitly(ImplicitUseTargetFlags.WithMembers)]
public class SonarrProfileFilterConfig
{
public bool IncludeOptional { get; set; }
diff --git a/src/TrashLib/Sonarr/Config/SonarrConfigurationValidator.cs b/src/TrashLib/Sonarr/Config/SonarrConfigurationValidator.cs
new file mode 100644
index 00000000..662e408a
--- /dev/null
+++ b/src/TrashLib/Sonarr/Config/SonarrConfigurationValidator.cs
@@ -0,0 +1,27 @@
+using FluentValidation;
+using JetBrains.Annotations;
+
+namespace TrashLib.Sonarr.Config
+{
+ [UsedImplicitly]
+ internal class SonarrConfigurationValidator : AbstractValidator
+ {
+ public SonarrConfigurationValidator(
+ ISonarrValidationMessages messages,
+ IValidator releaseProfileConfigValidator)
+ {
+ RuleFor(x => x.BaseUrl).NotEmpty().WithMessage(messages.BaseUrl);
+ RuleFor(x => x.ApiKey).NotEmpty().WithMessage(messages.ApiKey);
+ RuleForEach(x => x.ReleaseProfiles).SetValidator(releaseProfileConfigValidator);
+ }
+ }
+
+ [UsedImplicitly]
+ internal class ReleaseProfileConfigValidator : AbstractValidator
+ {
+ public ReleaseProfileConfigValidator(ISonarrValidationMessages messages)
+ {
+ RuleFor(x => x.Type).IsInEnum().WithMessage(messages.ReleaseProfileType);
+ }
+ }
+}
diff --git a/src/TrashLib/Sonarr/Config/SonarrValidationMessages.cs b/src/TrashLib/Sonarr/Config/SonarrValidationMessages.cs
new file mode 100644
index 00000000..1fd75e1f
--- /dev/null
+++ b/src/TrashLib/Sonarr/Config/SonarrValidationMessages.cs
@@ -0,0 +1,17 @@
+using JetBrains.Annotations;
+
+namespace TrashLib.Sonarr.Config
+{
+ [UsedImplicitly]
+ internal class SonarrValidationMessages : ISonarrValidationMessages
+ {
+ public string BaseUrl =>
+ "Property 'base_url' is required";
+
+ public string ApiKey =>
+ "Property 'api_key' is required";
+
+ public string ReleaseProfileType =>
+ "'type' is required for 'release_profiles' elements";
+ }
+}
diff --git a/src/TrashLib/Sonarr/QualityDefinition/ISonarrQualityDefinitionUpdater.cs b/src/TrashLib/Sonarr/QualityDefinition/ISonarrQualityDefinitionUpdater.cs
index e33f7cef..cf9ed1d4 100644
--- a/src/TrashLib/Sonarr/QualityDefinition/ISonarrQualityDefinitionUpdater.cs
+++ b/src/TrashLib/Sonarr/QualityDefinition/ISonarrQualityDefinitionUpdater.cs
@@ -1,4 +1,5 @@
using System.Threading.Tasks;
+using TrashLib.Sonarr.Config;
namespace TrashLib.Sonarr.QualityDefinition
{
diff --git a/src/TrashLib/Sonarr/QualityDefinition/SonarrQualityDefinitionUpdater.cs b/src/TrashLib/Sonarr/QualityDefinition/SonarrQualityDefinitionUpdater.cs
index 4ba1822b..c619267d 100644
--- a/src/TrashLib/Sonarr/QualityDefinition/SonarrQualityDefinitionUpdater.cs
+++ b/src/TrashLib/Sonarr/QualityDefinition/SonarrQualityDefinitionUpdater.cs
@@ -6,6 +6,7 @@ using System.Threading.Tasks;
using Serilog;
using TrashLib.Sonarr.Api;
using TrashLib.Sonarr.Api.Objects;
+using TrashLib.Sonarr.Config;
namespace TrashLib.Sonarr.QualityDefinition
{
diff --git a/src/TrashLib/Sonarr/ReleaseProfile/FilteredProfileData.cs b/src/TrashLib/Sonarr/ReleaseProfile/FilteredProfileData.cs
index f57c72e9..7738bb61 100644
--- a/src/TrashLib/Sonarr/ReleaseProfile/FilteredProfileData.cs
+++ b/src/TrashLib/Sonarr/ReleaseProfile/FilteredProfileData.cs
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Linq;
+using TrashLib.Sonarr.Config;
namespace TrashLib.Sonarr.ReleaseProfile
{
diff --git a/src/TrashLib/Sonarr/ReleaseProfile/IReleaseProfileGuideParser.cs b/src/TrashLib/Sonarr/ReleaseProfile/IReleaseProfileGuideParser.cs
index e6df1faf..8b806013 100644
--- a/src/TrashLib/Sonarr/ReleaseProfile/IReleaseProfileGuideParser.cs
+++ b/src/TrashLib/Sonarr/ReleaseProfile/IReleaseProfileGuideParser.cs
@@ -1,5 +1,6 @@
using System.Collections.Generic;
using System.Threading.Tasks;
+using TrashLib.Sonarr.Config;
namespace TrashLib.Sonarr.ReleaseProfile
{
diff --git a/src/TrashLib/Sonarr/ReleaseProfile/IReleaseProfileUpdater.cs b/src/TrashLib/Sonarr/ReleaseProfile/IReleaseProfileUpdater.cs
index 3e9cd360..01fb05d9 100644
--- a/src/TrashLib/Sonarr/ReleaseProfile/IReleaseProfileUpdater.cs
+++ b/src/TrashLib/Sonarr/ReleaseProfile/IReleaseProfileUpdater.cs
@@ -1,4 +1,5 @@
using System.Threading.Tasks;
+using TrashLib.Sonarr.Config;
namespace TrashLib.Sonarr.ReleaseProfile
{
diff --git a/src/TrashLib/Sonarr/ReleaseProfile/ReleaseProfileGuideParser.cs b/src/TrashLib/Sonarr/ReleaseProfile/ReleaseProfileGuideParser.cs
index aed506e8..d8cbbe16 100644
--- a/src/TrashLib/Sonarr/ReleaseProfile/ReleaseProfileGuideParser.cs
+++ b/src/TrashLib/Sonarr/ReleaseProfile/ReleaseProfileGuideParser.cs
@@ -7,6 +7,7 @@ using Common.Extensions;
using Flurl;
using Flurl.Http;
using Serilog;
+using TrashLib.Sonarr.Config;
namespace TrashLib.Sonarr.ReleaseProfile
{
diff --git a/src/TrashLib/Sonarr/ReleaseProfile/ReleaseProfileUpdater.cs b/src/TrashLib/Sonarr/ReleaseProfile/ReleaseProfileUpdater.cs
index b25a6b71..74933520 100644
--- a/src/TrashLib/Sonarr/ReleaseProfile/ReleaseProfileUpdater.cs
+++ b/src/TrashLib/Sonarr/ReleaseProfile/ReleaseProfileUpdater.cs
@@ -7,6 +7,7 @@ using Serilog;
using TrashLib.ExceptionTypes;
using TrashLib.Sonarr.Api;
using TrashLib.Sonarr.Api.Objects;
+using TrashLib.Sonarr.Config;
namespace TrashLib.Sonarr.ReleaseProfile
{
diff --git a/src/TrashLib/Sonarr/SonarrAutofacModule.cs b/src/TrashLib/Sonarr/SonarrAutofacModule.cs
index 1a745735..6126058f 100644
--- a/src/TrashLib/Sonarr/SonarrAutofacModule.cs
+++ b/src/TrashLib/Sonarr/SonarrAutofacModule.cs
@@ -1,5 +1,6 @@
using Autofac;
using TrashLib.Sonarr.Api;
+using TrashLib.Sonarr.Config;
using TrashLib.Sonarr.QualityDefinition;
using TrashLib.Sonarr.ReleaseProfile;
@@ -10,6 +11,7 @@ namespace TrashLib.Sonarr
protected override void Load(ContainerBuilder builder)
{
builder.RegisterType().As();
+ builder.RegisterType().As();
// Release Profile Support
builder.RegisterType().As();
diff --git a/src/TrashLib/TrashLib.csproj b/src/TrashLib/TrashLib.csproj
index eef0e7fe..8730575b 100644
--- a/src/TrashLib/TrashLib.csproj
+++ b/src/TrashLib/TrashLib.csproj
@@ -1,13 +1,14 @@
-
-
+
+
-
+
+
diff --git a/src/TrashUpdater.sln b/src/TrashUpdater.sln
index ccdee671..837bb1e1 100644
--- a/src/TrashUpdater.sln
+++ b/src/TrashUpdater.sln
@@ -19,7 +19,7 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common", "Common\Common.csp
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Common.Tests", "Common.Tests\Common.Tests.csproj", "{0720939D-1CA6-43D7-BBED-F8F894C4F562}"
EndProject
-Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Trash.TestLibrary", "Trash.TestLibrary\Trash.TestLibrary.csproj", "{33226068-65E3-4890-8671-59A56BA3F6F0}"
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrashLib.TestLibrary", "TrashLib.TestLibrary\TrashLib.TestLibrary.csproj", "{33226068-65E3-4890-8671-59A56BA3F6F0}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TrashLib", "TrashLib\TrashLib.csproj", "{4F6ACBA6-9A7D-487C-ACC1-787CCC90A381}"
EndProject