/// <summary>
        /// Populate the specification documents for all document variant infos.
        /// </summary>
        /// <returns>The operation generation results from populating the specification documents.</returns>
        private IList <OperationGenerationDiagnostic> GenerateSpecificationDocuments(
            TypeFetcher typeFetcher,
            IList <XElement> operationElements,
            XElement operationConfigElement,
            string documentVariantElementName,
            out IDictionary <DocumentVariantInfo, OpenApiDocument> specificationDocuments)
        {
            specificationDocuments = new Dictionary <DocumentVariantInfo, OpenApiDocument>();

            var operationGenerationResults = new List <OperationGenerationDiagnostic>();

            var referenceRegistryManagerMap = new Dictionary <DocumentVariantInfo, ReferenceRegistryManager>();

            foreach (var operationElement in operationElements)
            {
                string        url;
                OperationType operationMethod;

                try
                {
                    url = OperationHandler.GetUrl(operationElement);
                }
                catch (InvalidUrlException e)
                {
                    operationGenerationResults.Add(
                        new OperationGenerationDiagnostic
                    {
                        Errors =
                        {
                            new GenerationError
                            {
                                ExceptionType = e.GetType().Name,
                                Message       = e.Message
                            }
                        },
                        OperationMethod = SpecificationGenerationMessages.OperationMethodNotParsedGivenUrlIsInvalid,
                        Path            = e.Url
                    });

                    continue;
                }

                try
                {
                    operationMethod = OperationHandler.GetOperationMethod(url, operationElement);
                }
                catch (InvalidVerbException e)
                {
                    operationGenerationResults.Add(
                        new OperationGenerationDiagnostic
                    {
                        Errors =
                        {
                            new GenerationError
                            {
                                ExceptionType = e.GetType().Name,
                                Message       = e.Message
                            }
                        },
                        OperationMethod = e.Verb,
                        Path            = url
                    });

                    continue;
                }

                var operationGenerationErrors = new List <GenerationError>();

                try
                {
                    AddOperation(
                        specificationDocuments,
                        referenceRegistryManagerMap,
                        operationGenerationErrors,
                        DocumentVariantInfo.Default,
                        operationElement,
                        operationConfigElement,
                        typeFetcher);
                }
                catch (Exception e)
                {
                    operationGenerationErrors.Add(
                        new GenerationError
                    {
                        ExceptionType = e.GetType().Name,
                        Message       = e.Message
                    });
                }

                var customElements = operationElement.Descendants(documentVariantElementName);
                foreach (var customElement in customElements)
                {
                    try
                    {
                        var documentVariantInfo = new DocumentVariantInfo
                        {
                            Categorizer = customElement.Name.LocalName.Trim(),
                            Title       = customElement.Value.Trim()
                        };

                        AddOperation(
                            specificationDocuments,
                            referenceRegistryManagerMap,
                            operationGenerationErrors,
                            documentVariantInfo,
                            operationElement,
                            operationConfigElement,
                            typeFetcher);
                    }
                    catch (Exception e)
                    {
                        operationGenerationErrors.Add(
                            new GenerationError
                        {
                            ExceptionType = e.GetType().Name,
                            Message       = e.Message
                        });
                    }
                }

                var operationGenerationResult = new OperationGenerationDiagnostic
                {
                    OperationMethod = operationMethod.ToString(),
                    Path            = url
                };

                if (operationGenerationErrors.Any())
                {
                    foreach (var error in operationGenerationErrors)
                    {
                        operationGenerationResult.Errors.Add(new GenerationError(error));
                    }
                }

                operationGenerationResults.Add(operationGenerationResult);
            }

            foreach (var documentVariantInfo in specificationDocuments.Keys)
            {
                referenceRegistryManagerMap[documentVariantInfo]
                .SchemaReferenceRegistry.References.CopyInto(
                    specificationDocuments[documentVariantInfo].Components.Schemas);
            }

            return(operationGenerationResults);
        }
        /// <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);
                    }
                }
            }
        }
        /// <summary>
        /// Takes in annotation xml document and returns the OpenAPI document generation result
        /// which contains OpenAPI specification document(s).
        /// </summary>
        /// <param name="annotationXmlDocuments">The list of XDocuments representing annotation xmls.</param>
        /// <param name="contractAssemblyPaths">The contract assembly paths.</param>
        /// <param name="configurationXml">The serialized XDocument representing the generation configuration.</param>
        /// <param name="openApiDocumentVersion">The version of the OpenAPI document.</param>
        /// <param name="generationDiagnostic">A string representing serialized version of
        /// <see cref="GenerationDiagnostic"/>>
        /// </param>
        /// <returns>
        /// Dictionary mapping document variant metadata to their respective OpenAPI document.
        /// </returns>
        public IDictionary <DocumentVariantInfo, OpenApiDocument> GenerateOpenApiDocuments(
            IList <XDocument> annotationXmlDocuments,
            IList <string> contractAssemblyPaths,
            string configurationXml,
            string openApiDocumentVersion,
            out GenerationDiagnostic generationDiagnostic)
        {
            IDictionary <DocumentVariantInfo, OpenApiDocument> openApiDocuments
                = new Dictionary <DocumentVariantInfo, OpenApiDocument>();

            var operationElements = new List <XElement>();

            foreach (var annotationXmlDocument in annotationXmlDocuments)
            {
                operationElements.AddRange(
                    annotationXmlDocument.XPathSelectElements("//doc/members/member[url and verb]"));
            }

            XElement operationConfigElement = null;

            XElement documentConfigElement = null;

            var documentVariantElementNames = new List <string>();

            if (!string.IsNullOrWhiteSpace(configurationXml))
            {
                var configurationXmlDocument = XDocument.Parse(configurationXml);

                operationConfigElement      = configurationXmlDocument.XPathSelectElement("//configuration/operation");
                documentConfigElement       = configurationXmlDocument.XPathSelectElement("//configuration/document");
                documentVariantElementNames = configurationXmlDocument
                                              .XPathSelectElements("//configuration/document/variant/name")
                                              .Select(variantName => variantName.Value)
                                              .ToList();
            }

            if (!operationElements.Any())
            {
                generationDiagnostic = new GenerationDiagnostic
                {
                    DocumentGenerationDiagnostic = new DocumentGenerationDiagnostic
                    {
                        Errors =
                        {
                            new GenerationError
                            {
                                Message = SpecificationGenerationMessages.NoOperationElementFoundToParse
                            }
                        }
                    }
                };

                return(openApiDocuments);
            }

            try
            {
                generationDiagnostic = new GenerationDiagnostic();
                var documentGenerationDiagnostic = new DocumentGenerationDiagnostic();

                if (documentVariantElementNames?.Count > 1)
                {
                    documentGenerationDiagnostic.Errors.Add(new GenerationError
                    {
                        Message = string.Format(
                            SpecificationGenerationMessages.MoreThanOneVariantNameNotAllowed,
                            documentVariantElementNames.First())
                    });
                }

                var typeFetcher = new TypeFetcher(contractAssemblyPaths);

                var operationGenerationDiagnostics = GenerateSpecificationDocuments(
                    typeFetcher,
                    operationElements,
                    operationConfigElement,
                    documentVariantElementNames.FirstOrDefault(),
                    out var documents);

                foreach (var operationGenerationDiagnostic in operationGenerationDiagnostics)
                {
                    generationDiagnostic.OperationGenerationDiagnostics.Add(
                        new OperationGenerationDiagnostic(operationGenerationDiagnostic));
                }

                foreach (var variantInfoDocumentValuePair in documents)
                {
                    var openApiDocument = variantInfoDocumentValuePair.Value;

                    foreach (var documentFilter in _documentFilters)
                    {
                        try
                        {
                            documentFilter.Apply(
                                openApiDocument,
                                annotationXmlDocuments,
                                new DocumentFilterSettings
                            {
                                TypeFetcher            = typeFetcher,
                                OpenApiDocumentVersion = openApiDocumentVersion,
                            },
                                _openApiDocumentGenerationSettings);
                        }
                        catch (Exception e)
                        {
                            documentGenerationDiagnostic.Errors.Add(
                                new GenerationError
                            {
                                ExceptionType = e.GetType().Name,
                                Message       = e.Message
                            });
                        }
                    }

                    foreach (var filter in _postProcessingDocumentFilters)
                    {
                        filter.Apply(
                            openApiDocument,
                            new PostProcessingDocumentFilterSettings
                        {
                            OperationGenerationDiagnostics = operationGenerationDiagnostics
                        });
                    }
                }

                if (documentConfigElement != null)
                {
                    foreach (var documentConfigFilter in _documentConfigFilters)
                    {
                        try
                        {
                            documentConfigFilter.Apply(
                                documents,
                                documentConfigElement,
                                annotationXmlDocuments,
                                new DocumentConfigFilterSettings());
                        }
                        catch (Exception e)
                        {
                            documentGenerationDiagnostic.Errors.Add(
                                new GenerationError
                            {
                                ExceptionType = e.GetType().Name,
                                Message       = e.Message
                            });
                        }
                    }
                }

                var failedOperations = generationDiagnostic.OperationGenerationDiagnostics
                                       .Where(i => i.Errors.Count > 0);

                if (failedOperations.Any())
                {
                    var totalOperationsCount = generationDiagnostic.OperationGenerationDiagnostics.Count();

                    var exception = new UnableToGenerateAllOperationsException(
                        totalOperationsCount - failedOperations.Count(), totalOperationsCount);

                    documentGenerationDiagnostic.Errors.Add(
                        new GenerationError
                    {
                        ExceptionType = exception.GetType().Name,
                        Message       = exception.Message
                    });
                }

                generationDiagnostic.DocumentGenerationDiagnostic = documentGenerationDiagnostic;
                return(documents);
            }
            catch (Exception e)
            {
                generationDiagnostic = new GenerationDiagnostic
                {
                    DocumentGenerationDiagnostic =
                        new DocumentGenerationDiagnostic
                    {
                        Errors =
                        {
                            new GenerationError
                            {
                                ExceptionType = e.GetType().Name,
                                Message       = string.Format(SpecificationGenerationMessages.UnexpectedError, e)
                            }
                        }
                    }
                };

                return(openApiDocuments);
            }
        }