Browse Source

Support null values in routable collection members

Olivier Coanet 3 years ago
parent
commit
84a62538ee

+ 58 - 0
src/Abc.Zebus.Tests/Directory/MessageBindingTests.cs

@@ -1,4 +1,6 @@
 using System;
 using System;
+using System.Collections;
+using System.Collections.Generic;
 using Abc.Zebus.Directory;
 using Abc.Zebus.Directory;
 using Abc.Zebus.Routing;
 using Abc.Zebus.Routing;
 using Abc.Zebus.Testing.Extensions;
 using Abc.Zebus.Testing.Extensions;
@@ -24,6 +26,18 @@ namespace Abc.Zebus.Tests.Routing
             messageBinding.RoutingContent[2].ShouldEqual(new RoutingContentValue(message.OtherId.ToString()));
             messageBinding.RoutingContent[2].ShouldEqual(new RoutingContentValue(message.OtherId.ToString()));
         }
         }
 
 
+        [Test]
+        public void should_create_message_binding_from_message_with_null_value()
+        {
+            var message = new FakeRoutableCommandWithString { Id = null };
+
+            var messageBinding = MessageBinding.FromMessage(message);
+
+            messageBinding.MessageTypeId.ShouldEqual(MessageUtil.TypeId<FakeRoutableCommandWithString>());
+            messageBinding.RoutingContent.PartCount.ShouldEqual(1);
+            messageBinding.RoutingContent[0].ShouldEqual(new RoutingContentValue((string)null));
+        }
+
         [Test, SetCulture("FR-fr")]
         [Test, SetCulture("FR-fr")]
         public void should_create_message_binding_from_message_with_collections()
         public void should_create_message_binding_from_message_with_collections()
         {
         {
@@ -43,6 +57,36 @@ namespace Abc.Zebus.Tests.Routing
             messageBinding.RoutingContent[2].ShouldEqual(new RoutingContentValue(new[] { "1.5" }));
             messageBinding.RoutingContent[2].ShouldEqual(new RoutingContentValue(new[] { "1.5" }));
         }
         }
 
 
+        [Test]
+        public void should_create_message_binding_from_message_with_null_collection_items()
+        {
+            var message = new FakeRoutableCommandWithStringCollection
+            {
+                Ids = new() { "1", null, "2" },
+            };
+
+            var messageBinding = MessageBinding.FromMessage(message);
+
+            messageBinding.MessageTypeId.ShouldEqual(MessageUtil.TypeId<FakeRoutableCommandWithStringCollection>());
+            messageBinding.RoutingContent.PartCount.ShouldEqual(1);
+            messageBinding.RoutingContent[0].ShouldEqual(new RoutingContentValue(new[] { "1", null, "2" }));
+        }
+
+        [Test]
+        public void should_create_message_binding_from_message_with_null_collection()
+        {
+            var message = new FakeRoutableCommandWithStringCollection
+            {
+                Ids = null,
+            };
+
+            var messageBinding = MessageBinding.FromMessage(message);
+
+            messageBinding.MessageTypeId.ShouldEqual(MessageUtil.TypeId<FakeRoutableCommandWithStringCollection>());
+            messageBinding.RoutingContent.PartCount.ShouldEqual(1);
+            messageBinding.RoutingContent[0].ShouldEqual(new RoutingContentValue(Array.Empty<string>()));
+        }
+
         [Test]
         [Test]
         public void should_create_message_binding_from_message_with_properties()
         public void should_create_message_binding_from_message_with_properties()
         {
         {
@@ -76,5 +120,19 @@ namespace Abc.Zebus.Tests.Routing
             [RoutingPosition(2)]
             [RoutingPosition(2)]
             public int FeedId { get; set; }
             public int FeedId { get; set; }
         }
         }
+
+        [Routable]
+        public class FakeRoutableCommandWithString : ICommand
+        {
+            [RoutingPosition(1)]
+            public string Id { get; set; }
+        }
+
+        [Routable]
+        public class FakeRoutableCommandWithStringCollection : ICommand
+        {
+            [RoutingPosition(1)]
+            public List<string> Ids { get; set; }
+        }
     }
     }
 }
 }

+ 0 - 2
src/Abc.Zebus.Tests/Directory/PeerSubscriptionTreeTests.cs

@@ -272,8 +272,6 @@ namespace Abc.Zebus.Tests.Directory
             matchingPeers.ShouldBeEmpty();
             matchingPeers.ShouldBeEmpty();
         }
         }
 
 
-        // TODO: investigate this test case
-        // [TestCase("*", true)]
         [TestCase("#", true)]
         [TestCase("#", true)]
         [TestCase("1.*", true)]
         [TestCase("1.*", true)]
         [TestCase("2.*", false)]
         [TestCase("2.*", false)]

+ 3 - 2
src/Abc.Zebus.Tests/Routing/RoutingMatchingTests.cs

