Skip to content

Commit

Permalink
Merge pull request #30 from leancodepl/publisher/doc-the-code
Browse files Browse the repository at this point in the history
Document Publishers public API in the code
  • Loading branch information
Dragemil authored Sep 27, 2023
2 parents 711848a + baf0271 commit bc66efc
Show file tree
Hide file tree
Showing 10 changed files with 108 additions and 6 deletions.
15 changes: 12 additions & 3 deletions publisher/src/LeanCode.Pipe.TestClient/LeanPipeTestClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,9 @@

namespace LeanCode.Pipe.TestClient;

/// <summary>
/// LeanPipe client designed to be used in the integration tests.
/// </summary>
public class LeanPipeTestClient : IAsyncDisposable
{
private readonly ConcurrentDictionary<ITopic, LeanPipeSubscription> subscriptions =
Expand All @@ -23,6 +26,11 @@ public class LeanPipeTestClient : IAsyncDisposable

public IReadOnlyDictionary<ITopic, LeanPipeSubscription> Subscriptions => subscriptions;

/// <param name="leanPipeUrl">URL on which LeanPipe is exposed at.</param>
/// <param name="leanPipeTypes">Type catalog containing all topics and notifications.</param>
/// <param name="config">Underneath hub connection config.</param>
/// <param name="serializerOptions">Serializer options used for deserializing notifications.</param>
/// <param name="subscriptionCompletionTimeout">Time to wait for subscribe/unsubscribe response before considering failure.</param>
public LeanPipeTestClient(
Uri leanPipeUrl,
TypesCatalog leanPipeTypes,
Expand Down Expand Up @@ -57,6 +65,7 @@ public LeanPipeTestClient(
);
}

/// <returns>Unsubscription result, if it doesn't time out.</returns>
public async Task<SubscriptionResult?> UnsubscribeAsync<TTopic>(
TTopic topic,
CancellationToken ct = default
Expand All @@ -82,6 +91,8 @@ public LeanPipeTestClient(
return result;
}

/// <remarks>Connects if there is no active connection.</remarks>
/// <returns>Subscription result, if it doesn't time out.</returns>
public async Task<SubscriptionResult?> SubscribeAsync<TTopic>(
TTopic topic,
CancellationToken ct = default
Expand Down Expand Up @@ -119,9 +130,7 @@ public LeanPipeTestClient(

public Task ConnectAsync(CancellationToken ct = default) => hubConnection.StartAsync(ct);

/// <remarks>
/// Also clears all subscriptions.
/// </remarks>
/// <remarks>Also clears all subscriptions.</remarks>
public Task DisconnectAsync(CancellationToken ct = default)
{
foreach (var subscription in subscriptions.Values)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,12 @@ public static class LeanPipeTestClientExtensions
{
private static readonly TimeSpan DefaultNotificationAwaitTimeout = TimeSpan.FromSeconds(10);

/// <summary>
/// Subscribe to a topic instance or throw if it fails for any reason.
/// </summary>
/// <param name="topic">Topic instance to subscribe to.</param>
/// <returns>Subscription ID.</returns>
/// <exception cref="InvalidOperationException">The subscription failed for any reason.</exception>
public static async Task<Guid> SubscribeSuccessAsync<TTopic>(
this LeanPipeTestClient client,
TTopic topic,
Expand All @@ -30,6 +36,12 @@ public static async Task<Guid> SubscribeSuccessAsync<TTopic>(
}
}

/// <summary>
/// Unsubscribe from a topic instance or throw if it fails for any reason.
/// </summary>
/// <param name="topic">Topic instance to unsubscribe from.</param>
/// <returns>Subscription ID.</returns>
/// <exception cref="InvalidOperationException">The unsubscription failed for any reason.</exception>
public static async Task<Guid> UnsubscribeSuccessAsync<TTopic>(
this LeanPipeTestClient client,
TTopic topic,
Expand All @@ -54,6 +66,17 @@ public static async Task<Guid> UnsubscribeSuccessAsync<TTopic>(
}
}

/// <summary>
/// Returns a task, which completes when the next notification on the topic is received.
/// </summary>
/// <remarks>
/// The task should be collected before the action which triggers notification publish
/// and awaited after the trigger.
/// Otherwise there is a possibility that the notification after the expected one is awaited.</remarks>
/// <param name="topic">Topic instance on which notification is to be awaited.</param>
/// <param name="timeout">Timeout after which the notification is assumed to be not delivered.</param>
/// <returns>Task containing received notification.</returns>
/// <exception cref="InvalidOperationException">The topic instance received no notifications during the timeout.</exception>
public static async Task<object> WaitForNextNotificationOn<TTopic>(
this LeanPipeTestClient client,
TTopic topic,
Expand Down
16 changes: 16 additions & 0 deletions publisher/src/LeanCode.Pipe/BasicTopicKeys.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,17 @@

namespace LeanCode.Pipe;

/// <summary>
/// Convenience class for implementing topic keys for simple topics.
/// </summary>
/// <typeparam name="TT">Topic</typeparam>
public abstract class BasicTopicKeys<TT> : ISubscribingKeys<TT>
where TT : ITopic
{
/// <summary>
/// Used to generate SignalR groups keys when client subscribes, unsubscribes
/// or when the message is published.
/// </summary>
public abstract IEnumerable<string> Get(TT topic);

public ValueTask<IEnumerable<string>> GetForSubscribingAsync(
Expand All @@ -13,10 +21,12 @@ LeanPipeContext context
) => ValueTask.FromResult(Get(topic));
}

/// <inheritdoc cref="BasicTopicKeys{TT}"/>
public abstract class BasicTopicKeys<TT, TN1> : IPublishingKeys<TT, TN1>
where TT : ITopic, IProduceNotification<TN1>
where TN1 : notnull
{
/// <inheritdoc cref="BasicTopicKeys{TT}.Get"/>
public abstract IEnumerable<string> Get(TT topic);

public ValueTask<IEnumerable<string>> GetForSubscribingAsync(
Expand All @@ -31,6 +41,7 @@ CancellationToken ct
) => ValueTask.FromResult(Get(topic));
}

/// <inheritdoc cref="BasicTopicKeys{TT}"/>
public abstract class BasicTopicKeys<TT, TN1, TN2>
: BasicTopicKeys<TT, TN1>,
IPublishingKeys<TT, TN2>
Expand All @@ -45,6 +56,7 @@ public ValueTask<IEnumerable<string>> GetForPublishingAsync(
) => ValueTask.FromResult(Get(topic));
}

/// <inheritdoc cref="BasicTopicKeys{TT}"/>
public abstract class BasicTopicKeys<TT, TN1, TN2, TN3>
: BasicTopicKeys<TT, TN1, TN2>,
IPublishingKeys<TT, TN3>
Expand All @@ -63,6 +75,7 @@ public ValueTask<IEnumerable<string>> GetForPublishingAsync(
) => ValueTask.FromResult(Get(topic));
}

/// <inheritdoc cref="BasicTopicKeys{TT}"/>
public abstract class BasicTopicKeys<TT, TN1, TN2, TN3, TN4>
: BasicTopicKeys<TT, TN1, TN2, TN3>,
IPublishingKeys<TT, TN4>
Expand All @@ -83,6 +96,7 @@ public ValueTask<IEnumerable<string>> GetForPublishingAsync(
) => ValueTask.FromResult(Get(topic));
}

/// <inheritdoc cref="BasicTopicKeys{TT}"/>
public abstract class BasicTopicKeys<TT, TN1, TN2, TN3, TN4, TN5>
: BasicTopicKeys<TT, TN1, TN2, TN3, TN4>,
IPublishingKeys<TT, TN5>
Expand All @@ -105,6 +119,7 @@ public ValueTask<IEnumerable<string>> GetForPublishingAsync(
) => ValueTask.FromResult(Get(topic));
}

/// <inheritdoc cref="BasicTopicKeys{TT}"/>
public abstract class BasicTopicKeys<TT, TN1, TN2, TN3, TN4, TN5, TN6>
: BasicTopicKeys<TT, TN1, TN2, TN3, TN4, TN5>,
IPublishingKeys<TT, TN6>
Expand All @@ -129,6 +144,7 @@ public ValueTask<IEnumerable<string>> GetForPublishingAsync(
) => ValueTask.FromResult(Get(topic));
}

/// <inheritdoc cref="BasicTopicKeys{TT}"/>
public abstract class BasicTopicKeys<TT, TN1, TN2, TN3, TN4, TN5, TN6, TN7>
: BasicTopicKeys<TT, TN1, TN2, TN3, TN4, TN5, TN6>,
IPublishingKeys<TT, TN7>
Expand Down
10 changes: 10 additions & 0 deletions publisher/src/LeanCode.Pipe/ITopicKeys.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,26 @@

namespace LeanCode.Pipe;

/// <remarks>
/// Should be implemented in a single class per topic with <see cref="IPublishingKeys{TTopic,TNotification}"/>
/// for all topics notifications.
/// </remarks>
public interface ISubscribingKeys<in TTopic>
where TTopic : ITopic
{
/// <summary>
/// Used to generate SignalR groups keys when client subscribes or unsubscribes.
/// </summary>
ValueTask<IEnumerable<string>> GetForSubscribingAsync(TTopic topic, LeanPipeContext context);
}

public interface IPublishingKeys<in TTopic, TNotification> : ISubscribingKeys<TTopic>
where TTopic : ITopic, IProduceNotification<TNotification>
where TNotification : notnull
{
/// <summary>
/// Used to generate SignalR groups keys to which send message to.
/// </summary>
ValueTask<IEnumerable<string>> GetForPublishingAsync(
TTopic topic,
TNotification notification,
Expand Down
3 changes: 3 additions & 0 deletions publisher/src/LeanCode.Pipe/LeanPipeContext.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

namespace LeanCode.Pipe;

/// <summary>
/// Context that is included in all LeanPipe subscriptions.
/// </summary>
public class LeanPipeContext
{
public LeanPipeContext(HttpContext httpContext)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
using System.Diagnostics.CodeAnalysis;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Http.Connections;
using Microsoft.AspNetCore.Routing;
Expand All @@ -6,17 +7,23 @@ namespace LeanCode.Pipe;

public static class LeanPipeEndpointRouteBuilderExtensions
{
/// <summary>
/// Maps LeanPipe SignalR hub on the specified endpoint.
/// </summary>
public static IHubEndpointConventionBuilder MapLeanPipe(
this IEndpointRouteBuilder endpoints,
string pattern
[StringSyntax("Route")] string pattern
)
{
return endpoints.MapHub<LeanPipeSubscriber>(pattern);
}

/// <summary>
/// Maps LeanPipe SignalR hub on the specified endpoint.
/// </summary>
public static IHubEndpointConventionBuilder MapLeanPipe(
this IEndpointRouteBuilder endpoints,
string pattern,
[StringSyntax("Route")] string pattern,
Action<HttpConnectionDispatcherOptions>? configureOptions
)
{
Expand Down
12 changes: 12 additions & 0 deletions publisher/src/LeanCode.Pipe/LeanPipePublisher.cs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,10 @@

namespace LeanCode.Pipe;

/// <summary>
/// Allows publishing notifications to instances of <see cref="TTopic"/>.
/// Conveniently used with extension methods from <see cref="LeanPipePublisherExtensions"/>.
/// </summary>
public class LeanPipePublisher<TTopic>
where TTopic : ITopic
{
Expand Down Expand Up @@ -31,6 +35,10 @@ internal async Task PublishAsync(

public static class LeanPipePublisherExtensions
{
/// <summary>
/// Publishes to topic instance using provided SignalR groups keys.
/// Omits publishing keys implemented in <see cref="IPublishingKeys{TTopic,TNotification}"/>.
/// </summary>
public static async Task PublishAsync<TTopic, TNotification>(
this LeanPipePublisher<TTopic> publisher,
IEnumerable<string> keys,
Expand All @@ -46,6 +54,10 @@ public static async Task PublishAsync<TTopic, TNotification>(
await publisher.PublishAsync(keys, payload, ct);
}

/// <summary>
/// Publishes to topic instance using SignalR groups keys generated via implementation of
/// <see cref="IPublishingKeys{TTopic,TNotification}"/>.
/// </summary>
public static async Task PublishAsync<TTopic, TNotification>(
this LeanPipePublisher<TTopic> publisher,
TTopic topic,
Expand Down
21 changes: 21 additions & 0 deletions publisher/src/LeanCode.Pipe/LeanPipeServiceCollectionExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@ namespace LeanCode.Pipe;

public static class LeanPipeServiceCollectionExtensions
{
/// <summary>
/// Adds all classes required for LeanPipe to function to DI.
/// </summary>
/// <returns>Service builder allowing for overriding default LeanPipe implementations
/// and further configuration.</returns>
public static LeanPipeServicesBuilder AddLeanPipe(
this IServiceCollection services,
TypesCatalog topics,
Expand All @@ -28,6 +33,10 @@ TypesCatalog handlers
}
}

/// <summary>
/// Allows for overriding default LeanPipe implementations
/// and further configuration.
/// </summary>
public class LeanPipeServicesBuilder
{
public IServiceCollection Services { get; }
Expand All @@ -43,26 +52,38 @@ public LeanPipeServicesBuilder(IServiceCollection services, TypesCatalog topics)
Services.AddSingleton<IEnvelopeDeserializer>(new DefaultEnvelopeDeserializer(topics, null));
}

/// <summary>
/// Overrides serializing options used in subscription envelope deserializer.
/// </summary>
public LeanPipeServicesBuilder WithEnvelopeDeserializerOptions(JsonSerializerOptions options)
{
this.options = options;
ReplaceDefaultEnvelopeDeserializer();
return this;
}

/// <summary>
/// Overrides subscription envelope deserializer.
/// </summary>
public LeanPipeServicesBuilder WithEnvelopeDeserializer(IEnvelopeDeserializer deserializer)
{
Services.Replace(new ServiceDescriptor(typeof(IEnvelopeDeserializer), deserializer));
return this;
}

/// <summary>
/// Adds topics from another <see cref="TypesCatalog"/>.
/// </summary>
public LeanPipeServicesBuilder AddTopics(TypesCatalog newTopics)
{
topics = topics.Merge(newTopics);
ReplaceDefaultEnvelopeDeserializer();
return this;
}

/// <summary>
/// Adds subscription handlers and keys generators from another <see cref="TypesCatalog"/>.
/// </summary>
public LeanPipeServicesBuilder AddHandlers(TypesCatalog newHandlers)
{
Services.RegisterGenericTypes(
Expand Down
2 changes: 1 addition & 1 deletion publisher/src/LeanCode.Pipe/SubscriptionHandler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ ValueTask<bool> OnSubscribedAsync(
LeanPipeContext context
);

/// <returns>Whether subscription was correctly established.</returns>
/// <returns>Whether subscription was correctly severed.</returns>
ValueTask<bool> OnUnsubscribedAsync(
TTopic topic,
LeanPipeSubscriber leanPipeSubscriber,
Expand Down
1 change: 1 addition & 0 deletions publisher/src/LeanCode.Pipe/SubscriptionHandlerWrapper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ ValueTask<bool> OnSubscribedAsync(
LeanPipeSubscriber leanPipeSubscriber,
LeanPipeContext context
);

ValueTask<bool> OnUnsubscribedAsync(
object topic,
LeanPipeSubscriber leanPipeSubscriber,
Expand Down

0 comments on commit bc66efc

Please sign in to comment.