diff --git a/src/Recyclarr.Cli.Tests/Console/Helpers/CacheStoragePathTest.cs b/src/Recyclarr.Cli.Tests/Console/Helpers/CacheStoragePathTest.cs index e2f539b1..79770c13 100644 --- a/src/Recyclarr.Cli.Tests/Console/Helpers/CacheStoragePathTest.cs +++ b/src/Recyclarr.Cli.Tests/Console/Helpers/CacheStoragePathTest.cs @@ -16,7 +16,7 @@ public class CacheStoragePathTest : IntegrationFixture public void Use_guid_when_no_name() { var config = Substitute.ForPartsOf(); - config.BaseUrl = "something"; + config.BaseUrl = new Uri("http://something"); config.InstanceName = null; using var scope = Container.BeginLifetimeScope(builder => @@ -34,7 +34,7 @@ public class CacheStoragePathTest : IntegrationFixture public void Use_name_when_not_null() { var config = Substitute.ForPartsOf(); - config.BaseUrl = "something"; + config.BaseUrl = new Uri("http://something"); config.InstanceName = "thename"; using var scope = Container.BeginLifetimeScope(builder => diff --git a/src/Recyclarr.Cli/Console/Helpers/CacheStoragePath.cs b/src/Recyclarr.Cli/Console/Helpers/CacheStoragePath.cs index 29e661ec..d89ad8b7 100644 --- a/src/Recyclarr.Cli/Console/Helpers/CacheStoragePath.cs +++ b/src/Recyclarr.Cli/Console/Helpers/CacheStoragePath.cs @@ -33,7 +33,8 @@ public class CacheStoragePath : ICacheStoragePath dirName.Append($"{_config.InstanceName}_"); } - var guid = _hash.ComputeHash(Encoding.ASCII.GetBytes(_config.BaseUrl)).AsHexString(); + var url = _config.BaseUrl.OriginalString; + var guid = _hash.ComputeHash(Encoding.ASCII.GetBytes(url)).AsHexString(); dirName.Append(guid); return dirName.ToString(); } diff --git a/src/Recyclarr.TrashLib.Tests/Config/Parsing/ConfigurationLoaderEnvVarTest.cs b/src/Recyclarr.TrashLib.Tests/Config/Parsing/ConfigurationLoaderEnvVarTest.cs index 924891c5..856a6175 100644 --- a/src/Recyclarr.TrashLib.Tests/Config/Parsing/ConfigurationLoaderEnvVarTest.cs +++ b/src/Recyclarr.TrashLib.Tests/Config/Parsing/ConfigurationLoaderEnvVarTest.cs @@ -20,7 +20,7 @@ public class ConfigurationLoaderEnvVarTest : IntegrationFixture { var env = Resolve(); env.GetEnvironmentVariable("SONARR_API_KEY").Returns("the_api_key"); - env.GetEnvironmentVariable("SONARR_URL").Returns("the_url"); + env.GetEnvironmentVariable("SONARR_URL").Returns("http://the_url"); var sut = Resolve(); @@ -38,7 +38,7 @@ sonarr: { new { - BaseUrl = "the_url", + BaseUrl = new Uri("http://the_url"), ApiKey = "the_api_key" } }); @@ -63,7 +63,7 @@ sonarr: { new { - BaseUrl = "http://sonarr:1233" + BaseUrl = new Uri("http://sonarr:1233") } }); } @@ -79,7 +79,7 @@ sonarr: const string testYml = @" sonarr: instance: - base_url: !env_var SONARR_URL some value + base_url: !env_var SONARR_URL http://somevalue api_key: value "; @@ -90,7 +90,7 @@ sonarr: { new { - BaseUrl = "some value" + BaseUrl = new Uri("http://somevalue") } }); } @@ -106,7 +106,7 @@ sonarr: const string testYml = @" sonarr: instance: - base_url: !env_var SONARR_URL ""the url"" + base_url: !env_var SONARR_URL ""http://theurl"" api_key: !env_var SONARR_API 'the key' "; @@ -117,7 +117,7 @@ sonarr: { new { - BaseUrl = "the url", + BaseUrl = new Uri("http://theurl"), ApiKey = "the key" } }); @@ -131,7 +131,7 @@ sonarr: const string testYml = @" sonarr: instance: - base_url: !env_var SONARR_URL some value + base_url: !env_var SONARR_URL http://somevalue api_key: value "; @@ -142,7 +142,7 @@ sonarr: { new { - BaseUrl = "some value" + BaseUrl = new Uri("http://somevalue") } }); } @@ -155,7 +155,7 @@ sonarr: const string testYml = $@" sonarr: instance: - base_url: !env_var SONARR_URL {"\t"}some value + base_url: !env_var SONARR_URL {"\t"}http://somevalue api_key: value "; @@ -166,7 +166,7 @@ sonarr: { new { - BaseUrl = "some value" + BaseUrl = new Uri("http://somevalue") } }); } diff --git a/src/Recyclarr.TrashLib.Tests/Config/Parsing/ConfigurationLoaderSecretsTest.cs b/src/Recyclarr.TrashLib.Tests/Config/Parsing/ConfigurationLoaderSecretsTest.cs index 4a77b524..e30e7898 100644 --- a/src/Recyclarr.TrashLib.Tests/Config/Parsing/ConfigurationLoaderSecretsTest.cs +++ b/src/Recyclarr.TrashLib.Tests/Config/Parsing/ConfigurationLoaderSecretsTest.cs @@ -43,7 +43,7 @@ secret_rp: 1234567 { InstanceName = "instance1", ApiKey = "95283e6b156c42f3af8a9b16173f876b", - BaseUrl = "https://radarr:7878", + BaseUrl = new Uri("https://radarr:7878"), ReleaseProfiles = new List { new() diff --git a/src/Recyclarr.TrashLib.Tests/Config/Parsing/ConfigurationLoaderTest.cs b/src/Recyclarr.TrashLib.Tests/Config/Parsing/ConfigurationLoaderTest.cs index 71c98ba0..febd82c9 100644 --- a/src/Recyclarr.TrashLib.Tests/Config/Parsing/ConfigurationLoaderTest.cs +++ b/src/Recyclarr.TrashLib.Tests/Config/Parsing/ConfigurationLoaderTest.cs @@ -44,7 +44,7 @@ public class ConfigurationLoaderTest : IntegrationFixture var str = new StringBuilder($"{sectionName}:"); const string templateYaml = @" instance{1}: - base_url: {0} + base_url: http://{0} api_key: abc"; var counter = 0; @@ -55,10 +55,10 @@ public class ConfigurationLoaderTest : IntegrationFixture var baseDir = Fs.CurrentDirectory(); var fileData = new[] { - (baseDir.File("config1.yml"), MockYaml("sonarr", 1, 2)), - (baseDir.File("config2.yml"), MockYaml("sonarr", 3)), + (baseDir.File("config1.yml"), MockYaml("sonarr", "one", "two")), + (baseDir.File("config2.yml"), MockYaml("sonarr", "three")), (baseDir.File("config3.yml"), "bad yaml"), - (baseDir.File("config4.yml"), MockYaml("radarr", 4)) + (baseDir.File("config4.yml"), MockYaml("radarr", "four")) }; foreach (var (file, data) in fileData) @@ -68,14 +68,14 @@ public class ConfigurationLoaderTest : IntegrationFixture var expectedSonarr = new[] { - new {ApiKey = "abc", BaseUrl = "1"}, - new {ApiKey = "abc", BaseUrl = "2"}, - new {ApiKey = "abc", BaseUrl = "3"} + new {ApiKey = "abc", BaseUrl = new Uri("http://one")}, + new {ApiKey = "abc", BaseUrl = new Uri("http://two")}, + new {ApiKey = "abc", BaseUrl = new Uri("http://three")} }; var expectedRadarr = new[] { - new {ApiKey = "abc", BaseUrl = "4"} + new {ApiKey = "abc", BaseUrl = new Uri("http://four")} }; var loader = Resolve(); @@ -100,7 +100,7 @@ public class ConfigurationLoaderTest : IntegrationFixture new() { ApiKey = "95283e6b156c42f3af8a9b16173f876b", - BaseUrl = "http://localhost:8989", + BaseUrl = new Uri("http://localhost:8989"), InstanceName = "name", ReleaseProfiles = new List { diff --git a/src/Recyclarr.TrashLib.Tests/Config/ServiceConfigurationValidatorTest.cs b/src/Recyclarr.TrashLib.Tests/Config/ServiceConfigurationValidatorTest.cs index 4a656937..36293d4a 100644 --- a/src/Recyclarr.TrashLib.Tests/Config/ServiceConfigurationValidatorTest.cs +++ b/src/Recyclarr.TrashLib.Tests/Config/ServiceConfigurationValidatorTest.cs @@ -16,7 +16,7 @@ public class ServiceConfigurationValidatorTest : IntegrationFixture var config = new TestConfig { ApiKey = "valid", - BaseUrl = "valid", + BaseUrl = new Uri("http://valid"), InstanceName = "valid", LineNumber = 1, CustomFormats = new List @@ -51,7 +51,7 @@ public class ServiceConfigurationValidatorTest : IntegrationFixture var config = new TestConfig { ApiKey = "", // Must not be empty - BaseUrl = "valid", + BaseUrl = new Uri("http://valid"), CustomFormats = new List { new() @@ -84,7 +84,7 @@ public class ServiceConfigurationValidatorTest : IntegrationFixture var config = new TestConfig { ApiKey = "valid", - BaseUrl = "", + BaseUrl = new Uri("about:empty"), CustomFormats = new List { new() @@ -119,7 +119,7 @@ public class ServiceConfigurationValidatorTest : IntegrationFixture var config = new TestConfig { ApiKey = "valid", - BaseUrl = "valid", + BaseUrl = new Uri("http://valid"), CustomFormats = new List { new() @@ -152,7 +152,7 @@ public class ServiceConfigurationValidatorTest : IntegrationFixture var config = new TestConfig { ApiKey = "valid", - BaseUrl = "valid", + BaseUrl = new Uri("http://valid"), CustomFormats = new List { new() @@ -185,7 +185,7 @@ public class ServiceConfigurationValidatorTest : IntegrationFixture var config = new TestConfig { ApiKey = "valid", - BaseUrl = "valid", + BaseUrl = new Uri("http://valid"), CustomFormats = new List { new() diff --git a/src/Recyclarr.TrashLib/Config/Services/IServiceConfiguration.cs b/src/Recyclarr.TrashLib/Config/Services/IServiceConfiguration.cs index 265bb9b9..05de2005 100644 --- a/src/Recyclarr.TrashLib/Config/Services/IServiceConfiguration.cs +++ b/src/Recyclarr.TrashLib/Config/Services/IServiceConfiguration.cs @@ -1,18 +1,10 @@ -using System.Diagnostics.CodeAnalysis; - namespace Recyclarr.TrashLib.Config.Services; public interface IServiceConfiguration { string ServiceName { get; } - string? InstanceName { get; } - - [SuppressMessage("Design", "CA1056:URI-like properties should not be strings", - Justification = "This is not treated as a true URI until later")] - string BaseUrl { get; } - + Uri BaseUrl { get; } string ApiKey { get; } - bool DeleteOldCustomFormats { get; } } diff --git a/src/Recyclarr.TrashLib/Config/Services/ServiceConfiguration.cs b/src/Recyclarr.TrashLib/Config/Services/ServiceConfiguration.cs index 5ea1d6b7..90b63aa4 100644 --- a/src/Recyclarr.TrashLib/Config/Services/ServiceConfiguration.cs +++ b/src/Recyclarr.TrashLib/Config/Services/ServiceConfiguration.cs @@ -15,7 +15,7 @@ public abstract class ServiceConfiguration : IServiceConfiguration [YamlIgnore] public int LineNumber { get; set; } - public string BaseUrl { get; set; } = ""; + public Uri BaseUrl { get; set; } = new("about:empty"); public string ApiKey { get; set; } = ""; public ICollection CustomFormats { get; init; } = diff --git a/src/Recyclarr.TrashLib/Config/Services/ServiceConfigurationValidator.cs b/src/Recyclarr.TrashLib/Config/Services/ServiceConfigurationValidator.cs index 317e70e7..ac06ed81 100644 --- a/src/Recyclarr.TrashLib/Config/Services/ServiceConfigurationValidator.cs +++ b/src/Recyclarr.TrashLib/Config/Services/ServiceConfigurationValidator.cs @@ -16,7 +16,8 @@ internal class ServiceConfigurationValidator : AbstractValidator x.InstanceName).NotEmpty(); RuleFor(x => x.ServiceName).NotEmpty(); RuleFor(x => x.LineNumber).NotEqual(0); - RuleFor(x => x.BaseUrl).NotEmpty().WithMessage("Property 'base_url' is required"); + RuleFor(x => x.BaseUrl).Must(x => x.Scheme is "http" or "https") + .WithMessage("Property 'base_url' is required and must be a valid URL"); RuleFor(x => x.ApiKey).NotEmpty().WithMessage("Property 'api_key' is required"); RuleForEach(x => x.CustomFormats).SetValidator(new CustomFormatConfigValidator()); RuleFor(x => x.QualityDefinition).SetNonNullableValidator(new QualityDefinitionConfigValidator()); diff --git a/src/Recyclarr.TrashLib/Http/FlurlClientFactory.cs b/src/Recyclarr.TrashLib/Http/FlurlClientFactory.cs index 6bb2aa7b..07342205 100644 --- a/src/Recyclarr.TrashLib/Http/FlurlClientFactory.cs +++ b/src/Recyclarr.TrashLib/Http/FlurlClientFactory.cs @@ -20,7 +20,7 @@ public class FlurlClientFactory : IFlurlClientFactory _factory = new PerBaseUrlFlurlClientFactory(); } - public IFlurlClient Get(string baseUrl) + public IFlurlClient BuildClient(Uri baseUrl) { var client = _factory.Get(baseUrl); client.Settings = GetClientSettings(); diff --git a/src/Recyclarr.TrashLib/Http/IFlurlClientFactory.cs b/src/Recyclarr.TrashLib/Http/IFlurlClientFactory.cs index 38b892e6..2eb3a33e 100644 --- a/src/Recyclarr.TrashLib/Http/IFlurlClientFactory.cs +++ b/src/Recyclarr.TrashLib/Http/IFlurlClientFactory.cs @@ -4,5 +4,5 @@ namespace Recyclarr.TrashLib.Http; public interface IFlurlClientFactory { - IFlurlClient Get(string baseUrl); + IFlurlClient BuildClient(Uri baseUrl); } diff --git a/src/Recyclarr.TrashLib/Http/ServiceRequestBuilder.cs b/src/Recyclarr.TrashLib/Http/ServiceRequestBuilder.cs index 20b2df95..2fdad51f 100644 --- a/src/Recyclarr.TrashLib/Http/ServiceRequestBuilder.cs +++ b/src/Recyclarr.TrashLib/Http/ServiceRequestBuilder.cs @@ -16,7 +16,7 @@ public class ServiceRequestBuilder : IServiceRequestBuilder public IFlurlRequest Request(params object[] path) { - var client = _clientFactory.Get(_config.BaseUrl); + var client = _clientFactory.BuildClient(_config.BaseUrl); return client.Request(new[] {"api", "v3"}.Concat(path).ToArray()) .SetQueryParams(new {apikey = _config.ApiKey}); }