Skip to content

Commit 75d992c

Browse files
authored
enforce event to topic map if the endpoint specifies (#1242)
1 parent 556a35a commit 75d992c

File tree

7 files changed

+55
-4
lines changed

7 files changed

+55
-4
lines changed

src/AcceptanceTests/When_loading_from_options.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,8 @@ await Scenario.Define<Context>()
4545
{
4646
QueueNameToSubscriptionNameMap = { { Conventions.EndpointNamingConvention(typeof(Publisher)), TopicName } },
4747
PublishedEventToTopicsMap = { { typeof(Event).FullName, TopicName } },
48-
SubscribedEventToTopicsMap = { { typeof(Event).FullName, [TopicName] } }
48+
SubscribedEventToTopicsMap = { { typeof(Event).FullName, [TopicName] } },
49+
ThrowIfUnmappedEventTypes = true
4950
}, TopologyOptionsSerializationContext.Default.TopologyOptions);
5051
var options = JsonSerializer.Deserialize(serializedOptions, TopologyOptionsSerializationContext.Default.TopologyOptions);
5152
transport.Topology = TopicTopology.FromOptions(options);
@@ -72,7 +73,7 @@ await Scenario.Define<Context>()
7273
SubscribedEventToRuleNameMap = { { typeof(Event).FullName, typeof(Event).FullName.Shorten() } },
7374
TopicToPublishTo = TopicName,
7475
TopicToSubscribeOn = TopicName,
75-
EventsToMigrateMap = [typeof(Event).FullName]
76+
EventsToMigrateMap = [typeof(Event).FullName],
7677
}, TopologyOptionsSerializationContext.Default.TopologyOptions);
7778

7879
var options = JsonSerializer.Deserialize(serializedOptions, TopologyOptionsSerializationContext.Default.TopologyOptions);

src/Tests/ApprovalFiles/APIApprovals.Approve.approved.txt

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,8 @@ namespace NServiceBus.Transport.AzureServiceBus
142142
public System.Collections.Generic.HashSet<string> EventsToMigrateMap { get; init; }
143143
[NServiceBus.Transport.AzureServiceBus.AzureServiceBusRules]
144144
public System.Collections.Generic.Dictionary<string, string> SubscribedEventToRuleNameMap { get; init; }
145+
[System.Text.Json.Serialization.JsonIgnore]
146+
public new bool ThrowIfUnmappedEventTypes { get; }
145147
[NServiceBus.Transport.AzureServiceBus.AzureServiceBusTopics]
146148
[System.ComponentModel.DataAnnotations.Required]
147149
public required string? TopicToPublishTo { get; init; }
@@ -185,6 +187,7 @@ namespace NServiceBus.Transport.AzureServiceBus
185187
[NServiceBus.Transport.AzureServiceBus.AzureServiceBusTopics]
186188
[System.Text.Json.Serialization.JsonConverter(typeof(NServiceBus.Transport.AzureServiceBus.SubscribedEventToTopicsMapConverter))]
187189
public System.Collections.Generic.Dictionary<string, System.Collections.Generic.HashSet<string>> SubscribedEventToTopicsMap { get; init; }
190+
public bool ThrowIfUnmappedEventTypes { get; set; }
188191
}
189192
public sealed class TopologyOptionsDisableValidationValidator : Microsoft.Extensions.Options.IValidateOptions<NServiceBus.Transport.AzureServiceBus.TopologyOptions>
190193
{
@@ -198,6 +201,7 @@ namespace NServiceBus.Transport.AzureServiceBus
198201
{
199202
public TopologyOptionsSerializationContext() { }
200203
public TopologyOptionsSerializationContext(System.Text.Json.JsonSerializerOptions options) { }
204+
public System.Text.Json.Serialization.Metadata.JsonTypeInfo<bool> Boolean { get; }
201205
public System.Text.Json.Serialization.Metadata.JsonTypeInfo<System.Collections.Generic.Dictionary<string, System.Collections.Generic.HashSet<string>>> DictionaryStringHashSetString { get; }
202206
public System.Text.Json.Serialization.Metadata.JsonTypeInfo<System.Collections.Generic.Dictionary<string, string>> DictionaryStringString { get; }
203207
protected override System.Text.Json.JsonSerializerOptions? GeneratedSerializerOptions { get; }

src/Tests/EventRouting/TopicPerEventTopologyTests.cs

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
namespace NServiceBus.Transport.AzureServiceBus.Tests;
22

3+
using System;
34
using System.ComponentModel.DataAnnotations;
45
using NUnit.Framework;
56
using Particular.Approvals;
@@ -25,6 +26,31 @@ public void PublishDestination_Should_return_mapped_topic_when_event_is_mapped()
2526
Assert.That(result, Is.EqualTo("MyTopic"));
2627
}
2728

29+
[Test]
30+
public void PublishDestination_Should_default_topic_to_event_name()
31+
{
32+
var topologyOptions = new TopologyOptions();
33+
34+
var topology = TopicTopology.FromOptions(topologyOptions);
35+
36+
var result = topology.GetPublishDestination(typeof(MyEvent));
37+
38+
Assert.That(result, Is.EqualTo(typeof(MyEvent).FullName));
39+
}
40+
41+
[Test]
42+
public void PublishDestination_Should_throw_when_instructed_to_and_type_unmapped()
43+
{
44+
var topologyOptions = new TopologyOptions
45+
{
46+
ThrowIfUnmappedEventTypes = true
47+
};
48+
49+
var topology = TopicTopology.FromOptions(topologyOptions);
50+
51+
Assert.Throws<Exception>(() => topology.GetPublishDestination(typeof(MyEvent)));
52+
}
53+
2854
[Test]
2955
public void Should_self_validate()
3056
{

src/Transport/EventRouting/EntityValidator.cs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ static partial class EntityValidator
1919
: ValidationResult.Success;
2020
}
2121

22+
// Enforces naming according to the specification https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules#microsoftservicebus
2223
[GeneratedRegex(@"^(?=.{1,260}$)(?=^[A-Za-z0-9])(?!.*[\\?#])(?:[A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9./_-]*[A-Za-z0-9])$")]
2324
private static partial Regex TopicNameRegex();
2425

@@ -34,6 +35,7 @@ static partial class EntityValidator
3435
: ValidationResult.Success;
3536
}
3637