@@ -1,4 +1,5 @@
-using System.Collections.Generic;
+using System;
+using System.Collections.Generic;
 using System.Linq;
 using System.Linq;
 using Abc.Zebus.Directory;
 using Abc.Zebus.Directory;
 using Abc.Zebus.Routing;
 using Abc.Zebus.Routing;
@@ -26,7 +27,7 @@ namespace Abc.Zebus.Tests.Routing
         [TestCase("A.3.999", false)]
         [TestCase("A.3.999", false)]
         [TestCase("A.9.102", false)]
         [TestCase("A.9.102", false)]
         [TestCase("9.3.102", false)]
         [TestCase("9.3.102", false)]
-        public void should_matching_routable_message(string bindingKeyText, bool isMatchExpected)
+        public void should_match_routable_message(string bindingKeyText, bool isMatchExpected)
         {
         {
             var message = new FakeRoutableCommandWithCollection
             var message = new FakeRoutableCommandWithCollection
             {
             {

+ 10 - 20
src/Abc.Zebus/MessageTypeDescriptor.cs

@@ -125,38 +125,28 @@ namespace Abc.Zebus
                 where TMember : IConvertible
                 where TMember : IConvertible
                 => new RoutingContentValue(value?.ToString(CultureInfo.InvariantCulture));
                 => new RoutingContentValue(value?.ToString(CultureInfo.InvariantCulture));
 
 
-            private static RoutingContentValue GetMemberValues<TMember>(ICollection<TMember>? values)
-                => new RoutingContentValue(values != null ? values.Select(x => x!.ToString()).ToArray() : Array.Empty<string>());
+            private static RoutingContentValue GetMemberValues<TMember>(ICollection<TMember?>? values)
+                => new RoutingContentValue(values != null ? values.Select(x => x?.ToString()).ToArray() : Array.Empty<string>());
 
 
-            private static RoutingContentValue GetMemberValuesConvertible<TMember>(ICollection<TMember>? values)
+            private static RoutingContentValue GetMemberValuesConvertible<TMember>(ICollection<TMember?>? values)
                 where TMember : IConvertible
                 where TMember : IConvertible
-                => new RoutingContentValue(values != null ? values.Select(x => x.ToString(CultureInfo.InvariantCulture)).ToArray() : Array.Empty<string>());
+                => new RoutingContentValue(values != null ? values.Select(x => x?.ToString(CultureInfo.InvariantCulture)).ToArray() : Array.Empty<string>());
 
 
             private static MethodCallExpression BuildValueExpression(MemberInfo member)
             private static MethodCallExpression BuildValueExpression(MemberInfo member)
             {
             {
-                var memberExpression = GetMemberExpression();
-                var getValueMethod = GetValueMethodInfo(memberExpression.type);
+                var castExpression = Expression.Convert(ParameterExpression, member.DeclaringType!);
+                var memberExpression = Expression.MakeMemberAccess(castExpression, member);
+                var getValueMethod = GetValueMethodInfo(memberExpression.Type);
 
 
-                return Expression.Call(null, getValueMethod, memberExpression.value);
-
-                (Expression value, Type type) GetMemberExpression()
-                {
-                    var castExpression = Expression.Convert(ParameterExpression, member.DeclaringType!);
-
-                    return member switch
-                    {
-                        PropertyInfo propertyInfo => (Expression.Property(castExpression, propertyInfo), propertyInfo.PropertyType),
-                        FieldInfo fieldInfo       => (Expression.Field(castExpression, fieldInfo), fieldInfo.FieldType),
-                        _                         => throw new InvalidOperationException("Cannot define routing position on a member other than a field or property"),
-                    };
-                }
+                return Expression.Call(null, getValueMethod, memberExpression);
 
 
                 MethodInfo GetValueMethodInfo(Type memberType)
                 MethodInfo GetValueMethodInfo(Type memberType)
                 {
                 {
                     var collectionType = memberType.GetInterfaces().FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(ICollection<>));
                     var collectionType = memberType.GetInterfaces().FirstOrDefault(x => x.IsGenericType && x.GetGenericTypeDefinition() == typeof(ICollection<>));
-
                     if (collectionType != null)
                     if (collectionType != null)
                     {
                     {
+                        // The type could theoretically implement ICollection<> twice but picking randomly one implementation for this edge case should be ok.
+
                         var itemType = collectionType.GetGenericArguments()[0];
                         var itemType = collectionType.GetGenericArguments()[0];
                         return typeof(IConvertible).IsAssignableFrom(itemType) ? _getValuesConvertibleMethod.MakeGenericMethod(itemType) : _getValuesMethod.MakeGenericMethod(itemType);
                         return typeof(IConvertible).IsAssignableFrom(itemType) ? _getValuesConvertibleMethod.MakeGenericMethod(itemType) : _getValuesMethod.MakeGenericMethod(itemType);
                     }
                     }