File: src\Grpc\JsonTranscoding\src\Shared\Server\MethodOptions.cs
Web Access
Project: src\src\Grpc\JsonTranscoding\src\Microsoft.AspNetCore.Grpc.JsonTranscoding\Microsoft.AspNetCore.Grpc.JsonTranscoding.csproj (Microsoft.AspNetCore.Grpc.JsonTranscoding)
#region Copyright notice and license
 
// Copyright 2019 The gRPC Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
 
#endregion
 
using System.IO.Compression;
using System.Linq;
using Grpc.AspNetCore.Server;
using Grpc.Net.Compression;
 
namespace Grpc.Shared.Server;
 
/// <summary>
/// Options used to execute a gRPC method.
/// </summary>
internal sealed class MethodOptions
{
    /// <summary>
    /// Gets the list of compression providers used to compress and decompress gRPC messages.
    /// </summary>
    public IReadOnlyDictionary<string, ICompressionProvider> CompressionProviders { get; }
 
    /// <summary>
    /// Get a collection of interceptors to be executed with every call. Interceptors are executed in order.
    /// </summary>
    public IReadOnlyList<InterceptorRegistration> Interceptors { get; }
 
    /// <summary>
    /// Gets the maximum message size in bytes that can be sent from the server.
    /// </summary>
    public int? MaxSendMessageSize { get; }
 
    /// <summary>
    /// Gets the maximum message size in bytes that can be received by the server.
    /// </summary>
    public int? MaxReceiveMessageSize { get; }
 
    /// <summary>
    /// Gets a value indicating whether detailed error messages are sent to the peer.
    /// Detailed error messages include details from exceptions thrown on the server.
    /// </summary>
    public bool? EnableDetailedErrors { get; }
 
    /// <summary>
    /// Gets the compression algorithm used to compress messages sent from the server.
    /// The request grpc-accept-encoding header value must contain this algorithm for it to
    /// be used.
    /// </summary>
    public string? ResponseCompressionAlgorithm { get; }
 
    /// <summary>
    /// Gets the compression level used to compress messages sent from the server.
    /// The compression level will be passed to the compression provider.
    /// </summary>
    public CompressionLevel? ResponseCompressionLevel { get; }
 
    // Fast check for whether the service has any interceptors
    internal bool HasInterceptors { get; }
 
    private MethodOptions(
        Dictionary<string, ICompressionProvider> compressionProviders,
        InterceptorCollection interceptors,
        int? maxSendMessageSize,
        int? maxReceiveMessageSize,
        bool? enableDetailedErrors,
        string? responseCompressionAlgorithm,
        CompressionLevel? responseCompressionLevel)
    {
        CompressionProviders = compressionProviders;
        Interceptors = interceptors;
        HasInterceptors = interceptors.Count > 0;
        MaxSendMessageSize = maxSendMessageSize;
        MaxReceiveMessageSize = maxReceiveMessageSize;
        EnableDetailedErrors = enableDetailedErrors;
        ResponseCompressionAlgorithm = responseCompressionAlgorithm;
        ResponseCompressionLevel = responseCompressionLevel;
 
        if (ResponseCompressionAlgorithm != null)
        {
            if (!CompressionProviders.TryGetValue(ResponseCompressionAlgorithm, out var _))
            {
                throw new InvalidOperationException($"The configured response compression algorithm '{ResponseCompressionAlgorithm}' does not have a matching compression provider.");
            }
        }
    }
 
    /// <summary>
    /// Creates method options by merging together the settings the specificed <see cref="GrpcServiceOptions"/> collection.
    /// The <see cref="GrpcServiceOptions"/> should be ordered with items arranged in ascending order of precedence.
    /// When interceptors from multiple options are merged together they will be executed in reverse order of precendence.
    /// </summary>
    /// <param name="serviceOptions">A collection of <see cref="GrpcServiceOptions"/> instances, arranged in ascending order of precedence.</param>
    /// <returns>A new <see cref="MethodOptions"/> instanced with settings merged from specifid <see cref="GrpcServiceOptions"/> collection.</returns>
    public static MethodOptions Create(IEnumerable<GrpcServiceOptions> serviceOptions)
    {
        // This is required to get ensure that service methods without any explicit configuration
        // will continue to get the global configuration options
        var resolvedCompressionProviders = new Dictionary<string, ICompressionProvider>(StringComparer.Ordinal);
        var tempInterceptors = new List<InterceptorRegistration>();
        int? maxSendMessageSize = null;
        int? maxReceiveMessageSize = null;
        bool? enableDetailedErrors = null;
        string? responseCompressionAlgorithm = null;
        CompressionLevel? responseCompressionLevel = null;
 
        foreach (var options in Enumerable.Reverse(serviceOptions))
        {
            AddCompressionProviders(resolvedCompressionProviders, options.CompressionProviders);
            tempInterceptors.InsertRange(0, options.Interceptors);
            maxSendMessageSize ??= options.MaxSendMessageSize;
            maxReceiveMessageSize ??= options.MaxReceiveMessageSize;
            enableDetailedErrors ??= options.EnableDetailedErrors;
            responseCompressionAlgorithm ??= options.ResponseCompressionAlgorithm;
            responseCompressionLevel ??= options.ResponseCompressionLevel;
        }
 
        var interceptors = new InterceptorCollection();
        foreach (var interceptor in tempInterceptors)
        {
            interceptors.Add(interceptor);
        }
 
        return new MethodOptions
        (
            compressionProviders: resolvedCompressionProviders,
            interceptors: interceptors,
            maxSendMessageSize: maxSendMessageSize,
            maxReceiveMessageSize: maxReceiveMessageSize,
            enableDetailedErrors: enableDetailedErrors,
            responseCompressionAlgorithm: responseCompressionAlgorithm,
            responseCompressionLevel: responseCompressionLevel
        );
    }
 
    private static void AddCompressionProviders(Dictionary<string, ICompressionProvider> resolvedProviders, IList<ICompressionProvider>? compressionProviders)
    {
        if (compressionProviders != null)
        {
            foreach (var compressionProvider in compressionProviders)
            {
                if (!resolvedProviders.ContainsKey(compressionProvider.EncodingName))
                {
                    resolvedProviders.Add(compressionProvider.EncodingName, compressionProvider);
                }
            }
        }
    }
}