diff --git a/src/Exceptionless.Core/Configuration/MessageBusOptions.cs b/src/Exceptionless.Core/Configuration/MessageBusOptions.cs index a0ad599a87..4e78a98f63 100644 --- a/src/Exceptionless.Core/Configuration/MessageBusOptions.cs +++ b/src/Exceptionless.Core/Configuration/MessageBusOptions.cs @@ -24,15 +24,37 @@ public static MessageBusOptions ReadFromConfiguration(IConfiguration config, App if (cs != null) { - options.Data = cs.ParseConnectionString(); - options.Provider = options.Data.GetString(nameof(options.Provider)); - string? providerConnectionString = !String.IsNullOrEmpty(options.Provider) ? config.GetConnectionString(options.Provider) : null; + if (TryGetRawRabbitMqConnectionString(cs, out var connectionString)) + { + options.Provider = "rabbitmq"; + options.ConnectionString = connectionString; + options.Data = new Dictionary(StringComparer.OrdinalIgnoreCase) + { + [nameof(options.Provider)] = options.Provider + }; + } + else + { + options.Data = cs.ParseConnectionString(); + options.Provider = options.Data.GetString(nameof(options.Provider)); + } - var providerOptions = providerConnectionString.ParseConnectionString(defaultKey: "server"); - options.Data ??= new Dictionary(StringComparer.OrdinalIgnoreCase); - options.Data.AddRange(providerOptions); + if (String.IsNullOrEmpty(options.ConnectionString)) + { + string? providerConnectionString = !String.IsNullOrEmpty(options.Provider) ? config.GetConnectionString(options.Provider) : null; + if (String.Equals(options.Provider, "rabbitmq", StringComparison.OrdinalIgnoreCase) && !String.IsNullOrWhiteSpace(providerConnectionString)) + { + options.ConnectionString = TrimMatchingQuotes(providerConnectionString.Trim()); + } + else + { + var providerOptions = providerConnectionString.ParseConnectionString(defaultKey: "server"); + options.Data ??= new Dictionary(StringComparer.OrdinalIgnoreCase); + options.Data.AddRange(providerOptions); - options.ConnectionString = options.Data.BuildConnectionString(new HashSet { nameof(options.Provider) }); + options.ConnectionString = options.Data.BuildConnectionString(new HashSet { nameof(options.Provider) }); + } + } } else { @@ -47,4 +69,32 @@ public static MessageBusOptions ReadFromConfiguration(IConfiguration config, App return options; } + + private static bool TryGetRawRabbitMqConnectionString(string connectionString, out string? rawConnectionString) + { + rawConnectionString = null; + + const string providerPrefix = "provider="; + if (!connectionString.StartsWith(providerPrefix, StringComparison.OrdinalIgnoreCase)) + return false; + + int separatorIndex = connectionString.IndexOf(';'); + if (separatorIndex <= providerPrefix.Length) + return false; + + string provider = connectionString.Substring(providerPrefix.Length, separatorIndex - providerPrefix.Length).Trim(); + if (!String.Equals(provider, "rabbitmq", StringComparison.OrdinalIgnoreCase)) + return false; + + rawConnectionString = TrimMatchingQuotes(connectionString[(separatorIndex + 1)..].Trim()); + return !String.IsNullOrEmpty(rawConnectionString); + } + + private static string TrimMatchingQuotes(string value) + { + if (value.Length >= 2 && ((value[0] == '"' && value[^1] == '"') || (value[0] == '\'' && value[^1] == '\''))) + return value[1..^1]; + + return value; + } } diff --git a/src/Exceptionless.Job/appsettings.Development.yml b/src/Exceptionless.Job/appsettings.Development.yml index 7b7a1b7601..8d67aefcb9 100644 --- a/src/Exceptionless.Job/appsettings.Development.yml +++ b/src/Exceptionless.Job/appsettings.Development.yml @@ -4,6 +4,9 @@ ConnectionStrings: # Elasticsearch: server=https://elastic:elastic@localhost:9200 # Cache: provider=redis; # MessageBus: provider=redis; +# # RabbitMQ examples: +# # MessageBus: 'provider=rabbitmq;amqp://localhost/%2F' +# # rabbitmq: 'amqp://localhost/%2F' # Queue: provider=redis; # Storage: provider=folder;path=..\Exceptionless.Web\storage Email: smtp://localhost:1025 diff --git a/src/Exceptionless.Job/appsettings.Production.yml b/src/Exceptionless.Job/appsettings.Production.yml index b01a5221e1..4ed301f2a7 100644 --- a/src/Exceptionless.Job/appsettings.Production.yml +++ b/src/Exceptionless.Job/appsettings.Production.yml @@ -3,6 +3,9 @@ ConnectionStrings: # Elasticsearch: server=http://localhost:9200 # Cache: provider=redis;server="localhost,abortConnect=false" # MessageBus: provider=redis;server="localhost,abortConnect=false" +# # RabbitMQ examples: +# # MessageBus: 'provider=rabbitmq;amqp://localhost/%2F' +# # rabbitmq: 'amqp://localhost/%2F' # Queue: provider=redis;server="localhost,abortConnect=false" # Storage: '' # Email: 'smtps://user:password@domain.com:587' diff --git a/src/Exceptionless.Job/appsettings.Staging.yml b/src/Exceptionless.Job/appsettings.Staging.yml index db415b6211..93a54217e4 100644 --- a/src/Exceptionless.Job/appsettings.Staging.yml +++ b/src/Exceptionless.Job/appsettings.Staging.yml @@ -4,6 +4,9 @@ ConnectionStrings: # Elasticsearch: server=http://localhost:9200;replicas=0 # Cache: provider=redis; # MessageBus: provider=redis; +# # RabbitMQ examples: +# # MessageBus: 'provider=rabbitmq;amqp://localhost/%2F' +# # rabbitmq: 'amqp://localhost/%2F' # Queue: provider=redis; # Storage: provider=folder;path=.\storage= OAuth: FacebookId=395178683904310;GitHubId=7ef1dd5bfbc4ccf7f5ef;GoogleId=809763155066-enkkdmt4ierc33q9cft9nf5d5c02h30q.apps.googleusercontent.com;MicrosoftId=000000004C137E8B;SlackId=34500115540.177239122322; diff --git a/src/Exceptionless.Web/appsettings.Development.yml b/src/Exceptionless.Web/appsettings.Development.yml index b2a91a34bd..14c0664c01 100644 --- a/src/Exceptionless.Web/appsettings.Development.yml +++ b/src/Exceptionless.Web/appsettings.Development.yml @@ -4,6 +4,9 @@ ConnectionStrings: # Elasticsearch: server=https://elastic:elastic@localhost:9200 # Cache: provider=redis; # MessageBus: provider=redis; +# # RabbitMQ examples: +# # MessageBus: 'provider=rabbitmq;amqp://localhost/%2F' +# # rabbitmq: 'amqp://localhost/%2F' # Queue: provider=redis; # Storage: provider=folder;path=.\storage # LDAP: '' diff --git a/src/Exceptionless.Web/appsettings.Production.yml b/src/Exceptionless.Web/appsettings.Production.yml index a6bbd82962..80dde33957 100644 --- a/src/Exceptionless.Web/appsettings.Production.yml +++ b/src/Exceptionless.Web/appsettings.Production.yml @@ -3,6 +3,9 @@ ConnectionStrings: # Elasticsearch: server=http://localhost:9200 # Cache: provider=redis;server="localhost,abortConnect=false" # MessageBus: provider=redis;server="localhost,abortConnect=false" +# # RabbitMQ examples: +# # MessageBus: 'provider=rabbitmq;amqp://localhost/%2F' +# # rabbitmq: 'amqp://localhost/%2F' # Queue: provider=redis;server="localhost,abortConnect=false" # Storage: '' # Email: 'smtps://user:password@domain.com:587' diff --git a/src/Exceptionless.Web/appsettings.Staging.yml b/src/Exceptionless.Web/appsettings.Staging.yml index 9d97c7b5a4..9c083efb65 100644 --- a/src/Exceptionless.Web/appsettings.Staging.yml +++ b/src/Exceptionless.Web/appsettings.Staging.yml @@ -4,6 +4,9 @@ ConnectionStrings: # Elasticsearch: server=http://localhost:9200;replicas=0 # Cache: provider=redis; # MessageBus: provider=redis; +# # RabbitMQ examples: +# # MessageBus: 'provider=rabbitmq;amqp://localhost/%2F' +# # rabbitmq: 'amqp://localhost/%2F' # Queue: provider=redis; # Storage: provider=folder;path=.\storage= OAuth: FacebookId=395178683904310;GitHubId=7ef1dd5bfbc4ccf7f5ef;GoogleId=809763155066-enkkdmt4ierc33q9cft9nf5d5c02h30q.apps.googleusercontent.com;MicrosoftId=000000004C137E8B;SlackId=34500115540.177239122322; diff --git a/tests/Exceptionless.Tests/Configuration/MessageBusOptionsTests.cs b/tests/Exceptionless.Tests/Configuration/MessageBusOptionsTests.cs new file mode 100644 index 0000000000..15b9a8583f --- /dev/null +++ b/tests/Exceptionless.Tests/Configuration/MessageBusOptionsTests.cs @@ -0,0 +1,73 @@ +using Exceptionless.Core; +using Microsoft.Extensions.Configuration; +using Xunit; + +namespace Exceptionless.Tests.Configuration; + +public class MessageBusOptionsTests +{ + [Fact] + public void ReadFromConfiguration_WithInlineRabbitMqUri_PreservesRawConnectionString() + { + // Arrange + const string rabbitMqConnectionString = "amqp://localhost/%2F"; + var configuration = CreateConfiguration(new Dictionary + { + ["BaseURL"] = "http://localhost:7110/#!", + ["ConnectionStrings:MessageBus"] = $"provider=rabbitmq;{rabbitMqConnectionString}" + }); + + // Act + var options = AppOptions.ReadFromConfiguration(configuration); + + // Assert + Assert.Equal("rabbitmq", options.MessageBusOptions.Provider); + Assert.Equal(rabbitMqConnectionString, options.MessageBusOptions.ConnectionString); + } + + [Fact] + public void ReadFromConfiguration_WithQuotedRabbitMqUri_PreservesRawConnectionString() + { + // Arrange + const string rabbitMqConnectionString = "amqp://localhost/%2F"; + var configuration = CreateConfiguration(new Dictionary + { + ["BaseURL"] = "http://localhost:7110/#!", + ["ConnectionStrings:MessageBus"] = $"provider=rabbitmq;\"{rabbitMqConnectionString}\"" + }); + + // Act + var options = AppOptions.ReadFromConfiguration(configuration); + + // Assert + Assert.Equal("rabbitmq", options.MessageBusOptions.Provider); + Assert.Equal(rabbitMqConnectionString, options.MessageBusOptions.ConnectionString); + } + + [Fact] + public void ReadFromConfiguration_WithRabbitMqProviderConnectionString_PreservesRawConnectionString() + { + // Arrange + const string rabbitMqConnectionString = "amqp://localhost/%2F"; + var configuration = CreateConfiguration(new Dictionary + { + ["BaseURL"] = "http://localhost:7110/#!", + ["ConnectionStrings:MessageBus"] = "provider=rabbitmq", + ["ConnectionStrings:rabbitmq"] = $"\"{rabbitMqConnectionString}\"" + }); + + // Act + var options = AppOptions.ReadFromConfiguration(configuration); + + // Assert + Assert.Equal("rabbitmq", options.MessageBusOptions.Provider); + Assert.Equal(rabbitMqConnectionString, options.MessageBusOptions.ConnectionString); + } + + private static IConfiguration CreateConfiguration(Dictionary values) + { + return new ConfigurationBuilder() + .AddInMemoryCollection(values) + .Build(); + } +} diff --git a/tests/Exceptionless.Tests/appsettings.yml b/tests/Exceptionless.Tests/appsettings.yml index 5bdb00cb6a..d7896b5ea2 100644 --- a/tests/Exceptionless.Tests/appsettings.yml +++ b/tests/Exceptionless.Tests/appsettings.yml @@ -4,6 +4,9 @@ ConnectionStrings: # Elasticsearch: server=https://elastic:elastic@localhost:9200 # Cache: provider=redis; # MessageBus: provider=redis; + # # RabbitMQ examples: + # # MessageBus: 'provider=rabbitmq;amqp://localhost/%2F' + # # rabbitmq: 'amqp://localhost/%2F' # Queue: provider=redis; Storage: provider=folder;path=..\..\..\..\..\src\Exceptionless.Web\storage