Skip to content

Commit 5e5d77b

Browse files
patchoulishjeremydmiller
authored andcommitted
Add support for routing messages based on attribute decoration
1 parent a78f715 commit 5e5d77b

File tree

7 files changed

+98
-8
lines changed

7 files changed

+98
-8
lines changed

src/Testing/CoreTests/Runtime/Green/Messages.cs

+3-1
Original file line numberDiff line numberDiff line change
@@ -4,4 +4,6 @@ public class GreenMessage1;
44

55
public class GreenMessage2;
66

7-
public class GreenMessage3;
7+
public class GreenMessage3;
8+
9+
public class GreenAttribute : Attribute;
+10-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,16 @@
11
namespace CoreTests.Runtime.Red;
22

3+
[Red]
34
public class RedMessage1;
45

6+
[Crimson]
57
public class RedMessage2;
68

7-
public class RedMessage3;
9+
[Burgundy]
10+
public class RedMessage3;
11+
12+
public class RedAttribute : Attribute;
13+
14+
public class CrimsonAttribute : RedAttribute;
15+
16+
public class BurgundyAttribute : RedAttribute;

src/Testing/CoreTests/Runtime/SubscriptionTester.cs

+43-1
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,17 @@ public void description_of_type_name_rule()
4545
rule.ToString().ShouldBe("Message name is 'CoreTests.Runtime.RandomClass'");
4646
}
4747

48+
[Fact]
49+
public void description_of_attribute_rule()
50+
{
51+
var rule = new Subscription
52+
{
53+
BaseOrAttributeType = typeof(RandomClassAttribute),
54+
Scope = RoutingScope.Attribute
55+
};
56+
rule.ToString().ShouldBe("Message type is decorated with 'CoreTests.Runtime.RandomClassAttribute' or a derived type");
57+
}
58+
4859
[Fact]
4960
public void description_of_all_types()
5061
{
@@ -85,6 +96,20 @@ public void positive_assembly_test()
8596
rule.Matches(typeof(DeleteUser)).ShouldBeTrue();
8697
}
8798

99+
[Fact]
100+
public void negative_attribute_test()
101+
{
102+
var rule = new Subscription
103+
{
104+
Scope = RoutingScope.Attribute,
105+
BaseOrAttributeType = typeof(GreenAttribute)
106+
};
107+
108+
rule.Matches(typeof(GreenMessage1)).ShouldBeFalse();
109+
rule.Matches(typeof(GreenMessage2)).ShouldBeFalse();
110+
rule.Matches(typeof(GreenMessage3)).ShouldBeFalse();
111+
}
112+
88113
[Fact]
89114
public void positive_namespace_test()
90115
{
@@ -98,6 +123,23 @@ public void positive_namespace_test()
98123
rule.Matches(typeof(RedMessage2)).ShouldBeTrue();
99124
rule.Matches(typeof(RedMessage3)).ShouldBeTrue();
100125
}
126+
127+
[Fact]
128+
public void positive_attribute_test()
129+
{
130+
var rule = new Subscription
131+
{
132+
Scope = RoutingScope.Attribute,
133+
BaseOrAttributeType = typeof(RedAttribute)
134+
};
135+
136+
rule.Matches(typeof(RedMessage1)).ShouldBeTrue();
137+
rule.Matches(typeof(RedMessage2)).ShouldBeTrue();
138+
rule.Matches(typeof(RedMessage3)).ShouldBeTrue();
139+
}
101140
}
102141

103-
public class RandomClass;
142+
[RandomClass]
143+
public class RandomClass;
144+
145+
public class RandomClassAttribute : Attribute;

src/Wolverine/Configuration/PublishingExpression.cs

+30-1
Original file line numberDiff line numberDiff line change
@@ -160,6 +160,35 @@ public PublishingExpression MessagesFromAssemblyContaining<T>()
160160
return MessagesFromAssembly(typeof(T).Assembly);
161161
}
162162

