| | 1 | | // Copyright (c) ZeroC, Inc. |
| | 2 | |
|
| | 3 | | using IceRpc.Internal; |
| | 4 | | using IceRpc.Slice.Internal; |
| | 5 | | using System.Buffers; |
| | 6 | | using System.Diagnostics; |
| | 7 | | using System.IO.Pipelines; |
| | 8 | | using ZeroC.Slice; |
| | 9 | |
|
| | 10 | | namespace IceRpc.Slice; |
| | 11 | |
|
| | 12 | | /// <summary>Provides extension methods for <see cref="IncomingResponse" /> to decode its Slice-encoded payload. |
| | 13 | | /// </summary> |
| | 14 | | public static class IncomingResponseExtensions |
| | 15 | | { |
| | 16 | | /// <summary>Decodes a response payload.</summary> |
| | 17 | | /// <typeparam name="T">The type of the return value.</typeparam> |
| | 18 | | /// <param name="response">The incoming response.</param> |
| | 19 | | /// <param name="request">The outgoing request.</param> |
| | 20 | | /// <param name="encoding">The encoding of the response payload.</param> |
| | 21 | | /// <param name="sender">The proxy that sent the request.</param> |
| | 22 | | /// <param name="decodeReturnValue">A function that decodes the return value.</param> |
| | 23 | | /// <param name="defaultActivator">The activator to use when the activator provided by the request's <see |
| | 24 | | /// cref="ISliceFeature" /> is <see langword="null" />. Used only when <paramref name="encoding" /> is <see |
| | 25 | | /// cref="SliceEncoding.Slice1" />.</param> |
| | 26 | | /// <param name="cancellationToken">A cancellation token that receives the cancellation requests.</param> |
| | 27 | | /// <returns>A value task that holds the operation's return value. This value task is faulted and holds a <see |
| | 28 | | /// cref="SliceException" /> when encoding is Slice1 and the status code is |
| | 29 | | /// <see cref="StatusCode.ApplicationError"/>.</returns> |
| | 30 | | /// <exception cref="DispatchException">Thrown when the encoding is Slice1 and the status code of the response is |
| | 31 | | /// greater than <see cref="StatusCode.ApplicationError"/>, or when the encoding is Slice2 and the status code is |
| | 32 | | /// greater than <see cref="StatusCode.Ok"/>.</exception> |
| | 33 | | public static ValueTask<T> DecodeReturnValueAsync<T>( |
| | 34 | | this IncomingResponse response, |
| | 35 | | OutgoingRequest request, |
| | 36 | | SliceEncoding encoding, |
| | 37 | | IProxy sender, |
| | 38 | | DecodeFunc<T> decodeReturnValue, |
| | 39 | | IActivator? defaultActivator = null, |
| | 40 | | CancellationToken cancellationToken = default) |
| 95 | 41 | | { |
| 95 | 42 | | ISliceFeature feature = request.Features.Get<ISliceFeature>() ?? SliceFeature.Default; |
| 95 | 43 | | IActivator? activator = feature.Activator ?? defaultActivator; |
| | 44 | |
|
| 95 | 45 | | return response.StatusCode switch |
| 95 | 46 | | { |
| 94 | 47 | | StatusCode.Ok => response.DecodeValueAsync( |
| 94 | 48 | | encoding, |
| 94 | 49 | | feature, |
| 94 | 50 | | feature.BaseProxy ?? sender, |
| 94 | 51 | | decodeReturnValue, |
| 94 | 52 | | activator, |
| 94 | 53 | | cancellationToken), |
| 95 | 54 | |
|
| 0 | 55 | | StatusCode.ApplicationError when encoding == SliceEncoding.Slice1 => DecodeAndThrowExceptionAsync(), |
| 95 | 56 | |
|
| 1 | 57 | | _ => throw new DispatchException(response.StatusCode, response.ErrorMessage) |
| 1 | 58 | | { |
| 1 | 59 | | ConvertToInternalError = true |
| 1 | 60 | | } |
| 95 | 61 | | }; |
| | 62 | |
|
| | 63 | | async ValueTask<T> DecodeAndThrowExceptionAsync() => |
| 0 | 64 | | throw await response.DecodeSliceExceptionAsync(feature, sender, activator, cancellationToken) |
| 0 | 65 | | .ConfigureAwait(false); |
| 94 | 66 | | } |
| | 67 | |
|
| | 68 | | /// <summary>Verifies that a Slice-encoded response payload carries no return value or only tagged return values. |
| | 69 | | /// </summary> |
| | 70 | | /// <param name="response">The incoming response.</param> |
| | 71 | | /// <param name="request">The outgoing request.</param> |
| | 72 | | /// <param name="encoding">The encoding of the response payload.</param> |
| | 73 | | /// <param name="sender">The proxy that sent the request.</param> |
| | 74 | | /// <param name="defaultActivator">The activator to use when the activator provided by the request's <see |
| | 75 | | /// cref="ISliceFeature" /> is <see langword="null" />. Used only when <paramref name="encoding" /> is <see |
| | 76 | | /// cref="SliceEncoding.Slice1" />.</param> |
| | 77 | | /// <param name="cancellationToken">A cancellation token that receives the cancellation requests.</param> |
| | 78 | | /// <returns>A value task representing the asynchronous completion of the operation. This value task is faulted and |
| | 79 | | /// holds a <see cref="SliceException" /> when encoding is Slice1 and the status code is |
| | 80 | | /// <see cref="StatusCode.ApplicationError" />.</returns> |
| | 81 | | /// <exception cref="DispatchException">Thrown when the encoding is Slice1 and the status code of the response is |
| | 82 | | /// greater than <see cref="StatusCode.ApplicationError"/>, or when the encoding is Slice2 and the status code is |
| | 83 | | /// greater than <see cref="StatusCode.Ok"/>.</exception> |
| | 84 | | public static ValueTask DecodeVoidReturnValueAsync( |
| | 85 | | this IncomingResponse response, |
| | 86 | | OutgoingRequest request, |
| | 87 | | SliceEncoding encoding, |
| | 88 | | IProxy sender, |
| | 89 | | IActivator? defaultActivator = null, |
| | 90 | | CancellationToken cancellationToken = default) |
| 79 | 91 | | { |
| 79 | 92 | | ISliceFeature feature = request.Features.Get<ISliceFeature>() ?? SliceFeature.Default; |
| 79 | 93 | | IActivator? activator = defaultActivator ?? feature.Activator; |
| | 94 | |
|
| 79 | 95 | | return response.StatusCode switch |
| 79 | 96 | | { |
| 49 | 97 | | StatusCode.Ok => response.DecodeVoidAsync(encoding, feature, cancellationToken), |
| 32 | 98 | | StatusCode.ApplicationError when encoding == SliceEncoding.Slice1 => DecodeAndThrowExceptionAsync(), |
| 14 | 99 | | _ => throw new DispatchException(response.StatusCode, response.ErrorMessage) |
| 14 | 100 | | { |
| 14 | 101 | | ConvertToInternalError = true |
| 14 | 102 | | } |
| 79 | 103 | | }; |
| | 104 | |
|
| | 105 | | async ValueTask DecodeAndThrowExceptionAsync() => |
| 16 | 106 | | throw await response.DecodeSliceExceptionAsync(feature, sender, activator, cancellationToken) |
| 16 | 107 | | .ConfigureAwait(false); |
| 65 | 108 | | } |
| | 109 | |
|
| | 110 | | /// <summary>Verifies that a Slice2-encoded response payload carries no return value or only tagged return values. |
| | 111 | | /// </summary> |
| | 112 | | /// <param name="response">The incoming response.</param> |
| | 113 | | /// <param name="request">The outgoing request.</param> |
| | 114 | | /// <param name="sender">The proxy that sent the request (not used by this method).</param> |
| | 115 | | /// <param name="cancellationToken">A cancellation token that receives the cancellation requests.</param> |
| | 116 | | /// <returns>A value task representing the asynchronous completion of the operation.</returns> |
| | 117 | | /// <exception cref="DispatchException">Thrown if the status code of the response is greater than <see |
| | 118 | | /// cref="StatusCode.Ok" />.</exception> |
| | 119 | | /// <remarks>This method is a <see cref="ResponseDecodeFunc" />.</remarks> |
| | 120 | | public static ValueTask DecodeVoidReturnValueAsync( |
| | 121 | | this IncomingResponse response, |
| | 122 | | OutgoingRequest request, |
| | 123 | | IProxy sender, |
| | 124 | | CancellationToken cancellationToken = default) => |
| 15 | 125 | | response.DecodeVoidReturnValueAsync(request, SliceEncoding.Slice2, sender, null, cancellationToken); |
| | 126 | |
|
| | 127 | | // Slice1-only |
| | 128 | | private static async ValueTask<SliceException> DecodeSliceExceptionAsync( |
| | 129 | | this IncomingResponse response, |
| | 130 | | ISliceFeature feature, |
| | 131 | | IProxy sender, |
| | 132 | | IActivator? activator, |
| | 133 | | CancellationToken cancellationToken) |
| 16 | 134 | | { |
| 16 | 135 | | Debug.Assert(response.StatusCode == StatusCode.ApplicationError); |
| | 136 | |
|
| 16 | 137 | | ReadResult readResult = await response.Payload.ReadSegmentAsync( |
| 16 | 138 | | SliceEncoding.Slice1, |
| 16 | 139 | | feature.MaxSegmentSize, |
| 16 | 140 | | cancellationToken).ConfigureAwait(false); |
| | 141 | |
|
| | 142 | | // We never call CancelPendingRead on response.Payload; an interceptor can but it's not correct. |
| 16 | 143 | | if (readResult.IsCanceled) |
| 0 | 144 | | { |
| 0 | 145 | | throw new InvalidOperationException("Unexpected call to CancelPendingRead."); |
| | 146 | | } |
| | 147 | |
|
| 16 | 148 | | SliceException exception = DecodeBuffer(readResult.Buffer); |
| 16 | 149 | | response.Payload.AdvanceTo(readResult.Buffer.End); |
| 16 | 150 | | return exception; |
| | 151 | |
|
| | 152 | | SliceException DecodeBuffer(ReadOnlySequence<byte> buffer) |
| 16 | 153 | | { |
| | 154 | | // A Slice exception never sets Message, even when received over icerpc. |
| | 155 | |
|
| 16 | 156 | | var decoder = new SliceDecoder( |
| 16 | 157 | | buffer, |
| 16 | 158 | | SliceEncoding.Slice1, |
| 16 | 159 | | feature.BaseProxy ?? sender, |
| 16 | 160 | | maxCollectionAllocation: feature.MaxCollectionAllocation, |
| 16 | 161 | | activator, |
| 16 | 162 | | maxDepth: feature.MaxDepth); |
| | 163 | |
|
| 16 | 164 | | SliceException exception = decoder.DecodeException(response.ErrorMessage); |
| 16 | 165 | | decoder.CheckEndOfBuffer(); |
| 16 | 166 | | return exception; |
| 16 | 167 | | } |
| 16 | 168 | | } |
| | 169 | | } |