Skip to content

Commit

Permalink
#213 [FEATURE] Allow usages of the annotation attributes on interfaces (
Browse files Browse the repository at this point in the history
#214)

Co-authored-by: senn.g <[email protected]>
  • Loading branch information
smoerijf and senn.g authored Jun 29, 2024
1 parent f2f5fa1 commit 24b915d
Show file tree
Hide file tree
Showing 6 changed files with 220 additions and 24 deletions.
4 changes: 2 additions & 2 deletions src/Saunter/Attributes/AsyncApiAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
namespace Saunter.Attributes
{
/// <summary>
/// Marks a class as containing asyncapi channels.
/// Marks a class or interface as containing asyncapi channels.
/// </summary>
[AttributeUsage(AttributeTargets.Class)]
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Interface)]
public sealed class AsyncApiAttribute : Attribute
{
public string DocumentName { get; }
Expand Down
2 changes: 1 addition & 1 deletion src/Saunter/Attributes/ChannelAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Saunter.Attributes
{
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Interface)]
public class ChannelAttribute : Attribute
{
/// <summary>
Expand Down
2 changes: 1 addition & 1 deletion src/Saunter/Attributes/ChannelParameterAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Saunter.Attributes
{
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = true)]
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Interface, AllowMultiple = true)]
public class ChannelParameterAttribute : Attribute
{
public ChannelParameterAttribute(string name, Type type)
Expand Down
2 changes: 1 addition & 1 deletion src/Saunter/Attributes/OperationAttribute.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

namespace Saunter.Attributes
{
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class)]
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class | AttributeTargets.Interface)]
public abstract class OperationAttribute : Attribute
{
public OperationType OperationType { get; protected set; }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,15 +12,17 @@ namespace Saunter.Tests.Generation.DocumentGeneratorTests
{
public class ClassAttributesTests
{
[Fact]
public void GetDocument_GeneratesDocumentWithMultipleMessagesPerChannel()
[Theory]
[InlineData(typeof(TenantMessageConsumer))]
[InlineData(typeof(ITenantMessageConsumer))]
public void GetDocument_GeneratesDocumentWithMultipleMessagesPerChannel(Type type)
{
// Arrange
var options = new AsyncApiOptions();
var documentGenerator = new DocumentGenerator();

// Act
var document = documentGenerator.GenerateDocument(new[] { typeof(TenantMessageConsumer).GetTypeInfo() }, options, options.AsyncApi, ActivatorServiceProvider.Instance);
var document = documentGenerator.GenerateDocument(new[] { type.GetTypeInfo() }, options, options.AsyncApi, ActivatorServiceProvider.Instance);

// Assert
document.ShouldNotBeNull();
Expand All @@ -44,15 +46,17 @@ public void GetDocument_GeneratesDocumentWithMultipleMessagesPerChannel()
}


[Fact]
public void GenerateDocument_GeneratesDocumentWithMultipleMessagesPerChannelInTheSameMethod()
[Theory]
[InlineData(typeof(TenantGenericMessagePublisher))]
[InlineData(typeof(ITenantGenericMessagePublisher))]
public void GenerateDocument_GeneratesDocumentWithMultipleMessagesPerChannelInTheSameMethod(Type type)
{
// Arrange
var options = new AsyncApiOptions();
var documentGenerator = new DocumentGenerator();

// Act
var document = documentGenerator.GenerateDocument(new[] { typeof(TenantGenericMessagePublisher).GetTypeInfo() }, options, options.AsyncApi, ActivatorServiceProvider.Instance);
var document = documentGenerator.GenerateDocument(new[] { type.GetTypeInfo() }, options, options.AsyncApi, ActivatorServiceProvider.Instance);

// Assert
document.ShouldNotBeNull();
Expand All @@ -76,15 +80,17 @@ public void GenerateDocument_GeneratesDocumentWithMultipleMessagesPerChannelInTh
}


[Fact]
public void GenerateDocument_GeneratesDocumentWithSingleMessage()
[Theory]
[InlineData(typeof(TenantSingleMessagePublisher))]
[InlineData(typeof(ITenantSingleMessagePublisher))]
public void GenerateDocument_GeneratesDocumentWithSingleMessage(Type type)
{
// Arrange
var options = new AsyncApiOptions();
var documentGenerator = new DocumentGenerator();

// Act
var document = documentGenerator.GenerateDocument(new[] { typeof(TenantSingleMessagePublisher).GetTypeInfo() }, options, options.AsyncApi, ActivatorServiceProvider.Instance);
var document = documentGenerator.GenerateDocument(new[] { type.GetTypeInfo() }, options, options.AsyncApi, ActivatorServiceProvider.Instance);

// Assert
document.ShouldNotBeNull();
Expand All @@ -104,8 +110,10 @@ public void GenerateDocument_GeneratesDocumentWithSingleMessage()
}


[Fact]
public void GetDocument_WhenMultipleClassesUseSameChannelKey_GeneratesDocumentWithMultipleMessagesPerChannel()
[Theory]
[InlineData(typeof(TenantMessageConsumer), typeof(TenantMessagePublisher))]
[InlineData(typeof(ITenantMessageConsumer), typeof(ITenantMessagePublisher))]
public void GetDocument_WhenMultipleClassesUseSameChannelKey_GeneratesDocumentWithMultipleMessagesPerChannel(Type type1, Type type2)
{
// Arrange
var options = new AsyncApiOptions();
Expand All @@ -114,8 +122,8 @@ public void GetDocument_WhenMultipleClassesUseSameChannelKey_GeneratesDocumentWi
// Act
var document = documentGenerator.GenerateDocument(new[]
{
typeof(TenantMessageConsumer).GetTypeInfo(),
typeof(TenantMessagePublisher).GetTypeInfo()
type1.GetTypeInfo(),
type2.GetTypeInfo()
}, options, options.AsyncApi, ActivatorServiceProvider.Instance);

// Assert
Expand Down Expand Up @@ -153,15 +161,17 @@ public void GetDocument_WhenMultipleClassesUseSameChannelKey_GeneratesDocumentWi
}


[Fact]
public void GenerateDocument_GeneratesDocumentWithChannelParameters()
[Theory]
[InlineData(typeof(OneTenantMessageConsumer))]
[InlineData(typeof(IOneTenantMessageConsumer))]
public void GenerateDocument_GeneratesDocumentWithChannelParameters(Type type)
{
// Arrange
var options = new AsyncApiOptions();
var documentGenerator = new DocumentGenerator();

// Act
var document = documentGenerator.GenerateDocument(new[] { typeof(OneTenantMessageConsumer).GetTypeInfo() }, options, options.AsyncApi, ActivatorServiceProvider.Instance);
var document = documentGenerator.GenerateDocument(new[] { type.GetTypeInfo() }, options, options.AsyncApi, ActivatorServiceProvider.Instance);

// Assert
document.ShouldNotBeNull();
Expand All @@ -188,15 +198,17 @@ public void GenerateDocument_GeneratesDocumentWithChannelParameters()
}


[Fact]
public void GenerateDocument_GeneratesDocumentWithMessageHeader()
[Theory]
[InlineData(typeof(MyMessagePublisher))]
[InlineData(typeof(IMyMessagePublisher))]
public void GenerateDocument_GeneratesDocumentWithMessageHeader(Type type)
{
// Arrange
var options = new AsyncApiOptions();
var documentGenerator = new DocumentGenerator();

// Act
var document = documentGenerator.GenerateDocument(new[] { typeof(MyMessagePublisher).GetTypeInfo() }, options, options.AsyncApi, ActivatorServiceProvider.Instance);
var document = documentGenerator.GenerateDocument(new[] { type.GetTypeInfo() }, options, options.AsyncApi, ActivatorServiceProvider.Instance);

// Assert
document.ShouldNotBeNull();
Expand All @@ -216,6 +228,15 @@ public class MyMessagePublisher
public void PublishMyMessage() { }
}

[AsyncApi]
[Channel("channel.my.message")]
[PublishOperation]
public interface IMyMessagePublisher
{
[Message(typeof(MyMessage), HeadersType = typeof(MyMessageHeader))]
void PublishMyMessage();
}

[AsyncApi]
[Channel("asw.tenant_service.tenants_history", Description = "Tenant events.")]
[SubscribeOperation(OperationId = "TenantMessageConsumer", Summary = "Subscribe to domains events about tenants.")]
Expand All @@ -231,6 +252,21 @@ public void SubscribeTenantUpdatedEvent(Guid _, TenantUpdated __) { }
public void SubscribeTenantRemovedEvent(Guid _, TenantRemoved __) { }
}

[AsyncApi]
[Channel("asw.tenant_service.tenants_history", Description = "Tenant events.")]
[SubscribeOperation(OperationId = "TenantMessageConsumer", Summary = "Subscribe to domains events about tenants.")]
public interface ITenantMessageConsumer
{
[Message(typeof(TenantCreated))]
void SubscribeTenantCreatedEvent(Guid _, TenantCreated __);

[Message(typeof(TenantUpdated))]
void SubscribeTenantUpdatedEvent(Guid _, TenantUpdated __);

[Message(typeof(TenantRemoved))]
void SubscribeTenantRemovedEvent(Guid _, TenantRemoved __);
}

[AsyncApi]
[Channel("asw.tenant_service.tenants_history", Description = "Tenant events.")]
[PublishOperation(OperationId = "TenantMessagePublisher", Summary = "Publish domains events about tenants.")]
Expand All @@ -246,6 +282,21 @@ public void PublishTenantUpdatedEvent(Guid _, TenantUpdated __) { }
public void PublishTenantRemovedEvent(Guid _, TenantRemoved __) { }
}

[AsyncApi]
[Channel("asw.tenant_service.tenants_history", Description = "Tenant events.")]
[PublishOperation(OperationId = "TenantMessagePublisher", Summary = "Publish domains events about tenants.")]
public interface ITenantMessagePublisher
{
[Message(typeof(TenantCreated))]
void PublishTenantCreatedEvent(Guid _, TenantCreated __);

[Message(typeof(TenantUpdated))]
void PublishTenantUpdatedEvent(Guid _, TenantUpdated __);

[Message(typeof(TenantRemoved))]
void PublishTenantRemovedEvent(Guid _, TenantRemoved __);
}

[AsyncApi]
[Channel("asw.tenant_service.tenants_history", Description = "Tenant events.")]
[PublishOperation(OperationId = "TenantMessagePublisher", Summary = "Publish domains events about tenants.")]
Expand All @@ -260,6 +311,18 @@ public void PublishTenantEvent<TEvent>(Guid _, TEvent __)
}
}

