public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
        {
            if (swaggerDoc == null)
            {
                throw new ArgumentNullException(nameof(swaggerDoc));
            }

            var replacements = new OpenApiPaths();

            foreach (var(key, value) in swaggerDoc.Paths)
            {
                replacements.Add(key.Replace("{version}", swaggerDoc.Info.Version,
                                             StringComparison.InvariantCulture), value);
            }

            swaggerDoc.Paths = replacements;
        }
Ejemplo n.º 2
0
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                //app.UseMiddleware<StackifyMiddleware.RequestTracerMiddleware>();

                app.UseDeveloperExceptionPage();
            }
            else
            {
                // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
                app.UseHsts();
            }

            app.UseForwardedHeaders(new ForwardedHeadersOptions
            {
                ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto
            });

            app.UseRouting();
            app.UseCors("AnyOrigin");
            app.UseAuthentication();
            app.UseAuthorization();

            app.UseEndpoints(endpoints => {
                endpoints.MapControllers();
            });

            app.UseSwagger(c =>
            {
                string basePath = System.Environment.GetEnvironmentVariable("ASPNETCORE_APPL_PATH") ?? "/";
                c.PreSerializeFilters.Add((swaggerDoc, httpReq) =>
                {
                    var paths = new OpenApiPaths();
                    foreach (var path in swaggerDoc.Paths)
                    {
                        paths.Add(path.Key.Replace(basePath, "/"), path.Value);
                    }
                    swaggerDoc.Paths = paths;
                });
            });
            app.UseSwaggerUI(c =>
            {
                c.SwaggerEndpoint("v1/swagger.json", "post API V1");
            });
        }
Ejemplo n.º 3
0
        private static void FilterOnlyODataControllers(OpenApiDocument swaggerDoc, HttpRequest req)
        {
            // Filter Paths
            var apiPaths = swaggerDoc.Paths.Where(p => p.Key.StartsWith("/odata/"));
            var paths    = new OpenApiPaths();

            foreach (var path in apiPaths)
            {
                paths.Add(path.Key, path.Value);
            }
            swaggerDoc.Paths = paths;

            // Filter XmlComments
            var allTags = swaggerDoc.Tags;
            var tags    = allTags.Where(tag => tag.Reference.ExternalResource == "odata").ToList();

            swaggerDoc.Tags = tags;
        }
Ejemplo n.º 4
0
        private OpenApiPaths GeneratePaths(IReadOnlyList <ServiceEntry> entries, SchemaRepository schemaRepository)
        {
            var entriesByPath = entries.OrderBy(_options.SortKeySelector)
                                .GroupBy(p => p.Router.RoutePath);

            var paths = new OpenApiPaths();

            foreach (var group in entriesByPath)
            {
                paths.Add($"/{group.Key}",
                          new OpenApiPathItem
                {
                    Operations = GenerateOperations(group, schemaRepository)
                });
            }

            return(paths);
        }
Ejemplo n.º 5
0
        /// <summary>
        /// Visits <see cref="OpenApiPaths"/> and child objects
        /// </summary>
        internal void Walk(OpenApiPaths paths)
        {
            if (paths == null)
            {
                return;
            }

            _visitor.Visit(paths);

            // Visit Paths
            if (paths != null)
            {
                foreach (var pathItem in paths)
                {
                    Walk(pathItem.Key, () => Walk(pathItem.Value));// JSON Pointer uses ~1 as an escape character for /
                }
            }
        }
Ejemplo n.º 6
0
        private static OpenApiPaths BuildPaths(IEnumerable <RestEndpoint> restEndpoints)
        {
            var dictionary = restEndpoints
                             .ToDictionary(re => re.Path, re => new OpenApiPathItem()
            {
                Parameters = re.InputParameters
                             .Select(ip => new OpenApiParameter()
                {
                    Name     = ip.Name,
                    In       = ip.ParameterLocation,
                    Required = true,
                    Schema   = new OpenApiSchema()
                    {
                        Type = "string",
                    },
                    Description = ip.Description,
                }).ToList(),

                Operations = re.InputOperations
                             .ToDictionary(io => io.OperationType, io => new OpenApiOperation()
                {
                    Description = io.Description,
                    Responses   = io.ResponseValues
                                  .ToDictionary(rv => rv.Name, rv => new OpenApiResponse()
                    {
                        Reference = new OpenApiReference()
                        {
                            Id   = rv.Id,
                            Type = ReferenceType.Response,
                        },
                    }) as OpenApiResponses,
                }),
            });

            var openApiPaths = new OpenApiPaths();

            foreach (var kvp in dictionary)
            {
                openApiPaths.Add(kvp.Key, kvp.Value);
            }

            return(openApiPaths);
        }
Ejemplo n.º 7
0
        /// <summary>
        /// Parses the parameters in the URL to populate the in attribute of the param tags as path or query
        /// if not explicitly documented.
        /// </summary>
        /// <param name="paths">The paths to be updated.</param>
        /// <param name="element">The xml element representing an operation in the annotation xml.</param>
        /// <param name="settings">The operation filter settings.</param>
        public void Apply(OpenApiPaths paths, XElement element, PreProcessingOperationFilterSettings settings)
        {
            var paramElementsWithoutIn = element.Elements().Where(
                p => p.Name == KnownXmlStrings.Param &&
                p.Attribute(KnownXmlStrings.In)?.Value == null)
                                         .ToList();

            var url = element.Elements()
                      .FirstOrDefault(p => p.Name == KnownXmlStrings.Url)
                      ?.Value;

            if (!string.IsNullOrWhiteSpace(url))
            {
                foreach (var paramElement in paramElementsWithoutIn)
                {
                    var paramName = paramElement.Attribute(KnownXmlStrings.Name)?.Value;

                    if (url.Contains(
                            $"/{{{paramName}}}",
                            StringComparison.InvariantCultureIgnoreCase) &&
                        url.Contains(
                            $"={{{paramName}}}",
                            StringComparison.InvariantCultureIgnoreCase))
                    {
                        // The parameter is in both path and query. We cannot determine what to put for "in" attribute.
                        throw new ConflictingPathAndQueryParametersException(paramName, url);
                    }

                    if (url.Contains(
                            $"/{{{paramName}}}",
                            StringComparison.InvariantCultureIgnoreCase))
                    {
                        paramElement.Add(new XAttribute(KnownXmlStrings.In, KnownXmlStrings.Path));
                    }
                    else if (url.Contains(
                                 $"={{{paramName}}}",
                                 StringComparison.InvariantCultureIgnoreCase))
                    {
                        paramElement.Add(new XAttribute(KnownXmlStrings.In, KnownXmlStrings.Query));
                    }
                }
            }
        }
