using Grpc.Core; using Grpc.Net.Client; using Microsoft.Extensions.Logging; using MxGateway.Contracts.Proto; using Polly; namespace MxGateway.Client; /// /// Provides the .NET client entry point for the public MXAccess Gateway gRPC API. /// public sealed class MxGatewayClient : IAsyncDisposable { private readonly GrpcChannel _channel; private readonly IMxGatewayClientTransport _transport; private readonly ResiliencePipeline _safeUnaryRetryPipeline; private bool _disposed; internal MxGatewayClient( MxGatewayClientOptions options, IMxGatewayClientTransport transport) { ArgumentNullException.ThrowIfNull(options); options.Validate(); Options = options; _transport = transport ?? throw new ArgumentNullException(nameof(transport)); _safeUnaryRetryPipeline = MxGatewayClientRetryPolicy.Create( options.Retry, options.LoggerFactory?.CreateLogger()); _channel = null!; } private MxGatewayClient( GrpcChannel channel, IMxGatewayClientTransport transport) { _channel = channel; _transport = transport; Options = transport.Options; _safeUnaryRetryPipeline = MxGatewayClientRetryPolicy.Create( Options.Retry, Options.LoggerFactory?.CreateLogger()); } public MxGatewayClientOptions Options { get; } public MxAccessGateway.MxAccessGatewayClient RawClient => _transport.RawClient ?? throw new InvalidOperationException("The raw generated gRPC client is not available for this client instance."); public static MxGatewayClient Create(MxGatewayClientOptions options) { ArgumentNullException.ThrowIfNull(options); options.Validate(); var channel = GrpcChannel.ForAddress( options.Endpoint, new GrpcChannelOptions { LoggerFactory = options.LoggerFactory, }); return new MxGatewayClient( channel, new GrpcMxGatewayClientTransport( options, new MxAccessGateway.MxAccessGatewayClient(channel))); } public async Task OpenSessionAsync( OpenSessionRequest? request = null, CancellationToken cancellationToken = default) { OpenSessionReply reply = await OpenSessionRawAsync( request ?? new OpenSessionRequest(), cancellationToken) .ConfigureAwait(false); return new MxGatewaySession(this, reply); } public Task OpenSessionRawAsync( OpenSessionRequest request, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(request); ThrowIfDisposed(); return _transport.OpenSessionAsync(request, CreateCallOptions(cancellationToken)); } public Task CloseSessionRawAsync( CloseSessionRequest request, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(request); ThrowIfDisposed(); return ExecuteSafeUnaryAsync( token => _transport.CloseSessionAsync(request, CreateCallOptions(token)), cancellationToken); } public Task InvokeAsync( MxCommandRequest request, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(request); ThrowIfDisposed(); if (MxGatewayClientRetryPolicy.IsRetryableCommand(request.Command?.Kind ?? MxCommandKind.Unspecified)) { return ExecuteSafeUnaryAsync( token => _transport.InvokeAsync(request, CreateCallOptions(token)), cancellationToken); } return _transport.InvokeAsync(request, CreateCallOptions(cancellationToken)); } public IAsyncEnumerable StreamEventsAsync( StreamEventsRequest request, CancellationToken cancellationToken = default) { ArgumentNullException.ThrowIfNull(request); ThrowIfDisposed(); return _transport.StreamEventsAsync(request, CreateCallOptions(cancellationToken)); } public ValueTask DisposeAsync() { if (_disposed) { return ValueTask.CompletedTask; } _disposed = true; _channel?.Dispose(); return ValueTask.CompletedTask; } internal CallOptions CreateCallOptions(CancellationToken cancellationToken) { Metadata headers = new() { { "authorization", $"Bearer {Options.ApiKey}" }, }; return new CallOptions( headers, DateTime.UtcNow.Add(Options.DefaultCallTimeout), cancellationToken); } private Task ExecuteSafeUnaryAsync( Func> call, CancellationToken cancellationToken) { return _safeUnaryRetryPipeline.ExecuteAsync( async token => await call(token).ConfigureAwait(false), cancellationToken) .AsTask(); } private void ThrowIfDisposed() { ObjectDisposedException.ThrowIf(_disposed, this); } }