[AsyncApi]
[Channel("asw.tenant_service.tenants_history", Description = "Tenant events.")]
[PublishOperation(OperationId = "TenantMessagePublisher", Summary = "Publish domains events about tenants.")]
public interface ITenantGenericMessagePublisher
{
[Message(typeof(AnyTenantCreated))]
[Message(typeof(AnyTenantUpdated))]
[Message(typeof(AnyTenantRemoved))]
void PublishTenantEvent<TEvent>(Guid _, TEvent __)
where TEvent : IEvent;
}

[AsyncApi]
[Channel("asw.tenant_service.tenants_history", Description = "Tenant events.")]
[PublishOperation(OperationId = "TenantSingleMessagePublisher", Summary = "Publish single domain event about tenants.")]
Expand All @@ -271,6 +334,15 @@ public void PublishTenantCreated(Guid _, AnyTenantCreated __)
}
}

[AsyncApi]
[Channel("asw.tenant_service.tenants_history", Description = "Tenant events.")]
[PublishOperation(OperationId = "TenantSingleMessagePublisher", Summary = "Publish single domain event about tenants.")]
public interface ITenantSingleMessagePublisher
{
[Message(typeof(AnyTenantCreated))]
public void PublishTenantCreated(Guid _, AnyTenantCreated __);
}

