Burgyn / MMLib.SwaggerForOcelot

This repo contains swagger extension for ocelot.
MIT License
351 stars 93 forks source link

IsItSelfGatewayFirstDefinition implementation #305

Open EynsherKiel opened 3 weeks ago

EynsherKiel commented 3 weeks ago

Hello there,

I suggest adding the [it self gateway first definition] property to ensure that Gateway is the first definition in the list. Full code implementation in the comments, thanks!

using Microsoft.OpenApi.Models;
using MMLib.SwaggerForOcelot.Aggregates;
using System;
using System.Collections.Generic;

namespace MMLib.SwaggerForOcelot.Configuration
{
    /// <summary>
    /// Options for generating docs of ApiGateway.
    /// </summary>
    public class OcelotSwaggerGenOptions
    {
        /// <summary>
        /// Gets or sets a value indicating whether [generate docs for aggregates].
        /// </summary>
        /// <value>
        ///   <c>true</c> if [generate docs for aggregates]; otherwise, <c>false</c>.
        /// </value>
        public bool GenerateDocsForAggregates { get; set; } = false;

        /// <summary>
        /// Gets or sets a value indicating whether [generate docs for gateway it self].
        /// </summary>
        /// <value>
        ///   <c>true</c> if [generate docs for gateway it self]; otherwise, <c>false</c>.
        /// </value>
        public bool GenerateDocsForGatewayItSelf { get; set; } = false;

        /// <summary>
        /// Gets or sets a value indicating whether [it self gateway first definition].
        /// </summary>
        /// <value>
        ///   <c>true</c> if [it self gateway first definition]; otherwise, <c>false</c>.
        /// </value>
        public bool IsItSelfGatewayFirstDefinition { get; set; } = false;

        /// <summary>
        /// Gets or sets a value indicating downstream docs cache expire duration in seconds.
        /// </summary>
        /// <value>
        ///     <c>0</c> if it won't be cached; otherwise, cache expire duration in seconds.
        /// </value>
        public TimeSpan DownstreamDocsCacheExpire { get; set; } = TimeSpan.Zero;

        /// <summary>
        /// Generates docs for gateway it self with options.
        /// </summary>
        /// <param name="options">Gateway itself docs generation options.</param>
        public void GenerateDocsDocsForGatewayItSelf(Action<OcelotGatewayItSelfSwaggerGenOptions> options = null)
        {
            GenerateDocsForGatewayItSelf = true;

            OcelotGatewayItSelfSwaggerGenOptions = new OcelotGatewayItSelfSwaggerGenOptions();
            options?.Invoke(OcelotGatewayItSelfSwaggerGenOptions);

            GatewayDocsTitle = OcelotGatewayItSelfSwaggerGenOptions.GatewayDocsTitle ?? GatewayDocsTitle;
            GatewayDocsOpenApiInfo = OcelotGatewayItSelfSwaggerGenOptions.GatewayDocsOpenApiInfo ?? GatewayDocsOpenApiInfo;
        }

        /// <summary>
        /// Adds a mapping between Ocelot's AuthenticationProviderKey and Swagger's securityScheme
        /// If a route has a match, security definition will be added to the endpoint with the provided AllowedScopes from the config.
        /// </summary>
        /// <param name="authenticationProviderKey"></param>
        /// <param name="securityScheme"></param>
        public void AddAuthenticationProviderKeyMapping(string authenticationProviderKey, string securityScheme)
        {
            AuthenticationProviderKeyMap.Add(authenticationProviderKey, securityScheme);
        }

        /// <summary>
        /// Register aggregate docs generator post process.
        /// </summary>
        public Action<SwaggerAggregateRoute, IEnumerable<RouteDocs>, OpenApiPathItem, OpenApiDocument> AggregateDocsGeneratorPostProcess { get; set; }
             = AggregateRouteDocumentationGenerator.DefaultPostProcess;

        internal static OcelotSwaggerGenOptions Default { get; } = new OcelotSwaggerGenOptions();

        internal const string AggregatesKey = "aggregates";

        internal const string GatewayKey = "gateway";

        internal string GatewayDocsTitle { get; set; } = "Gateway";

