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;
}
}
}