Ejemplo n.º 8
0
        /// <summary>
        ///
        /// </summary>
        /// <param name="app"></param>
        /// <param name="configuration"></param>
        /// <returns></returns>
        public static IApplicationBuilder UseKaneko(this IApplicationBuilder app, IConfiguration configuration)
        {
            string serviceName = configuration["ServiceName"];

            if (bool.Parse(configuration["Consul:Enable"]))
            {
                app.UseConsul();
            }
            app.UseCors(serviceName);
            app.UseSwagger(c =>
            {
                //加上服务名,支持直接在ocelot进行api测试
                string basepath = "";
                if (!string.IsNullOrEmpty(serviceName))
                {
                    basepath = $"/{serviceName}";
                }
                c.PreSerializeFilters.Add((swaggerDoc, httpReq) =>
                {
                    OpenApiPaths paths = new OpenApiPaths();
                    foreach (var path in swaggerDoc.Paths)
                    {
                        paths.Add(basepath + path.Key, path.Value);
                    }
                    swaggerDoc.Paths = paths;
                });
            });

            app.UseSwaggerUI(options =>
            {
                options.SwaggerEndpoint("/swagger/v1/swagger.json", serviceName);
                options.RoutePrefix = string.Empty;
            });

            app.UseRouting();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapDefaultControllerRoute();
            });

            return(app);
        }
Ejemplo n.º 9
0
        //public CompilerResults CompileThenSave(string fileName)//not working in .net core
        //{
        //	using CodeDomProvider provider = CodeDomProvider.CreateProvider("CSharp");
        //	CodeGeneratorOptions options = new CodeGeneratorOptions() { BracingStyle = "C", IndentString = "\t" };
        //	var s = WriteToText();
        //	var results = provider.CompileAssemblyFromSource(  //https://docs.microsoft.com/en-us/dotnet/core/compatibility/unsupported-apis
        //		new CompilerParameters(new string[] { "System.Net.Http", "Newtonsoft.Json" })
        //		{
        //			GenerateInMemory = true,
        //		}, s
        //		);

        //	File.WriteAllText(fileName, s); //save the file anyway

        //	return results;
        //}

        string[] GetContainerClassNames(OpenApiPaths paths)
        {
            if (settings.ContainerNameStrategy == ContainerNameStrategy.None)
            {
                return(new string[] { settings.ContainerClassName });
            }

            List <string> names = new List <string>();

            foreach (KeyValuePair <string, OpenApiPathItem> p in paths)
            {
                foreach (KeyValuePair <OperationType, OpenApiOperation> op in p.Value.Operations)
                {
                    string name = nameComposer.GetContainerName(op.Value, p.Key);
                    names.Add(name);
                }
            }

            return(names.Distinct().ToArray());
        }
Ejemplo n.º 10
0
        private OpenApiPaths GeneratePaths(IEnumerable <ApiDescription> apiDescriptions, SchemaRepository schemaRepository)
        {
            var apiDescriptionsByPath = apiDescriptions
                                        .OrderBy(_options.SortKeySelector)
                                        .GroupBy(apiDesc => apiDesc.RelativePathSansQueryString());

            var paths = new OpenApiPaths();

            foreach (var group in apiDescriptionsByPath)
            {
                paths.Add($"/{group.Key}",
                          new OpenApiPathItem
                {
                    Operations = GenerateOperations(group, schemaRepository)
                });
            }
            ;

            return(paths);
        }
Ejemplo n.º 11
0
        /// <inheritdoc/>
        public OpenApiDocument GenerateFor(IDebuggingHandler handler)
        {
            var repository = new SchemaRepository();
            var paths      = new OpenApiPaths();

            GeneratePaths(paths, handler, repository);

            return(new OpenApiDocument
            {
                Info = new OpenApiInfo
                {
                    Title = handler.Title,
                },
                Paths = paths,
                Components = new OpenApiComponents
                {
                    Schemas = repository.Schemas,
                },
            });
        }
Ejemplo n.º 12
0
        void GeneratePaths(OpenApiPaths paths, IDebuggingHandler handler, SchemaRepository repository)
        {
            foreach ((var path, var artifact) in handler.Artifacts)
            {
                var item = new OpenApiPathItem();

                AddGetOperation(handler, item, GenerateOperation(handler, artifact, repository));
                AddPostOperation(handler, item, GenerateOperation(handler, artifact, repository));

                var tag = path.ToString().Contains('/', StringComparison.InvariantCultureIgnoreCase) ? path.ToString().Split('/')[1] : path.ToString();
                foreach ((_, var operation) in item.Operations)
                {
                    operation.Tags = new[] { new OpenApiTag {
                                                 Name = tag
                                             } };
                }

                paths.Add(path, item);
            }
        }
        /// <summary>
        /// Fetches the URL value and creates multiple operations based on optional parameters.
        /// </summary>
        /// <param name="paths">The paths to be updated.</param>
        /// <param name="element">The xml element representing an operation in the annotation xml.</param>
        /// <param name="settings">The operation filter settings.</param>
        public void Apply(OpenApiPaths paths, XElement element, PreProcessingOperationFilterSettings settings)
        {
            var paramElements = element.Elements()
                                .Where(
                p => p.Name == KnownXmlStrings.Param)
                                .ToList();

            // We need both the full URL and the absolute paths for processing.
            // Full URL contains all path and query parameters.
            // Absolute path is needed to get OperationId parsed out correctly.
            var fullUrl = element.Elements()
                          .FirstOrDefault(p => p.Name == KnownXmlStrings.Url)
                          ?.Value;

            var absolutePath = fullUrl.UrlStringToAbsolutePath();

            var operationMethod = (OperationType)Enum.Parse(
                typeof(OperationType),
                element.Elements().FirstOrDefault(p => p.Name == KnownXmlStrings.Verb)?.Value,
                ignoreCase: true);

            var allGeneratedPathStrings = GeneratePossiblePaths(
                absolutePath,
                paramElements.Where(
                    p => p.Attribute(KnownXmlStrings.In)?.Value == KnownXmlStrings.Path)
                .ToList());

            foreach (var pathString in allGeneratedPathStrings)
            {
                if (!paths.ContainsKey(pathString))
                {
                    paths[pathString] = new OpenApiPathItem();
                }

                paths[pathString].Operations[operationMethod] =
                    new OpenApiOperation
                {
                    OperationId = OperationHandler.GetOperationId(pathString, operationMethod)
                };
            }
        }
        public static SwaggerOptions Rebase(this SwaggerOptions options, Func <PathString, PathString> rebase)
        {
            options
            .PreSerializeFilters
            .Add
            (
                (swagger, httpReq) =>
            {
                var rebasedPaths = new OpenApiPaths();

                foreach (var path in swagger.Paths)
                {
                    rebasedPaths.Add(Uri.UnescapeDataString(rebase.Invoke(path.Key)), path.Value);
                }

                swagger.Paths = rebasedPaths;
            }
            );

            return(options);
        }