[AsyncApi]
[Channel("asw.tenant_service.{tenant_id}.{tenant_status}", Description = "A tenant events.")]
[ChannelParameter("tenant_id", typeof(long), Description = "The tenant identifier.")]
Expand All @@ -287,6 +359,23 @@ public void SubscribeTenantUpdatedEvent(Guid _, TenantUpdated __) { }
[Message(typeof(TenantRemoved))]
public void SubscribeTenantRemovedEvent(Guid _, TenantRemoved __) { }
}

[AsyncApi]
[Channel("asw.tenant_service.{tenant_id}.{tenant_status}", Description = "A tenant events.")]
[ChannelParameter("tenant_id", typeof(long), Description = "The tenant identifier.")]
[ChannelParameter("tenant_status", typeof(string), Description = "The tenant status.")]
[SubscribeOperation(OperationId = "OneTenantMessageConsumer", Summary = "Subscribe to domains events about a tenant.")]
public interface IOneTenantMessageConsumer
{
[Message(typeof(TenantCreated))]
void SubscribeTenantCreatedEvent(Guid _, TenantCreated __);

[Message(typeof(TenantUpdated))]
void SubscribeTenantUpdatedEvent(Guid _, TenantUpdated __);

[Message(typeof(TenantRemoved))]
void SubscribeTenantRemovedEvent(Guid _, TenantRemoved __);
}
}

