< Summary

Information
Class: IceRpc.Slice.ProxyExtensions
Assembly: IceRpc.Slice
File(s): /home/runner/work/icerpc-csharp/icerpc-csharp/src/IceRpc.Slice/ProxyExtensions.cs
Tag: 275_13775359185
Line coverage
81%
Covered lines: 69
Uncovered lines: 16
Coverable lines: 85
Total lines: 215
Line coverage: 81.1%
Branch coverage
83%
Covered branches: 20
Total branches: 24
Branch coverage: 83.3%
Method coverage
100%
Covered methods: 6
Total methods: 6
Method coverage: 100%

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
.cctor()100%11100%
InvokeAsync(...)83.33%19.321262.96%
ReadResponseAsync()100%11100%
InvokeAsync(...)83.33%13.581277.77%
ReadResponseAsync()100%11100%
ToProxy(...)100%11100%

File(s)

/home/runner/work/icerpc-csharp/icerpc-csharp/src/IceRpc.Slice/ProxyExtensions.cs

#LineLine coverage
 1// Copyright (c) ZeroC, Inc.
 2
 3using IceRpc.Features;
 4using IceRpc.Internal;
 5using System.Collections.Immutable;
 6using System.IO.Pipelines;
 7using ZeroC.Slice;
 8
 9namespace IceRpc.Slice;
 10
 11/// <summary>Represents a delegate that decodes the return value from a Slice-encoded response.</summary>
 12/// <typeparam name="T">The type of the return value to read.</typeparam>
 13/// <param name="response">The incoming response.</param>
 14/// <param name="request">The outgoing request.</param>
 15/// <param name="sender">The proxy that sent the request.</param>
 16/// <param name="cancellationToken">A cancellation token that receives the cancellation requests.</param>
 17/// <returns>A value task that contains the return value or a <see cref="SliceException" /> when the status code of the
 18/// response is <see cref="StatusCode.ApplicationError" />.</returns>
 19public delegate ValueTask<T> ResponseDecodeFunc<T>(
 20    IncomingResponse response,
 21    OutgoingRequest request,
 22    IProxy sender,
 23    CancellationToken cancellationToken);
 24
 25/// <summary>Represents a delegate that decodes the "void" return value from a Slice-encoded response.</summary>
 26/// <param name="response">The incoming response.</param>
 27/// <param name="request">The outgoing request.</param>
 28/// <param name="sender">The proxy that sent the request.</param>
 29/// <param name="cancellationToken">A cancellation token that receives the cancellation requests.</param>
 30/// <returns>A value task that contains a <see cref="SliceException" /> when the status code of the response is
 31/// <see cref="StatusCode.ApplicationError" />.</returns>
 32public delegate ValueTask ResponseDecodeFunc(
 33    IncomingResponse response,
 34    OutgoingRequest request,
 35    IProxy sender,
 36    CancellationToken cancellationToken);
 37
 38/// <summary>Provides extension methods for <see cref="IProxy" /> and generated proxy structs that implement this
 39/// interface.</summary>
 40public static class ProxyExtensions
 41{
 242    private static readonly IDictionary<RequestFieldKey, OutgoingFieldValue> _idempotentFields =
 243        new Dictionary<RequestFieldKey, OutgoingFieldValue>
 244        {
 245            [RequestFieldKey.Idempotent] = default
 246        }.ToImmutableDictionary();
 47
 48    /// <summary>Sends a request to a service and decodes the response.</summary>
 49    /// <typeparam name="TProxy">The type of the proxy struct.</typeparam>
 50    /// <typeparam name="T">The response type.</typeparam>
 51    /// <param name="proxy">A proxy to the remote service.</param>
 52    /// <param name="operation">The name of the operation, as specified in Slice.</param>
 53    /// <param name="payload">The payload of the request. <see langword="null" /> is equivalent to an empty
 54    /// payload.</param>
 55    /// <param name="payloadContinuation">The optional payload continuation of the request.</param>
 56    /// <param name="responseDecodeFunc">The decode function for the response payload. It decodes and throws an
 57    /// exception when the status code of the response is <see cref="StatusCode.ApplicationError" />.</param>
 58    /// <param name="features">The invocation features.</param>
 59    /// <param name="idempotent">When <see langword="true" />, the request is idempotent.</param>
 60    /// <param name="cancellationToken">A cancellation token that receives the cancellation requests.</param>
 61    /// <returns>The operation's return value.</returns>
 62    /// <exception cref="SliceException">Thrown if the response carries a Slice exception.</exception>
 63    public static Task<T> InvokeAsync<TProxy, T>(
 64        this TProxy proxy,
 65        string operation,
 66        PipeReader? payload,
 67        PipeReader? payloadContinuation,
 68        ResponseDecodeFunc<T> responseDecodeFunc,
 69        IFeatureCollection? features = null,
 70        bool idempotent = false,
 71        CancellationToken cancellationToken = default) where TProxy : struct, IProxy
 4872    {
 4873        if (proxy.Invoker is not IInvoker invoker)
 074        {
 075            throw new InvalidOperationException("Cannot send requests using a proxy with a null invoker.");
 76        }
 77
 4878        if (payload is null && payloadContinuation is not null)
 079        {
 080            throw new ArgumentNullException(
 081                nameof(payload),
 082                $"When {nameof(payloadContinuation)} is not null, {nameof(payload)} cannot be null.");
 83        }
 84
 4885        var request = new OutgoingRequest(proxy.ServiceAddress)
 4886        {
 4887            Features = features ?? FeatureCollection.Empty,
 4888            Fields = idempotent ?
 4889                _idempotentFields : ImmutableDictionary<RequestFieldKey, OutgoingFieldValue>.Empty,
 4890            Operation = operation,
 4891            Payload = payload ?? EmptyPipeReader.Instance,
 4892            PayloadContinuation = payloadContinuation
 4893        };
 94
 95        Task<IncomingResponse> responseTask;
 96        try
 4897        {
 4898            responseTask = invoker.InvokeAsync(request, cancellationToken);
 4899        }
 0100        catch
 0101        {
 0102            request.Dispose();
 0103            throw;
 104        }
 105
 106        // ReadResponseAsync is responsible for disposing the request
 48107        return ReadResponseAsync(responseTask, request);
 108
 109        async Task<T> ReadResponseAsync(Task<IncomingResponse> responseTask, OutgoingRequest request)
 48110        {
 111            try
 48112            {
 48113                IncomingResponse response = await responseTask.ConfigureAwait(false);
 48114                return await responseDecodeFunc(
 48115                    response,
 48116                    request,
 48117                    proxy,
 48118                    cancellationToken).ConfigureAwait(false);
 119            }
 120            finally
 48121            {
 48122                request.Dispose();
 48123            }
 48124        }
 48125    }
 126
 127    /// <summary>Sends a request to a service and decodes the "void" response.</summary>
 128    /// <typeparam name="TProxy">The type of the proxy struct.</typeparam>
 129    /// <param name="proxy">A proxy for the remote service.</param>
 130    /// <param name="operation">The name of the operation, as specified in Slice.</param>
 131    /// <param name="payload">The payload of the request. <see langword="null" /> is equivalent to an empty
 132    /// payload.</param>
 133    /// <param name="payloadContinuation">The payload continuation of the request.</param>
 134    /// <param name="responseDecodeFunc">The decode function for the response payload. It decodes and throws an
 135    /// exception when the status code of the response is <see cref="StatusCode.ApplicationError" />.</param>
 136    /// <param name="features">The invocation features.</param>
 137    /// <param name="idempotent">When <see langword="true" />, the request is idempotent.</param>
 138    /// <param name="oneway">When <see langword="true" />, the request is sent one-way and an empty response is returned
 139    /// immediately after sending the request.</param>
 140    /// <param name="cancellationToken">A cancellation token that receives the cancellation requests.</param>
 141    /// <returns>A task that completes when the void response is returned.</returns>
 142    /// <exception cref="SliceException">Thrown if the response carries a failure.</exception>
 143    public static Task InvokeAsync<TProxy>(
 144        this TProxy proxy,
 145        string operation,
 146        PipeReader? payload,
 147        PipeReader? payloadContinuation,
 148        ResponseDecodeFunc responseDecodeFunc,
 149        IFeatureCollection? features = null,
 150        bool idempotent = false,
 151        bool oneway = false,
 152        CancellationToken cancellationToken = default) where TProxy : struct, IProxy
 73153    {
 73154        if (proxy.Invoker is not IInvoker invoker)
 0155        {
 0156            throw new InvalidOperationException("Cannot send requests using a proxy with a null invoker.");
 157        }
 158
 73159        if (payload is null && payloadContinuation is not null)
 0160        {
 0161            throw new ArgumentNullException(
 0162                nameof(payload),
 0163                $"When {nameof(payloadContinuation)} is not null, {nameof(payload)} cannot be null.");
 164        }
 165
 73166        var request = new OutgoingRequest(proxy.ServiceAddress)
 73167        {
 73168            Features = features ?? FeatureCollection.Empty,
 73169            Fields = idempotent ? _idempotentFields : ImmutableDictionary<RequestFieldKey, OutgoingFieldValue>.Empty,
 73170            IsOneway = oneway,
 73171            Operation = operation,
 73172            Payload = payload ?? EmptyPipeReader.Instance,
 73173            PayloadContinuation = payloadContinuation
 73174        };
 175
 176        Task<IncomingResponse> responseTask;
 177        try
 73178        {
 73179            responseTask = invoker.InvokeAsync(request, cancellationToken);
 72180        }
 1181        catch
 1182        {
 1183            request.Dispose();
 1184            throw;
 185        }
 186
 187        // ReadResponseAsync is responsible for disposing the request
 72188        return ReadResponseAsync(responseTask, request);
 189
 190        async Task ReadResponseAsync(Task<IncomingResponse> responseTask, OutgoingRequest request)
 72191        {
 192            try
 72193            {
 72194                IncomingResponse response = await responseTask.ConfigureAwait(false);
 195
 72196                await responseDecodeFunc(
 72197                    response,
 72198                    request,
 72199                    proxy,
 72200                    cancellationToken).ConfigureAwait(false);
 43201            }
 202            finally
 72203            {
 72204                request.Dispose();
 72205            }
 43206        }
 72207    }
 208
 209    /// <summary>Converts a proxy into a proxy struct. This conversion always succeeds.</summary>
 210    /// <typeparam name="TProxy">The type of the target proxy struct.</typeparam>
 211    /// <param name="proxy">The source proxy.</param>
 212    /// <returns>A new instance of <typeparamref name="TProxy" />.</returns>
 213    public static TProxy ToProxy<TProxy>(this IProxy proxy) where TProxy : struct, IProxy =>
 3214        new() { EncodeOptions = proxy.EncodeOptions, Invoker = proxy.Invoker, ServiceAddress = proxy.ServiceAddress };
 215}