Ejemplo n.º 15
0
        private OpenApiPaths GetOpenApiPaths(RpcRouteMetaData metaData, SchemaRepository schemaRepository)
        {
            OpenApiPaths paths = new OpenApiPaths();

            List <UniqueMethod> uniqueMethods = this.GetUniqueKeyMethodPairs(metaData);

            foreach (UniqueMethod method in uniqueMethods)
            {
                string           operationKey = method.UniqueUrl.Replace("/", "_").Replace("#", "|");
                OpenApiOperation operation    = this.GetOpenApiOperation(operationKey, method.Info, schemaRepository);

                var pathItem = new OpenApiPathItem()
                {
                    Operations = new Dictionary <OperationType, OpenApiOperation>()
                    {
                        [OperationType.Post] = operation
                    }
                };
                paths.Add(method.UniqueUrl, pathItem);
            }

            return(paths);
        }
        public void TestApply()
        {
            var filterRoutesDocumentFilter = new FilterRoutesDocumentFilter();
            var schemaGeneratorMock        = new Mock <ISchemaGenerator>();

            var paths = new OpenApiPaths
            {
                { VersionPrefix + "someVersionedEndpoint", new OpenApiPathItem() },
                { VersionPrefix + "someOtherVersionedEndpoint", new OpenApiPathItem() },
                { "EndpointWithoutVersion", new OpenApiPathItem() }
            };

            var swaggerDoc = new OpenApiDocument
            {
                Paths = paths
            };
            var context = new DocumentFilterContext(new[] { new ApiDescription() }, schemaGeneratorMock.Object,
                                                    new SchemaRepository());

            filterRoutesDocumentFilter.Apply(swaggerDoc, context);

            swaggerDoc.Paths.Keys.Should().NotContain(key => !key.StartsWith(VersionPrefix));
        }
Ejemplo n.º 17
0
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
                //过滤器
                app.UseSwagger(c => {
                    c.PreSerializeFilters.Add((swaggerDoc, httpReq) => {
                        OpenApiPaths paths = new OpenApiPaths();
                        foreach (var path in swaggerDoc.Paths)
                        {
                            if (path.Key.StartsWith("/api/"))//过滤Path
                            {
                                paths.Add(path.Key, path.Value);
                            }
                        }
                        swaggerDoc.Paths = paths;

                        swaggerDoc.Servers = new List <OpenApiServer> {
                            new OpenApiServer {
                                Url = $"{httpReq.Scheme}://{httpReq.Host.Value}"
                            }
                        };
                    });
                });
                app.UseSwaggerUI(options =>
                {
                    options.SwaggerEndpoint("/swagger/Examination/swagger.json", "Examination");//Swagger文档路径
                    options.SwaggerEndpoint("/swagger/Business/swagger.json", "Business");
                    options.SwaggerEndpoint("/swagger/Authority/swagger.json", "Authority");
                    options.SwaggerEndpoint("/swagger/Social/swagger.json", "Social");
                    options.RoutePrefix = "";//设置为首页访问
                });
            }

            app.UseMvc();
        }
Ejemplo n.º 18
0
        // This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseHttpsRedirection();
            app.UseRouting();
            app.UseAuthorization();
            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllers();
            });

            app.UseSwagger(c =>
            {
                c.PreSerializeFilters.Add((swaggerDoc, httpReq) =>
                {
                    var paths = new OpenApiPaths();
                    foreach (var path in swaggerDoc.Paths)
                    {
                        paths.Add(BasePath + path.Key, path.Value);
                    }

                    swaggerDoc.Paths = paths;

                    var servers = new OpenApiServer
                    {
                        Url = httpReq.Host + BasePath,
                    };

                    swaggerDoc.Servers.Add(servers);
                });
            });
            app.UseSwaggerUI(c => { c.SwaggerEndpoint("/swagger/v1/swagger.json", "Swashbuckle Test API V1"); });
        }
Ejemplo n.º 19
0
        private static List <LogKeyValueItem> ValidatePathsAndOperations(
            ApiOptionsValidation validationOptions,
            OpenApiPaths paths)
        {
            var logItems    = new List <LogKeyValueItem>();
            var logCategory = validationOptions.StrictMode
                ? LogCategoryType.Error
                : LogCategoryType.Warning;

            foreach (var path in paths)
            {
                if (!path.Key.IsStringFormatParametersBalanced(false))
                {
                    logItems.Add(LogItemHelper.Create(logCategory, ValidationRuleNameConstants.Path01, $"Path parameters are not well-formatted for '{path.Key}'."));
                }

                var globalPathParameterNames = path.Value.Parameters
                                               .Where(x => x.In == ParameterLocation.Path)
                                               .Select(x => x.Name)
                                               .ToList();

                if (globalPathParameterNames.Any())
                {
                    logItems.AddRange(ValidatePathsAndOperationsHelper.ValidateGlobalParameters(validationOptions, globalPathParameterNames, path));
                }
                else
                {
                    logItems.AddRange(ValidatePathsAndOperationsHelper.ValidateMissingOperationParameters(validationOptions, path));
                    logItems.AddRange(ValidatePathsAndOperationsHelper.ValidateOperationsWithParametersNotPresentInPath(validationOptions, path));
                }

                logItems.AddRange(ValidatePathsAndOperationsHelper.ValidateGetOperations(validationOptions, path));
            }

            return(logItems);
        }
Ejemplo n.º 20
0
 public override void Visit(OpenApiPaths item) => Validate(item);