        internal OpenApiInfo GatewayDocsOpenApiInfo { get; set; } = new()
        {
            Title = "Gateway",
            Version = GatewayKey,
        };

        internal OcelotGatewayItSelfSwaggerGenOptions OcelotGatewayItSelfSwaggerGenOptions { get; private set; }

        internal Dictionary<string, string> AuthenticationProviderKeyMap { get; } = new();
    }
}
using Kros.Utils;
using Microsoft.Extensions.Options;
using MMLib.SwaggerForOcelot.Configuration;
using System;
using System.Collections.Generic;
using System.Linq;

namespace MMLib.SwaggerForOcelot.Repositories
{
    /// <summary>
    /// Provider for obtaining <see cref="SwaggerEndPointOptions"/>.
    /// </summary>
    public class SwaggerEndPointProvider : ISwaggerEndPointProvider
    {
        private readonly Lazy<Dictionary<string, SwaggerEndPointOptions>> _swaggerEndPoints;
        private readonly IOptionsMonitor<List<SwaggerEndPointOptions>> _swaggerEndPointsOptions;
        private readonly OcelotSwaggerGenOptions _options;

        /// <summary>
        /// Initializes a new instance of the <see cref="SwaggerEndPointProvider"/> class.
        /// </summary>
        /// <param name="swaggerEndPoints">The swagger end points.</param>
        public SwaggerEndPointProvider(
            IOptionsMonitor<List<SwaggerEndPointOptions>> swaggerEndPoints,
            OcelotSwaggerGenOptions options)
        {
            _swaggerEndPointsOptions = Check.NotNull(swaggerEndPoints, nameof(swaggerEndPoints));

            _swaggerEndPoints = new Lazy<Dictionary<string, SwaggerEndPointOptions>>(Init);
            _options = options;
        }

        /// <inheritdoc/>
        public IReadOnlyList<SwaggerEndPointOptions> GetAll()
            => _swaggerEndPoints.Value.Values.ToList();

        /// <inheritdoc/>
        public SwaggerEndPointOptions GetByKey(string key)
            => _swaggerEndPoints.Value[$"/{key}"];

        private Dictionary<string, SwaggerEndPointOptions> Init()
        {
            var ret = _swaggerEndPointsOptions.CurrentValue.Select(p => KeyValuePair.Create($"/{p.KeyToPath}", p));

            if (_options.GenerateDocsForAggregates)
            {
                ret = ret.Append(AddEndpoint(OcelotSwaggerGenOptions.AggregatesKey, "Aggregates"));
            }

            if (_options.GenerateDocsForGatewayItSelf)
            {
                var gateway = AddEndpoint(OcelotSwaggerGenOptions.GatewayKey, _options.GatewayDocsTitle);

                ret = _options.IsItSelfGatewayFirstDefinition ?
                    ret.Prepend(gateway) :
                    ret.Append(gateway);
            }

            return ret.ToDictionary(x => x.Key, x => x.Value);
        }

        private static KeyValuePair<string, SwaggerEndPointOptions> AddEndpoint(string key, string description)
            => KeyValuePair.Create($"/{key}", new SwaggerEndPointOptions()
            {
                Key = key,
                TransformByOcelotConfig = false,
                Config = new List<SwaggerEndPointConfig>() {
                    new SwaggerEndPointConfig()
                    {
                        Name = description,
                        Version = key,
                        Url = ""
                    }
                }
            });
    }
}
EynsherKiel commented 3 weeks ago

btw, my workaround

public class SwaggerEndPointProviderFixedFilter(
    IOptionsMonitor<List<SwaggerEndPointOptions>> swaggerEndPoints,
    OcelotSwaggerGenOptions options) : SwaggerEndPointProvider(
        swaggerEndPoints: swaggerEndPoints,
        options: options), ISwaggerEndPointProvider
{
    public new IReadOnlyList<SwaggerEndPointOptions> GetAll() => [.. base.GetAll().OrderByDescending(x => x.Key == "gateway")];
}
services.AddTransient<ISwaggerEndPointProvider, SwaggerEndPointProviderFixedFilter>();