38+
// Enforces naming according to the specification https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules#microsoftservicebus
3739
// Note the queue pattern is the same as the topic pattern. Deliberately kept separate for future extensibility.
3840
[GeneratedRegex(@"^(?=.{1,260}$)(?=^[A-Za-z0-9])(?!.*[\\?#])(?:[A-Za-z0-9]|[A-Za-z0-9][A-Za-z0-9./_-]*[A-Za-z0-9])$")]
3941
private static partial Regex QueueNameRegex();
@@ -50,6 +52,7 @@ static partial class EntityValidator
5052
: ValidationResult.Success;
5153
}
5254

55+
// Enforces naming according to the specification https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules#microsoftservicebus
5356
[GeneratedRegex(@"^(?!\$)(?=.{1,50}$)(?=^[A-Za-z0-9])(?!.*[\/\\?#])[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$")]
5457
private static partial Regex RuleNameRegex();
5558

@@ -66,6 +69,7 @@ static partial class EntityValidator
6669
: ValidationResult.Success;
6770
}
6871

72+
//enforces naming according to the specification https://learn.microsoft.com/en-us/azure/azure-resource-manager/management/resource-name-rules#microsoftservicebus
6973
// Note the subscription pattern is the same as the rule pattern. Deliberately kept separate for future extensibility.
7074
[GeneratedRegex(@"^(?!\$)(?=.{1,50}$)(?=^[A-Za-z0-9])(?!.*[\/\\?#])[A-Za-z0-9](?:[A-Za-z0-9._-]*[A-Za-z0-9])?$")]
7175
private static partial Regex SubscriptionNameRegex();

src/Transport/EventRouting/MigrationTopologyOptions.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ namespace NServiceBus.Transport.AzureServiceBus;
33
using System;
44
using System.Collections.Generic;
55
using System.ComponentModel.DataAnnotations;
6+
using System.Text.Json.Serialization;
67
using Particular.Obsoletes;
78

89
/// <summary>
@@ -45,4 +46,9 @@ public Dictionary<string, string> SubscribedEventToRuleNameMap
4546
get;
4647
init => field = value ?? [];
4748
} = [];
49+
50+
// NOTE: explicitly set to true always and ignored from JSON, since MigrationTopology is already obsolete and we don't want to have a fallback naming strategy
51+
/// <inheritdoc cref="TopologyOptions.ThrowIfUnmappedEventTypes" />
52+
[JsonIgnore]
53+
public new bool ThrowIfUnmappedEventTypes => true;
4854
}

src/Transport/EventRouting/TopicPerEventTopology.cs

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
namespace NServiceBus.Transport.AzureServiceBus;
22

33
using System;
4-
using System.Collections.Generic;
54

65
/// <summary>
76
/// A topology that uses separate topic for each event.
@@ -105,7 +104,13 @@ public void OverrideSubscriptionNameFor(string queueName, string subscriptionNam
105104

106105
/// <inheritdoc />
107106
protected override string GetPublishDestinationCore(string eventTypeFullName)
108-
=> Options.PublishedEventToTopicsMap.GetValueOrDefault(eventTypeFullName, eventTypeFullName);
107+
{
108+
if (!Options.PublishedEventToTopicsMap.TryGetValue(eventTypeFullName, out string? topic) && Options.ThrowIfUnmappedEventTypes)
109+
{
110+
throw new Exception($"Unmapped event type '{eventTypeFullName}'. All events must be mapped in `{nameof(TopologyOptions.PublishedEventToTopicsMap)}` when `{nameof(TopologyOptions.ThrowIfUnmappedEventTypes)}` is set");
111+
}
112+
return topic ?? eventTypeFullName;
113+
}
109114

110115
internal override SubscriptionManager CreateSubscriptionManager(
111116
SubscriptionManagerCreationOptions creationOptions) =>

src/Transport/EventRouting/TopologyOptions.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,4 +43,9 @@ public Dictionary<string, string> QueueNameToSubscriptionNameMap
4343
get;
4444
init => field = value ?? [];
4545
} = [];
46+
47+
/// <summary>
48+
/// Determines if an exception should be thrown when attempting to publish an event not mapped in PublishedEventToTopicsMap
49+
/// </summary>
50+
public bool ThrowIfUnmappedEventTypes { get; set; } = false;
4651
}

0 commit comments

Comments
 (0)