Ejemplo n.º 21
0
        /// <summary>
        /// Converts the alternative param tags (queryParam, pathParam, header) to standard param tags.
        /// </summary>
        /// <param name="paths">The paths to be updated.</param>
        /// <param name="element">The xml element representing an operation in the annotation xml.</param>
        /// <param name="settings">The operation filter settings.</param>
        public void Apply(OpenApiPaths paths, XElement element, PreProcessingOperationFilterSettings settings)
        {
            var pathParamElements = element.Elements()
                                    .Where(p => p.Name == KnownXmlStrings.PathParam)
                                    .ToList();

            var queryParamElements = element.Elements()
                                     .Where(p => p.Name == KnownXmlStrings.QueryParam)
                                     .ToList();

            var headerParamElements = element.Elements()
                                      .Where(p => p.Name == KnownXmlStrings.Header)
                                      .ToList();

            var requestTypeElements = element.Elements()
                                      .Where(p => p.Name == KnownXmlStrings.RequestType)
                                      .ToList();

            var paramElements = element.Elements().Where(i => i.Name == KnownXmlStrings.Param);

            if (pathParamElements.Any())
            {
                foreach (var pathParamElement in pathParamElements)
                {
                    var conflictingPathParam = paramElements.Where(
                        i => i.Attribute("name")?.Value == pathParamElement.Attribute("name")?.Value);

                    // Remove param tags that have same name as pathParam tags
                    // e.g. if service is documented like below, it will remove the param tag
                    //
                    // <param name="samplePathParam">Sample path param</param>
                    // <pathParam name="samplePathParam" in="path">Sample path param</pathParam>
                    conflictingPathParam?.Remove();

                    pathParamElement.Name = KnownXmlStrings.Param;
                    pathParamElement.Add(new XAttribute(KnownXmlStrings.In, KnownXmlStrings.Path));
                }
            }

            if (queryParamElements.Any())
            {
                foreach (var queryParamElement in queryParamElements)
                {
                    var conflictingQueryParam = paramElements.Where(
                        i => i.Attribute("name")?.Value == queryParamElement.Attribute("name")?.Value);

                    // Remove param tags that have same name as queryParam tags
                    // e.g. if service is documented like below, it will remove the param tag
                    //
                    // <param name="sampleQueryParam">Sample query param</param>
                    // <queryParam name="sampleQueryParam" in="path">Sample query param</queryParam>
                    conflictingQueryParam?.Remove();

                    queryParamElement.Name = KnownXmlStrings.Param;
                    queryParamElement.Add(new XAttribute(KnownXmlStrings.In, KnownXmlStrings.Query));
                }
            }

            if (requestTypeElements.Any())
            {
                var paramTagToRemove = element.Elements()
                                       .Where(i => i.Name == KnownXmlStrings.Param &&
                                              string.IsNullOrWhiteSpace(i.Attribute("in")?.Value));

                // If there are still conflicting param tags remaining, then it's safe to assume that these are neither
                // path nor query params and could be documented request params which is not intended to be used with
                // C# document generator so remove the tags.
                paramTagToRemove?.Remove();

                foreach (var requestTypeElement in requestTypeElements)
                {
                    requestTypeElement.Name = KnownXmlStrings.Param;
                    requestTypeElement.Add(new XAttribute(KnownXmlStrings.In, KnownXmlStrings.Body));
                }
            }

            foreach (var headerParamElement in headerParamElements)
            {
                headerParamElement.Name = KnownXmlStrings.Param;
                headerParamElement.Add(new XAttribute(KnownXmlStrings.In, KnownXmlStrings.Header));
            }
        }
Ejemplo n.º 22
0
        /// <summary>
        /// Use swagger in application
        /// </summary>
        /// <param name="app"></param>
        public static void UseSwagger(this IApplicationBuilder app)
        {
            var config    = app.ApplicationServices.GetRequiredService <IOpenApiConfig>();
            var auth      = app.ApplicationServices.GetService <IServerAuthConfig>();
            var server    = app.ApplicationServices.GetRequiredService <IServer>();
            var addresses = app.ServerFeatures.Get <IServerAddressesFeature>()?.Addresses
                            .Select(a => new Uri(a.Replace("://*", "://localhost")))
                            .ToList() ?? new List <Uri>();

            // Enable swagger and swagger ui
            app.UseSwagger(options => {
                options.PreSerializeFilters.Add((doc, request) => {
                    doc.Servers = new List <OpenApiServer>();
                    foreach (var scheme in addresses
                             .Select(a => a.Scheme)
                             .Append("https")
                             .Append(request.Scheme)
                             .Distinct())
                    {
                        var url = $"{scheme}://{request.Host.Value}";

                        // If config.OpenApiServerHost is set, we will use that instead of request.Host.Value
                        if (!string.IsNullOrEmpty(config.OpenApiServerHost))
                        {
                            url = $"{scheme}://{config.OpenApiServerHost}";
                        }

                        doc.Servers.Add(new OpenApiServer {
                            Description = $"{scheme} endpoint.",
                            Url         = url
                        });
                    }

                    // If request.PathBase exists, then we will prepend it to doc.Paths.
                    if (request.PathBase.HasValue)
                    {
                        var pathBase      = request.PathBase.Value;
                        var prefixedPaths = new OpenApiPaths();
                        foreach (var path in doc.Paths)
                        {
                            prefixedPaths.Add(pathBase + path.Key, path.Value);
                        }
                        doc.Paths = prefixedPaths;
                    }
                });
                options.SerializeAsV2 = true;
                options.RouteTemplate = "swagger/{documentName}/openapi.json";
            });
            if (!config.UIEnabled)
            {
                return;
            }

            var api   = app.ApplicationServices.GetRequiredService <IActionDescriptorCollectionProvider>();
            var infos = api.GetOpenApiInfos(null, null);

            // Where to host the ui
            app.UseSwaggerUI(options => {
                foreach (var info in infos)
                {
                    if (config.WithAuth)
                    {
                        options.OAuthAppName(info.Title);
                        options.OAuthClientId(config.OpenApiAppId);
                        if (!string.IsNullOrEmpty(config.OpenApiAppSecret))
                        {
                            options.OAuthClientSecret(config.OpenApiAppSecret);
                        }
                        var resource = auth?.JwtBearerProviders?.FirstOrDefault();
                        if (!string.IsNullOrEmpty(resource?.Audience))
                        {
                            options.OAuthAdditionalQueryStringParams(
                                new Dictionary <string, string> {
                                ["resource"] = resource.Audience
                            });
                        }
                    }
                    options.SwaggerEndpoint($"{info.Version}/openapi.json",
                                            info.Version);
                }
            });
        }
