From cc7b0cd0423b8b3911aa105121d22ccb94a341b7 Mon Sep 17 00:00:00 2001 From: Mark van Renswoude Date: Sun, 12 Feb 2017 14:42:49 +0100 Subject: [PATCH] Fixed #8: Account for all-capital abbreviations in routing key strategy --- Tapet.Tests/Properties/AssemblyInfo.cs | 23 ++++++ Tapet.Tests/Tapet.Tests.csproj | 80 +++++++++++++++++++ .../TypeNameRoutingKeyStrategyTests.cs | 72 +++++++++++++++++ Tapet.Tests/packages.config | 9 +++ Tapeti.sln | 6 ++ Tapeti/Default/TypeNameRoutingKeyStrategy.cs | 57 ++++++------- 6 files changed, 219 insertions(+), 28 deletions(-) create mode 100644 Tapet.Tests/Properties/AssemblyInfo.cs create mode 100644 Tapet.Tests/Tapet.Tests.csproj create mode 100644 Tapet.Tests/TypeNameRoutingKeyStrategyTests.cs create mode 100644 Tapet.Tests/packages.config diff --git a/Tapet.Tests/Properties/AssemblyInfo.cs b/Tapet.Tests/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..fed8cc8 --- /dev/null +++ b/Tapet.Tests/Properties/AssemblyInfo.cs @@ -0,0 +1,23 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Tapet.Tests")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Tapet.Tests")] +[assembly: AssemblyCopyright("")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("ba6bc046-5e60-410a-ae84-be1d91561a96")] diff --git a/Tapet.Tests/Tapet.Tests.csproj b/Tapet.Tests/Tapet.Tests.csproj new file mode 100644 index 0000000..4cbecb5 --- /dev/null +++ b/Tapet.Tests/Tapet.Tests.csproj @@ -0,0 +1,80 @@ + + + + + Debug + AnyCPU + {BA6BC046-5E60-410A-AE84-BE1D91561A96} + Library + Properties + Tapet.Tests + Tapet.Tests + v4.6.1 + 512 + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + + + + + + + + + + + + ..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll + True + + + ..\packages\xunit.assert.2.1.0\lib\dotnet\xunit.assert.dll + True + + + ..\packages\xunit.extensibility.core.2.1.0\lib\dotnet\xunit.core.dll + True + + + ..\packages\xunit.extensibility.execution.2.1.0\lib\net45\xunit.execution.desktop.dll + True + + + + + + + + + + + + {8ab4fd33-4aaa-465c-8579-9db3f3b23813} + Tapeti + + + + + \ No newline at end of file diff --git a/Tapet.Tests/TypeNameRoutingKeyStrategyTests.cs b/Tapet.Tests/TypeNameRoutingKeyStrategyTests.cs new file mode 100644 index 0000000..1638d47 --- /dev/null +++ b/Tapet.Tests/TypeNameRoutingKeyStrategyTests.cs @@ -0,0 +1,72 @@ +using System; +using Tapeti.Default; +using Xunit; + +namespace Tapet.Tests +{ + // ReSharper disable InconsistentNaming + public class TypeNameRoutingKeyStrategyTests + { + private class Example { } + + [Fact] + public void Singleword() + { + AssertRoutingKey("example", typeof(Example)); + } + + + private class ExampleMessage { } + + [Fact] + public void SinglewordMessagePostfix() + { + AssertRoutingKey("example", typeof(ExampleMessage)); + } + + + private class ExampleMultiWord { } + + [Fact] + public void Multiword() + { + AssertRoutingKey("example.multi.word", typeof(ExampleMultiWord)); + } + + + private class ExampleMultiWordMessage { } + + [Fact] + public void MultiwordMessagePostfix() + { + AssertRoutingKey("example.multi.word", typeof(ExampleMultiWordMessage)); + } + + + private class ACRTestMessage { } + + [Fact] + public void Acronym() + { + AssertRoutingKey("acr.test", typeof(ACRTestMessage)); + } + + + private class ACRTestMIXEDCaseMESSAGE { } + + [Fact] + public void MixedCasing() + { + AssertRoutingKey("acr.test.mixed.case", typeof(ACRTestMIXEDCaseMESSAGE)); + } + + private void AssertRoutingKey(string expected, Type messageType) + { + if (expected == null) throw new ArgumentNullException(nameof(expected)); + if (messageType == null) throw new ArgumentNullException(nameof(messageType)); + + Assert.Equal(expected, new TypeNameRoutingKeyStrategy().GetRoutingKey(messageType)); + } + } + // ReSharper restore InconsistentNaming +} diff --git a/Tapet.Tests/packages.config b/Tapet.Tests/packages.config new file mode 100644 index 0000000..44a585a --- /dev/null +++ b/Tapet.Tests/packages.config @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/Tapeti.sln b/Tapeti.sln index 33bd48a..d161beb 100644 --- a/Tapeti.sln +++ b/Tapeti.sln @@ -15,6 +15,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tapeti.Flow.SQL", "Tapeti.F EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tapeti.Annotations", "Tapeti.Annotations\Tapeti.Annotations.csproj", "{C4897D64-D04E-4AE9-BD98-D64295D1D13A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Tapet.Tests", "Tapet.Tests\Tapet.Tests.csproj", "{BA6BC046-5E60-410A-AE84-BE1D91561A96}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -45,6 +47,10 @@ Global {C4897D64-D04E-4AE9-BD98-D64295D1D13A}.Debug|Any CPU.Build.0 = Debug|Any CPU {C4897D64-D04E-4AE9-BD98-D64295D1D13A}.Release|Any CPU.ActiveCfg = Release|Any CPU {C4897D64-D04E-4AE9-BD98-D64295D1D13A}.Release|Any CPU.Build.0 = Release|Any CPU + {BA6BC046-5E60-410A-AE84-BE1D91561A96}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BA6BC046-5E60-410A-AE84-BE1D91561A96}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BA6BC046-5E60-410A-AE84-BE1D91561A96}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BA6BC046-5E60-410A-AE84-BE1D91561A96}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/Tapeti/Default/TypeNameRoutingKeyStrategy.cs b/Tapeti/Default/TypeNameRoutingKeyStrategy.cs index b5e146d..d221a82 100644 --- a/Tapeti/Default/TypeNameRoutingKeyStrategy.cs +++ b/Tapeti/Default/TypeNameRoutingKeyStrategy.cs @@ -2,58 +2,59 @@ using System.Collections.Concurrent; using System.Collections.Generic; using System.Linq; +using System.Text; +using System.Text.RegularExpressions; namespace Tapeti.Default { public class TypeNameRoutingKeyStrategy : IRoutingKeyStrategy { - private readonly ConcurrentDictionary routingKeyCache = new ConcurrentDictionary(); + private const string SeparatorPattern = @" + (? RoutingKeyCache = new ConcurrentDictionary(); public string GetRoutingKey(Type messageType) { - return routingKeyCache.GetOrAdd(messageType, BuildRoutingKey); + return RoutingKeyCache.GetOrAdd(messageType, BuildRoutingKey); } protected virtual string BuildRoutingKey(Type messageType) { // Split PascalCase into dot-separated parts. If the class name ends in "Message" leave that out. - var words = SplitUpperCase(messageType.Name); + var words = SplitPascalCase(messageType.Name); + if (words == null) + return ""; - if (words.Count > 1 && words.Last().Equals("Message", StringComparison.InvariantCulture)) + if (words.Count > 1 && words.Last().Equals("Message", StringComparison.InvariantCultureIgnoreCase)) words.RemoveAt(words.Count - 1); return string.Join(".", words.Select(s => s.ToLower())); } - - protected static List SplitUpperCase(string source) + private static List SplitPascalCase(string value) { - var words = new List(); + var split = SeparatorRegex.Split(value); + if (split.Length == 0) + return null; - if (string.IsNullOrEmpty(source)) - return words; + var result = new List(split.Length - 1 / 2) { split[0] }; + for (var i = 1; i < split.Length; i += 2) + result.Add(split[i] + split[i + 1]); - var wordStartIndex = 0; - - var letters = source.ToCharArray(); - var previousChar = char.MinValue; - - // Intentionally skip the first character - for (var charIndex = 1; charIndex < letters.Length; charIndex++) - { - if (char.IsUpper(letters[charIndex]) && !char.IsWhiteSpace(previousChar)) - { - words.Add(new string(letters, wordStartIndex, charIndex - wordStartIndex)); - wordStartIndex = charIndex; - } - - previousChar = letters[charIndex]; - } - - words.Add(new string(letters, wordStartIndex, letters.Length - wordStartIndex)); - return words; + return result; } } }