Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for routing messages based on attribute decoration #1150

Merged
merged 1 commit into from
Dec 2, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion src/Testing/CoreTests/Runtime/Green/Messages.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,6 @@ public class GreenMessage1;

public class GreenMessage2;

public class GreenMessage3;
public class GreenMessage3;

public class GreenAttribute : Attribute;
11 changes: 10 additions & 1 deletion src/Testing/CoreTests/Runtime/Red/Messages.cs
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
namespace CoreTests.Runtime.Red;

[Red]
public class RedMessage1;

[Crimson]
public class RedMessage2;

public class RedMessage3;
[Burgundy]
public class RedMessage3;

public class RedAttribute : Attribute;

public class CrimsonAttribute : RedAttribute;

public class BurgundyAttribute : RedAttribute;
44 changes: 43 additions & 1 deletion src/Testing/CoreTests/Runtime/SubscriptionTester.cs
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,17 @@ public void description_of_type_name_rule()
rule.ToString().ShouldBe("Message name is 'CoreTests.Runtime.RandomClass'");
}

[Fact]
public void description_of_attribute_rule()
{
var rule = new Subscription
{
BaseOrAttributeType = typeof(RandomClassAttribute),
Scope = RoutingScope.Attribute
};
rule.ToString().ShouldBe("Message type is decorated with 'CoreTests.Runtime.RandomClassAttribute' or a derived type");
}

[Fact]
public void description_of_all_types()
{
Expand Down Expand Up @@ -85,6 +96,20 @@ public void positive_assembly_test()
rule.Matches(typeof(DeleteUser)).ShouldBeTrue();
}

[Fact]
public void negative_attribute_test()
{
var rule = new Subscription
{
Scope = RoutingScope.Attribute,
BaseOrAttributeType = typeof(GreenAttribute)
};

rule.Matches(typeof(GreenMessage1)).ShouldBeFalse();
rule.Matches(typeof(GreenMessage2)).ShouldBeFalse();
rule.Matches(typeof(GreenMessage3)).ShouldBeFalse();
}

[Fact]
public void positive_namespace_test()
{
Expand All @@ -98,6 +123,23 @@ public void positive_namespace_test()
rule.Matches(typeof(RedMessage2)).ShouldBeTrue();
rule.Matches(typeof(RedMessage3)).ShouldBeTrue();
}

[Fact]
public void positive_attribute_test()
{
var rule = new Subscription
{
Scope = RoutingScope.Attribute,
BaseOrAttributeType = typeof(RedAttribute)
};

rule.Matches(typeof(RedMessage1)).ShouldBeTrue();
rule.Matches(typeof(RedMessage2)).ShouldBeTrue();
rule.Matches(typeof(RedMessage3)).ShouldBeTrue();
}
}

public class RandomClass;
[RandomClass]
public class RandomClass;

public class RandomClassAttribute : Attribute;
31 changes: 30 additions & 1 deletion src/Wolverine/Configuration/PublishingExpression.cs
Original file line number Diff line number Diff line change
Expand Up @@ -160,6 +160,35 @@ public PublishingExpression MessagesFromAssemblyContaining<T>()
return MessagesFromAssembly(typeof(T).Assembly);
}

/// <summary>
/// Create a publishing rule for all messages decorated with the specified attribute type or a derived type
/// </summary>
/// <param name="attributeType"></param>
/// <returns></returns>
public PublishingExpression MessagesDecoratedWith(Type attributeType)
{
AutoAddSubscriptions = true;

_subscriptions.Add(new Subscription()
{
Scope = RoutingScope.Attribute,
BaseOrAttributeType = attributeType,
});

return this;
}

/// <summary>
/// Create a publishing rule for all messages decorated with the attribute of type T or a derived type
/// </summary>
/// <typeparam name="T"></typeparam>
/// <returns></returns>
public PublishingExpression MessagesDecoratedWith<T>()
where T : Attribute
{
return MessagesDecoratedWith(typeof(T));
}

internal void AttachSubscriptions()
{
if (!_endpoints.Any())
Expand All @@ -182,6 +211,6 @@ internal void AddSubscriptionForAllMessages()
/// <typeparam name="T"></typeparam>
public void MessagesImplementing<T>()
{
_subscriptions.Add(new Subscription { BaseType = typeof(T), Scope = RoutingScope.Implements });
_subscriptions.Add(new Subscription { BaseOrAttributeType = typeof(T), Scope = RoutingScope.Implements });
}
}
3 changes: 2 additions & 1 deletion src/Wolverine/Runtime/Routing/RoutingScope.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@ public enum RoutingScope
Type,
TypeName,
All,
Implements
Implements,
Attribute
}
11 changes: 9 additions & 2 deletions src/Wolverine/Runtime/Routing/Subscription.cs
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,10 @@ public string[] ContentTypes
/// </summary>
public string Match { get; init; } = string.Empty;

public Type? BaseType { get; init; }
/// <summary>
/// A base type if matching on implementation or an attribute type if matching on decoration
/// </summary>
public Type? BaseOrAttributeType { get; init; }

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

case RoutingScope.TypeName:
return $"Message name is '{Match}'";

case RoutingScope.Attribute:
return $"Message type is decorated with '{BaseOrAttributeType?.FullName}' or a derived type";
}

throw new ArgumentOutOfRangeException();
Expand Down
2 changes: 1 addition & 1 deletion src/Wolverine/Transports/Local/LocalTransport.cs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ public LocalTransport() : base(TransportConstants.Local, "Local (In Memory)")
var agentQueue = _queues[TransportConstants.Agents];
agentQueue.TelemetryEnabled = false;
agentQueue.Subscriptions.Add(new Subscription
{ Scope = RoutingScope.Implements, BaseType = typeof(IAgentCommand) });
{ Scope = RoutingScope.Implements, BaseOrAttributeType = typeof(IAgentCommand) });
agentQueue.ExecutionOptions.MaxDegreeOfParallelism = 20;
agentQueue.Role = EndpointRole.System;
agentQueue.Mode = EndpointMode.BufferedInMemory;
Expand Down
Loading