Ejemplo n.º 23
0
 /// <summary>
 /// Visits <see cref="OpenApiPaths"/>
 /// </summary>
 public virtual void Visit(OpenApiPaths paths)
 {
 }
        /// <inheritdoc />
        public IDocument Build(Assembly assembly)
        {
            if (this._strategy.IsNullOrDefault())
            {
                this._strategy = new DefaultNamingStrategy();
            }

            var paths = new OpenApiPaths();

            var methods = this._helper.GetHttpTriggerMethods(assembly);

            foreach (var method in methods)
            {
                var trigger = this._helper.GetHttpTriggerAttribute(method);
                if (trigger.IsNullOrDefault())
                {
                    continue;
                }

                var function = this._helper.GetFunctionNameAttribute(method);
                if (function.IsNullOrDefault())
                {
                    continue;
                }

                var path = this._helper.GetHttpEndpoint(function, trigger);
                if (path.IsNullOrWhiteSpace())
                {
                    continue;
                }

                var verb = this._helper.GetHttpVerb(trigger);

                var item       = this._helper.GetOpenApiPath(path, paths);
                var operations = item.Operations;

                var operation = this._helper.GetOpenApiOperation(method, function, verb);
                if (operation.IsNullOrDefault())
                {
                    continue;
                }

                operation.Security    = this._helper.GetOpenApiSecurityRequirement(method, this._strategy);
                operation.Parameters  = this._helper.GetOpenApiParameters(method, trigger, this._strategy, this._collection);
                operation.RequestBody = this._helper.GetOpenApiRequestBody(method, this._strategy, this._collection);
                operation.Responses   = this._helper.GetOpenApiResponses(method, this._strategy, this._collection);

                operations[verb] = operation;
                item.Operations  = operations;

                paths[path] = item;
            }

            this.OpenApiDocument.Paths = paths;
            this.OpenApiDocument.Components.Schemas         = this._helper.GetOpenApiSchemas(methods, this._strategy, this._collection);
            this.OpenApiDocument.Components.SecuritySchemes = this._helper.GetOpenApiSecuritySchemes(methods, this._strategy);
            // this.OpenApiDocument.SecurityRequirements = this.OpenApiDocument
            //                                                 .Paths
            //                                                 .SelectMany(p => p.Value.Operations.SelectMany(q => q.Value.Security))
            //                                                 .Where(p => !p.IsNullOrDefault())
            //                                                 .Distinct(new OpenApiSecurityRequirementComparer())
            //                                                 .ToList();

            return(this);
        }
        /// <inheritdoc />
        public OpenApiPathItem GetOpenApiPath(string path, OpenApiPaths paths)
        {
            var item = paths.ContainsKey(path) ? paths[path] : new OpenApiPathItem();

            return(item);
        }
Ejemplo n.º 26
0
        public static void GenerateClientAPIs(Settings settings, OpenApiPaths paths, OpenApiComponents components, string outputBasePath)
        {
            string currentDir = System.IO.Directory.GetCurrentDirectory();

            if (settings.ClientLibraryProjectFolderName != null)
            {
                string csharpClientProjectDir = System.IO.Path.IsPathRooted(settings.ClientLibraryProjectFolderName) ?
                                                settings.ClientLibraryProjectFolderName : System.IO.Path.Combine(outputBasePath, settings.ClientLibraryProjectFolderName);

                if (!System.IO.Directory.Exists(csharpClientProjectDir))
                {
                    if (settings.CreateFolder)
                    {
                        System.IO.Directory.CreateDirectory(csharpClientProjectDir);
                    }
                    else
                    {
                        string msg = $"{csharpClientProjectDir} not exist while current directory is {currentDir}";
                        throw new CodeGenException(msg);
                    }
                }

                string path = System.IO.Path.Combine(csharpClientProjectDir, settings.ClientLibraryFileName);
                ControllersClientApiGen gen = new ControllersClientApiGen(settings);
                gen.CreateCodeDom(paths, components);
                gen.Save(path);
            }

            string CreateTsPath(string folder, string fileName)
            {
                if (folder != null)
                {
                    string theFolder;
                    try
                    {
                        theFolder = System.IO.Path.IsPathRooted(folder) ?
                                    folder : System.IO.Path.Combine(outputBasePath, folder);

                        if (!System.IO.Directory.Exists(theFolder))
                        {
                            if (settings.CreateFolder)
                            {
                                System.IO.Directory.CreateDirectory(theFolder);
                            }
                            else
                            {
                                string msg = $"{theFolder} not exist while current directory is {currentDir}";
                                throw new CodeGenException(msg);
                            }
                        }
                    }
                    catch (ArgumentException e)
                    {
                        Trace.TraceWarning(e.Message);
                        string msg = $"Invalid TypeScriptFolder {folder} while current directory is {currentDir}";
                        throw new CodeGenException(msg);
                    }

                    if (!System.IO.Directory.Exists(theFolder))
                    {
                        string msg = $"TypeScriptFolder {theFolder} not exist while current directory is {currentDir}";
                        throw new CodeGenException(msg);
                    }
                    return(System.IO.Path.Combine(theFolder, fileName));
                }
                ;

                return(null);
            }

            if (settings.Plugins != null)
            {
                string exeDir = System.IO.Path.GetDirectoryName(System.Reflection.Assembly.GetExecutingAssembly().Location);
                foreach (JSPlugin plugin in settings.Plugins)
                {
                    JSOutput jsOutput = new JSOutput
                    {
                        JSPath      = CreateTsPath(plugin.TargetDir, plugin.TSFile),
                        AsModule    = plugin.AsModule,
                        ContentType = plugin.ContentType,
                    };

                    string assemblyFilePath = System.IO.Path.Combine(exeDir, plugin.AssemblyName + ".dll");
                    Ts.ControllersTsClientApiGenBase tsGen = PluginFactory.CreateImplementationsFromAssembly(assemblyFilePath, settings, jsOutput);
                    if (tsGen != null)
                    {
                        Trace.TraceInformation($"Generate codes with {tsGen.ProductName} ......");
                        tsGen.CreateCodeDom(paths, components);
                        tsGen.Save();
                    }
                    else
                    {
                        Trace.TraceWarning($"Not done with plugin {plugin.AssemblyName}");
                    }
                }
            }
        }