public class TenantCreated { }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.

using System;
using System.Reflection;
using Saunter.AsyncApiSchema.v2;
using Saunter.Attributes;
using Saunter.Generation;
using Shouldly;
using Xunit;
using System.Linq;

namespace Saunter.Tests.Generation.DocumentGeneratorTests
{
public class InterfaceAttributeTests
{
[Theory]
[InlineData(typeof(IServiceEvents))]
[InlineData(typeof(ServiceEventsFromInterface))]
[InlineData(typeof(ServiceEventsFromAnnotatedInterface))] // Check that annotations are not inherited from the interface
public void NonAnnotatedTypesTest(Type type)
{
// Arrange
var options = new AsyncApiOptions();
var documentGenerator = new DocumentGenerator();

// Act
var document = documentGenerator.GenerateDocument(new[] { type.GetTypeInfo() }, options, options.AsyncApi, ActivatorServiceProvider.Instance);

// Assert
document.ShouldNotBeNull();
document.Channels.Count.ShouldBe(0);
}

[Theory]
[InlineData(typeof(IAnnotatedServiceEvents), "interface")]
[InlineData(typeof(AnnotatedServiceEventsFromInterface), "class")]
[InlineData(typeof(AnnotatedServiceEventsFromAnnotatedInterface), "class")] // Check that the actual type's annotation takes precedence of the inherited interface
public void AnnotatedTypesTest(Type type, string source)
{
// Arrange
var options = new AsyncApiOptions();
var documentGenerator = new DocumentGenerator();

// Act
var document = documentGenerator.GenerateDocument(new[] { type.GetTypeInfo() }, options, options.AsyncApi, ActivatorServiceProvider.Instance);

// Assert
document.ShouldNotBeNull();
document.Channels.Count.ShouldBe(1);

var channel = document.Channels.First();
channel.Key.ShouldBe($"{source}.event");
channel.Value.Description.ShouldBeNull();

var publish = channel.Value.Publish;
publish.ShouldNotBeNull();
publish.OperationId.ShouldBe("PublishEvent");
publish.Description.ShouldBe($"({source}) Subscribe to domains events about a tenant.");

var messageRef = publish.Message.ShouldBeOfType<MessageReference>();
messageRef.Id.ShouldBe("tenantEvent");
}

[AsyncApi]
private interface IAnnotatedServiceEvents
{
[Channel("interface.event")]
[PublishOperation(typeof(TenantEvent), Description = "(interface) Subscribe to domains events about a tenant.")]
void PublishEvent(TenantEvent evt);
}

private interface IServiceEvents
{
void PublishEvent(TenantEvent evt);
}

private class ServiceEventsFromInterface : IServiceEvents
{
public void PublishEvent(TenantEvent evt) { }
}

private class ServiceEventsFromAnnotatedInterface : IAnnotatedServiceEvents
{
public void PublishEvent(TenantEvent evt) { }
}

[AsyncApi]
private class AnnotatedServiceEventsFromInterface : IAnnotatedServiceEvents
{
[Channel("class.event")]
[PublishOperation(typeof(TenantEvent), Description = "(class) Subscribe to domains events about a tenant.")]
public void PublishEvent(TenantEvent evt) { }
}

[AsyncApi]
private class AnnotatedServiceEventsFromAnnotatedInterface : IAnnotatedServiceEvents
{
[Channel("class.event")]
[PublishOperation(typeof(TenantEvent), Description = "(class) Subscribe to domains events about a tenant.")]
public void PublishEvent(TenantEvent evt) { }
}

private class TenantEvent { }
}
}

0 comments on commit 24b915d

Please sign in to comment.