163+
/// <summary>
164+
/// Create a publishing rule for all messages decorated with the specified attribute type or a derived type
165+
/// </summary>
166+
/// <param name="attributeType"></param>
167+
/// <returns></returns>
168+
public PublishingExpression MessagesDecoratedWith(Type attributeType)
169+
{
170+
AutoAddSubscriptions = true;
171+
172+
_subscriptions.Add(new Subscription()
173+
{
174+
Scope = RoutingScope.Attribute,
175+
BaseOrAttributeType = attributeType,
176+
});
177+
178+
return this;
179+
}
180+
181+
/// <summary>
182+
/// Create a publishing rule for all messages decorated with the attribute of type T or a derived type
183+
/// </summary>
184+
/// <typeparam name="T"></typeparam>
185+
/// <returns></returns>
186+
public PublishingExpression MessagesDecoratedWith<T>()
187+
where T : Attribute
188+
{
189+
return MessagesDecoratedWith(typeof(T));
190+
}
191+
163192
internal void AttachSubscriptions()
164193
{
165194
if (!_endpoints.Any())
@@ -182,6 +211,6 @@ internal void AddSubscriptionForAllMessages()
182211
/// <typeparam name="T"></typeparam>
183212
public void MessagesImplementing<T>()
184213
{
185-
_subscriptions.Add(new Subscription { BaseType = typeof(T), Scope = RoutingScope.Implements });
214+
_subscriptions.Add(new Subscription { BaseOrAttributeType = typeof(T), Scope = RoutingScope.Implements });
186215
}
187216
}

src/Wolverine/Runtime/Routing/RoutingScope.cs

+2-1
Original file line numberDiff line numberDiff line change
@@ -7,5 +7,6 @@ public enum RoutingScope
77
Type,
88
TypeName,
99
All,
10-
Implements
10+
Implements,
11+
Attribute
1112
}

src/Wolverine/Runtime/Routing/Subscription.cs

+9-2
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,10 @@ public string[] ContentTypes
4343
/// </summary>
4444
public string Match { get; init; } = string.Empty;
4545

46-
public Type? BaseType { get; init; }
46+
/// <summary>
47+
/// A base type if matching on implementation or an attribute type if matching on decoration
48+
/// </summary>
49+
public Type? BaseOrAttributeType { get; init; }
4750

4851
/// <summary>
4952
/// Create a subscription for a specific message type
@@ -80,7 +83,8 @@ public bool Matches(Type type)
8083
RoutingScope.Type => type.Name.EqualsIgnoreCase(Match) || type.FullName!.EqualsIgnoreCase(Match) ||
8184
type.ToMessageTypeName().EqualsIgnoreCase(Match),
8285
RoutingScope.TypeName => type.ToMessageTypeName().EqualsIgnoreCase(Match),
83-
RoutingScope.Implements => type.CanBeCastTo(BaseType!),
86+
RoutingScope.Implements => type.CanBeCastTo(BaseOrAttributeType!),
87+
RoutingScope.Attribute => type.IsDefined(BaseOrAttributeType!, inherit: false),
8488
_ => !type.CanBeCastTo<IAgentCommand>()
8589
};
8690
}
@@ -140,6 +144,9 @@ public override string ToString()
140144

141145
case RoutingScope.TypeName:
142146
return $"Message name is '{Match}'";
147+
148+
case RoutingScope.Attribute:
149+
return $"Message type is decorated with '{BaseOrAttributeType?.FullName}' or a derived type";
143150
}
144151

145152
throw new ArgumentOutOfRangeException();

src/Wolverine/Transports/Local/LocalTransport.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ public LocalTransport() : base(TransportConstants.Local, "Local (In Memory)")
3636
var agentQueue = _queues[TransportConstants.Agents];
3737
agentQueue.TelemetryEnabled = false;
3838
agentQueue.Subscriptions.Add(new Subscription
39-
{ Scope = RoutingScope.Implements, BaseType = typeof(IAgentCommand) });
39+
{ Scope = RoutingScope.Implements, BaseOrAttributeType = typeof(IAgentCommand) });
4040
agentQueue.ExecutionOptions.MaxDegreeOfParallelism = 20;
4141
agentQueue.Role = EndpointRole.System;
4242
agentQueue.Mode = EndpointMode.BufferedInMemory;

0 commit comments

Comments
 (0)