Ejemplo n.º 27
0
        /// <summary>
        /// Converts the alternative param tags (queryParam, pathParam, header) to standard param tags.
        /// </summary>
        /// <param name="paths">The paths to be updated.</param>
        /// <param name="element">The xml element representing an operation in the annotation xml.</param>
        /// <param name="settings">The operation filter settings.</param>
        /// <returns>The list of generation errors, if any produced when processing the filter.</returns>
        public IList <GenerationError> Apply(
            OpenApiPaths paths,
            XElement element,
            PreProcessingOperationFilterSettings settings)
        {
            var generationErrors = new List <GenerationError>();

            try
            {
                var pathParamElements = element.Elements()
                                        .Where(p => p.Name == KnownXmlStrings.PathParam)
                                        .ToList();

                var queryParamElements = element.Elements()
                                         .Where(p => p.Name == KnownXmlStrings.QueryParam)
                                         .ToList();

                var headerParamElements = element.Elements()
                                          .Where(p => p.Name == KnownXmlStrings.Header)
                                          .ToList();

                var requestTypeElements = element.Elements()
                                          .Where(p => p.Name == KnownXmlStrings.RequestType)
                                          .ToList();

                var paramElements = element.Elements().Where(i => i.Name == KnownXmlStrings.Param);
                var paramElementsWithInAttributeNotSpecified = paramElements.Where(i => i.Attribute("in") == null);

                if (pathParamElements.Any())
                {
                    foreach (var pathParamElement in pathParamElements)
                    {
                        var conflictingPathParam = paramElements.Where(
                            i => i.Attribute("name")?.Value == pathParamElement.Attribute("name")?.Value);

                        // Remove param tags that have same name as pathParam tags
                        // e.g. if service is documented like below, it will remove the param tag
                        //
                        // <param name="samplePathParam">Sample path param</param>
                        // <pathParam name="samplePathParam" in="path">Sample path param</pathParam>
                        conflictingPathParam?.Remove();

                        var nameAttribute = pathParamElement.Attribute(KnownXmlStrings.Name);
                        var name          = nameAttribute?.Value.Trim();

                        if (!string.IsNullOrWhiteSpace(name))
                        {
                            nameAttribute.Value = name;
                        }

                        pathParamElement.Name = KnownXmlStrings.Param;
                        pathParamElement.Add(new XAttribute(KnownXmlStrings.In, KnownXmlStrings.Path));
                    }
                }

                if (queryParamElements.Any())
                {
                    foreach (var queryParamElement in queryParamElements)
                    {
                        var conflictingQueryParam = paramElements.Where(
                            i => i.Attribute("name")?.Value == queryParamElement.Attribute("name")?.Value);

                        // Remove param tags that have same name as queryParam tags
                        // e.g. if service is documented like below, it will remove the param tag
                        //
                        // <param name="sampleQueryParam">Sample query param</param>
                        // <queryParam name="sampleQueryParam" in="path">Sample query param</queryParam>
                        conflictingQueryParam?.Remove();

                        var nameAttribute = queryParamElement.Attribute(KnownXmlStrings.Name);
                        var name          = nameAttribute?.Value.Trim();

                        if (!string.IsNullOrWhiteSpace(name))
                        {
                            nameAttribute.Value = name;
                        }

                        queryParamElement.Name = KnownXmlStrings.Param;
                        queryParamElement.Add(new XAttribute(KnownXmlStrings.In, KnownXmlStrings.Query));
                    }
                }

                if (requestTypeElements.Any())
                {
                    var paramTagToRemove = element.Elements()
                                           .Where(i => i.Name == KnownXmlStrings.Param &&
                                                  string.IsNullOrWhiteSpace(i.Attribute("in")?.Value));

                    // If there are still conflicting param tags remaining, then it's safe to assume that these are neither
                    // path nor query params and could be documented request params which is not intended to be used with
                    // C# document generator so remove the tags.
                    paramTagToRemove?.Remove();

                    foreach (var requestTypeElement in requestTypeElements)
                    {
                        requestTypeElement.Name = KnownXmlStrings.Param;
                        requestTypeElement.Add(new XAttribute(KnownXmlStrings.In, KnownXmlStrings.Body));
                    }
                }

                foreach (var headerParamElement in headerParamElements)
                {
                    var nameAttribute = headerParamElement.Attribute(KnownXmlStrings.Name);
                    var name          = nameAttribute?.Value.Trim();

                    if (!string.IsNullOrWhiteSpace(name))
                    {
                        nameAttribute.Value = name;
                    }

                    headerParamElement.Name = KnownXmlStrings.Param;
                    headerParamElement.Add(new XAttribute(KnownXmlStrings.In, KnownXmlStrings.Header));
                }

                // If any of the alternative tags are present then remove any param tag element that have "in" attribute
                // not specified b/c assumption is made that those params are not supposed to be processed by
                // CSharp Annotation Document Generator.
                if (pathParamElements.Any() ||
                    queryParamElements.Any() ||
                    requestTypeElements.Any() ||
                    headerParamElements.Any())
                {
                    paramElementsWithInAttributeNotSpecified?.Remove();
                }
            }
            catch (Exception ex)
            {
                generationErrors.Add(
                    new GenerationError
                {
                    Message       = ex.Message,
                    ExceptionType = ex.GetType().Name
                });
            }

            return(generationErrors);
        }
Ejemplo n.º 28
0
 public override void Visit(OpenApiPaths paths)
 {
     Locations.Add(this.PathString);
 }
        public OpenApiPaths GeneratePaths()
        {
            OpenApiPaths paths = new OpenApiPaths();

            foreach (Ac4yClass ac4yClass in Parameter.ClassList)
            {
                paths
                .Add(
                    "/" + ac4yClass.Name + "/{id}", new OpenApiPathItem
                {
                    Operations = new Dictionary <OperationType, OpenApiOperation>
                    {
                        [OperationType.Get] = new OpenApiOperation
                        {
                            Description = "Egy, az adott id-hez tartozó, " + ac4yClass.Name + " típusú rekord lekérdezése.",
                            Parameters  = new List <OpenApiParameter>
                            {
                                new OpenApiParameter
                                {
                                    Name        = "id",
                                    In          = ParameterLocation.Path,
                                    Description = "A rekord is-ével lekérdezi a konkrét rekordot",
                                    Required    = true,
                                    Schema      = new OpenApiSchema
                                    {
                                        Type = "integer"
                                    }
                                }
                            },
                            Responses = new OpenApiResponses
                            {
                                ["200"] = new OpenApiResponse
                                {
                                    Description = "A lekérdezés sikeres volt, a rekord adatait adja vissza",
                                    Content     =
                                    {
                                        ["application/json"] = new OpenApiMediaType
                                                {
                                                Schema = new OpenApiSchema
                                                {
                                                Title     = "Vendor",
                                                Reference = new OpenApiReference
                                                {
                                                Type = ReferenceType.Schema,
                                                Id   = ac4yClass.Name
                                                }
                                                }
                                                }
                                    }
                                },
                                ["400"] = new OpenApiResponse
                                {
                                    Description = "A kérés hibára futott, a hibaüzenet bővebben tájékoztat",
                                    Content     =
                                    {
                                        ["application/json"] = new OpenApiMediaType
                                                    {
                                                    Schema = new OpenApiSchema
                                                    {
                                                    Title      = "Hiba üzenet",
                                                    Properties = new Dictionary <string, OpenApiSchema>
                                                    {
                                                    ["Message"] = new OpenApiSchema
                                                    {
                                                    Type        = "string",
                                                    Description = "A hibáról informáló üzenet"
                                                    }
                                                    }
                                                    }
                                                    }
                                    }
                                }
                            }
                        },
                        [OperationType.Patch] = new OpenApiOperation
                        {
                            Description = "Update-el 1 adott rekordot az id alapján",
                            Parameters  = new List <OpenApiParameter>
                            {
                                new OpenApiParameter
                                {
                                    Name        = "id",
                                    In          = ParameterLocation.Path,
                                    Description = "A rekord id-hez tartozó rekordot fogja frissíteni",
                                    Required    = true,
                                    Schema      = new OpenApiSchema
                                    {
                                        Type = "integer"
                                    }
                                }
                            },
                            RequestBody = new OpenApiRequestBody
                            {
                                Required = true,
                                Content  =
                                {
                                    ["application/json"] = new OpenApiMediaType
                                            {
                                            Example = GenerateExample(ac4yClass),
                                            Schema  = new OpenApiSchema
                                            {
                                            Title     = "Vendor",
                                            Reference = new OpenApiReference
                                            {
                                            Type = ReferenceType.Schema,
                                            Id   = ac4yClass.Name + "WithoutID"
                                            }
                                            }
                                            }
                                }
                            },
                            Responses = new OpenApiResponses
                            {
                                ["200"] = new OpenApiResponse
                                {
                                    Description = "Az adatok frissítése sikeres volt, a rekord a db-ben is frissült",
                                },
                                ["400"] = new OpenApiResponse
                                {
                                    Description = "A kérés hibára futott, a hibaüzenet bővebben tájékoztat",
                                    Content     =
                                    {
                                        ["application/json"] = new OpenApiMediaType
                                                    {
                                                    Schema = new OpenApiSchema
                                                    {
                                                    Title      = "Hiba üzenet",
                                                    Properties = new Dictionary <string, OpenApiSchema>
                                                    {
                                                    ["Message"] = new OpenApiSchema
                                                    {
                                                    Type        = "string",
                                                    Description = "A hibáról informáló üzenet"
                                                    }
                                                    }
                                                    }
                                                    }
                                    }
                                }
                            }
                        },
                        [OperationType.Delete] = new OpenApiOperation
                        {
                            Description = "Töröl 1, adott rekordot az id alapján és a rekordhoz tartozó összes kapcsolt rekordot " +
                                          "\nKapcsolt rekordok: " + GetConnectedPropertyNames(ac4yClass),
                            Parameters = new List <OpenApiParameter>
                            {
                                new OpenApiParameter
                                {
                                    Name        = "id",
                                    In          = ParameterLocation.Path,
                                    Description = "A rekord id-ével törli a konkrét rekordot és a hozzá kapcsolódókat is. " +
                                                  "\nKapcsolt rekordok: " + GetConnectedPropertyNames(ac4yClass),
                                    Required = true,
                                    Schema   = new OpenApiSchema
                                    {
                                        Type = "integer"
                                    }
                                }
                            },
                            Responses = new OpenApiResponses
                            {
                                ["200"] = new OpenApiResponse
                                {
                                    Description = "A törlés sikeres volt, a rekord a db-ből is törlődött",
                                },
                                ["400"] = new OpenApiResponse
                                {
                                    Description = "A kérés hibára futott, a hibaüzenet bővebben tájékoztat",
                                    Content     =
                                    {
                                        ["application/json"] = new OpenApiMediaType
                                                    {
                                                    Schema = new OpenApiSchema
                                                    {
                                                    Title      = "Hiba üzenet",
                                                    Properties = new Dictionary <string, OpenApiSchema>
                                                    {
                                                    ["Message"] = new OpenApiSchema
                                                    {
                                                    Type        = "string",
                                                    Description = "A hibáról informáló üzenet"
                                                    }
                                                    }
                                                    }
                                                    }
                                    }
                                }
                            }
                        }
                    }
                }
                    );

                paths.Add(
                    "/" + ac4yClass.Name, new OpenApiPathItem
                {
                    Operations = new Dictionary <OperationType, OpenApiOperation>
                    {
                        [OperationType.Get] = new OpenApiOperation
                        {
                            Description = ac4yClass.Name + "típusú rekordok lekérdezése",
                            Responses   = new OpenApiResponses
                            {
                                ["200"] = new OpenApiResponse
                                {
                                    Description = "A lekérdezés sikeres volt, a rekordok listáját adja vissza",
                                    Content     =
                                    {
                                        ["application/json"] = new OpenApiMediaType
                                                    {
                                                    Schema = new OpenApiSchema
                                                    {
                                                    Type  = "array",
                                                    Items = new OpenApiSchema
                                                    {
                                                    Reference = new OpenApiReference
                                                    {
                                                    Type = ReferenceType.Schema,
                                                    Id   = ac4yClass.Name
                                                    }
                                                    }
                                                    }
                                                    }
                                    }
                                },
                                ["400"] = new OpenApiResponse
                                {
                                    Description = "A kérés hibára futott, a hibaüzenet bővebben tájékoztat",
                                    Content     =
                                    {
                                        ["application/json"] = new OpenApiMediaType
                                                    {
                                                    Schema = new OpenApiSchema
                                                    {
                                                    Title      = "Hiba üzenet",
                                                    Properties = new Dictionary <string, OpenApiSchema>
                                                    {
                                                    ["Message"] = new OpenApiSchema
                                                    {
                                                    Type        = "string",
                                                    Description = "A hibáról informáló üzenet"
                                                    }
                                                    }
                                                    }
                                                    }
                                    }
                                }
                            }
                        },
                        [OperationType.Post] = new OpenApiOperation
                        {
                            Description = "Új rekord létrehozása",
                            RequestBody = new OpenApiRequestBody
                            {
                                Required = true,
                                Content  =
                                {
                                    ["application/json"] = new OpenApiMediaType
                                            {
                                            Example = GenerateExample(ac4yClass),
                                            Schema  = new OpenApiSchema
                                            {
                                            Title     = "Vendor",
                                            Reference = new OpenApiReference
                                            {
                                            Type = ReferenceType.Schema,
                                            Id   = ac4yClass.Name + "WithoutID"
                                            }
                                            }
                                            }
                                }
                            },
                            Responses = new OpenApiResponses
                            {
                                ["200"] = new OpenApiResponse
                                {
                                    Description = "A felvitel sikeres volt, a rekord a db-be is bekerült",
                                },
                                ["400"] = new OpenApiResponse
                                {
                                    Description = "A kérés hibára futott, a hibaüzenet bővebben tájékoztat",
                                    Content     =
                                    {
                                        ["application/json"] = new OpenApiMediaType
                                                    {
                                                    Schema = new OpenApiSchema
                                                    {
                                                    Title      = "Hiba üzenet",
                                                    Properties = new Dictionary <string, OpenApiSchema>
                                                    {
                                                    ["Message"] = new OpenApiSchema
                                                    {
                                                    Type        = "string",
                                                    Description = "A hibáról informáló üzenet"
                                                    }
                                                    }
                                                    }
                                                    }
                                    }
                                }
                            }
                        }
                    }
                }
                    );
            }

            return(paths);
        }
        /// <summary>
        /// Add operation and update the operation filter settings based on the given document variant info.
        /// </summary>
        private void AddOperation(
            IDictionary <DocumentVariantInfo, OpenApiDocument> specificationDocuments,
            IDictionary <DocumentVariantInfo, ReferenceRegistryManager> referenceRegistryManagerMap,
            IList <GenerationError> operationGenerationErrors,
            DocumentVariantInfo documentVariantInfo,
            XElement operationElement,
            XElement operationConfigElement,
            TypeFetcher typeFetcher)
        {
            var paths = new OpenApiPaths();

            foreach (var preprocessingOperationFilter in _preProcessingOperationFilters)
            {
                try
                {
                    preprocessingOperationFilter.Apply(
                        paths,
                        operationElement,
                        new PreProcessingOperationFilterSettings());
                }
                catch (Exception e)
                {
                    operationGenerationErrors.Add(
                        new GenerationError
                    {
                        ExceptionType = e.GetType().Name,
                        Message       = e.Message
                    }
                        );
                }
            }

            if (!referenceRegistryManagerMap.ContainsKey(documentVariantInfo))
            {
                referenceRegistryManagerMap[documentVariantInfo] =
                    new ReferenceRegistryManager(_openApiDocumentGenerationSettings);
            }

            foreach (var pathToPathItem in paths)
            {
                var path     = pathToPathItem.Key;
                var pathItem = pathToPathItem.Value;

                foreach (var operationMethodToOperation in pathItem.Operations)
                {
                    var operationMethod = operationMethodToOperation.Key;
                    var operation       = operationMethodToOperation.Value;

                    var operationFilterSettings = new OperationFilterSettings
                    {
                        TypeFetcher = typeFetcher,
                        ReferenceRegistryManager = referenceRegistryManagerMap[documentVariantInfo],
                        Path            = path,
                        OperationMethod = operationMethod.ToString()
                    };

                    // Apply all the operation-related filters to extract information related to the operation.
                    // It is important that these are applied before the config filters below
                    // since the config filters may rely on information generated from operation filters.
                    foreach (var operationFilter in _operationFilters)
                    {
                        try
                        {
                            operationFilter.Apply(
                                operation,
                                operationElement,
                                operationFilterSettings);
                        }
                        catch (Exception e)
                        {
                            operationGenerationErrors.Add(
                                new GenerationError
                            {
                                ExceptionType = e.GetType().Name,
                                Message       = e.Message
                            }
                                );
                        }
                    }

                    if (operationConfigElement != null)
                    {
                        // Apply the config-related filters to extract information from the config xml
                        // that can be applied to the operations.
                        foreach (var configFilter in _operationConfigFilters)
                        {
                            try
                            {
                                configFilter.Apply(
                                    operation,
                                    operationConfigElement,
                                    new OperationConfigFilterSettings
                                {
                                    OperationFilterSettings = operationFilterSettings,
                                    OperationFilters        = _operationFilters
                                });
                            }
                            catch (Exception e)
                            {
                                operationGenerationErrors.Add(
                                    new GenerationError
                                {
                                    ExceptionType = e.GetType().Name,
                                    Message       = e.Message
                                }
                                    );
                            }
                        }
                    }

                    // Add the processed operation to the specification document.
                    if (!specificationDocuments.ContainsKey(documentVariantInfo))
                    {
                        specificationDocuments.Add(
                            documentVariantInfo,
                            new OpenApiDocument
                        {
                            Components = new OpenApiComponents(),
                            Paths      = new OpenApiPaths()
                        });
                    }

                    // Copy operations from local Paths object to the Paths in the specification document.
                    var documentPaths = specificationDocuments[documentVariantInfo].Paths;

                    if (!documentPaths.ContainsKey(path))
                    {
                        documentPaths.Add(
                            path,
                            new OpenApiPathItem
                        {
                            Operations =
                            {
                                [operationMethod] = operation
                            }
                        });
                    }
                    else
                    {
                        if (documentPaths[path].Operations.ContainsKey(operationMethod))
                        {
                            throw new DuplicateOperationException(
                                      path,
                                      operationMethod.ToString(),
                                      documentVariantInfo.Title);
                        }

                        documentPaths[path].Operations.Add(operationMethod, operation);
                    }
                }
            }
        }