public void InvalidDocumentationShouldYieldFailure( string testCaseName, IList <string> inputXmlFiles, IList <string> inputBinaryFiles, int expectedOperationGenerationResultsCount, string expectedJsonFile, DocumentGenerationDiagnostic expectedDocumentGenerationResult, IList <OperationGenerationDiagnostic> expectedFailureOperationGenerationResults) { _output.WriteLine(testCaseName); var documents = new List <XDocument>(); documents.AddRange(inputXmlFiles.Select(XDocument.Load)); var input = new OpenApiGeneratorConfig(documents, inputBinaryFiles, "1.0.0", FilterSetVersion.V1); GenerationDiagnostic result; var generator = new OpenApiGenerator(); var openApiDocuments = generator.GenerateDocuments(input, out result); openApiDocuments.Should().NotBeNull(); result.DocumentGenerationDiagnostic.Should().BeEquivalentTo(expectedDocumentGenerationResult); result.OperationGenerationDiagnostics.Count.Should().Be(expectedOperationGenerationResultsCount); openApiDocuments[DocumentVariantInfo.Default].Should().NotBeNull(); var failurePaths = result.OperationGenerationDiagnostics.Where( p => p.Errors.Count > 0) .ToList(); var actualDocument = openApiDocuments[DocumentVariantInfo.Default] .SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); var expectedDocument = File.ReadAllText(expectedJsonFile); _output.WriteLine(actualDocument); failurePaths.Should().BeEquivalentTo(expectedFailureOperationGenerationResults); // We are doing serialization and deserialization to force the resulting actual document // to have the exact fields we will see in the resulting document based on the contract resolver. // Without serialization and deserialization, the actual document may have fields that should // not be present, such as empty list fields. var openApiStringReader = new OpenApiStringReader(); var actualDeserializedDocument = openApiStringReader.Read( actualDocument, out OpenApiDiagnostic diagnostic); diagnostic.Errors.Count.Should().Be(0); actualDeserializedDocument .Should() .BeEquivalentTo(openApiStringReader.Read(expectedDocument, out var _)); }
/// <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); } }
/// <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="openApiInfoDescription">The description to use while populating OpenApiInfo.</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, string openApiInfoDescription, out GenerationDiagnostic generationDiagnostic) { IDictionary <DocumentVariantInfo, OpenApiDocument> openApiDocuments = new Dictionary <DocumentVariantInfo, OpenApiDocument>(); var operationElements = new List <XElement>(); var propertyElements = new List <XElement>(); foreach (var annotationXmlDocument in annotationXmlDocuments) { operationElements.AddRange( annotationXmlDocument.XPathSelectElements("//doc/members/member[url and verb]")); propertyElements.AddRange(annotationXmlDocument.XPathSelectElements("//doc/members/member") .Where( m => m.Attribute(KnownXmlStrings.Name) != null && m.Attribute(KnownXmlStrings.Name).Value.StartsWith("P:"))); } 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 { var propertyNameResolverTypeName = _openApiDocumentGenerationSettings.SchemaGenerationSettings .PropertyNameResolver.GetType().FullName; var internalGenerationContext = new InternalGenerationContext(); var internalSchemaGenerationSettings = new InternalSchemaGenerationSettings() { PropertyNameResolverName = propertyNameResolverTypeName }; generationDiagnostic = new GenerationDiagnostic(); var documentGenerationDiagnostic = new DocumentGenerationDiagnostic(); if (documentVariantElementNames?.Count > 1) { documentGenerationDiagnostic.Errors.Add(new GenerationError { Message = string.Format( SpecificationGenerationMessages.MoreThanOneVariantNameNotAllowed, documentVariantElementNames.First()) }); } IList <string> serializedOperationElements = operationElements.Select(i => i.ToString()).ToList(); // Operation config elements can contain the types that needs to be fetched too, // so add it to the list of operation elements which will be used to fetch type information. if (operationConfigElement != null) { serializedOperationElements.Add(operationConfigElement.ToString()); } #if !NETFRAMEWORK var assemblyLoader = new AssemblyLoader.AssemblyLoader(); assemblyLoader.RegisterAssemblyPaths(contractAssemblyPaths); var internalGenerationContextAsString = new AssemblyLoader.AssemblyLoader().BuildInternalGenerationContext( contractAssemblyPaths, serializedOperationElements, propertyElements.Select(i => i.ToString()).ToList(), documentVariantElementNames.FirstOrDefault(), internalSchemaGenerationSettings); internalGenerationContext = (InternalGenerationContext)JsonConvert.DeserializeObject( internalGenerationContextAsString, typeof(InternalGenerationContext)); #else using (var isolatedDomain = new AppDomainCreator <AssemblyLoader.AssemblyLoader>()) { isolatedDomain.Object.RegisterAssemblyPaths(contractAssemblyPaths); var internalGenerationContextAsString = isolatedDomain.Object.BuildInternalGenerationContext( contractAssemblyPaths, serializedOperationElements, propertyElements.Select(i => i.ToString()).ToList(), documentVariantElementNames.FirstOrDefault(), internalSchemaGenerationSettings); internalGenerationContext = (InternalGenerationContext)JsonConvert.DeserializeObject( internalGenerationContextAsString, typeof(InternalGenerationContext)); } #endif GenerationContext generationContext = internalGenerationContext.ToGenerationContext(); var operationGenerationDiagnostics = GenerateSpecificationDocuments( generationContext, operationElements, operationConfigElement, documentVariantElementNames.FirstOrDefault(), out var documents); foreach (var operationGenerationDiagnostic in operationGenerationDiagnostics) { generationDiagnostic.OperationGenerationDiagnostics.Add( new OperationGenerationDiagnostic(operationGenerationDiagnostic)); } var referenceRegistryManager = new ReferenceRegistryManager(_openApiDocumentGenerationSettings); foreach (var variantInfoDocumentValuePair in documents) { var openApiDocument = variantInfoDocumentValuePair.Value; foreach (var documentFilter in _documentFilters) { var generationErrors = documentFilter.Apply( openApiDocument, annotationXmlDocuments, new DocumentFilterSettings { OpenApiDocumentVersion = openApiDocumentVersion, OpenApiInfoDescription = openApiInfoDescription, ReferenceRegistryManager = referenceRegistryManager }, _openApiDocumentGenerationSettings); foreach (var error in generationErrors) { documentGenerationDiagnostic.Errors.Add(error); } } foreach (var filter in _postProcessingDocumentFilters) { var generationErrors = filter.Apply( openApiDocument, new PostProcessingDocumentFilterSettings { OperationGenerationDiagnostics = operationGenerationDiagnostics }); foreach (var error in generationErrors) { documentGenerationDiagnostic.Errors.Add(error); } } referenceRegistryManager.SecuritySchemeReferenceRegistry.References.CopyInto( openApiDocument.Components.SecuritySchemes); } if (documentConfigElement != null) { foreach (var documentConfigFilter in _documentConfigFilters) { var generationErrors = documentConfigFilter.Apply( documents, documentConfigElement, annotationXmlDocuments, new DocumentConfigFilterSettings()); foreach (var error in generationErrors) { documentGenerationDiagnostic.Errors.Add(error); } } } 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); } }
public void GenerateDocumentMultipleVariantsShouldYieldFailure( string testCaseName, IList <string> inputXmlFiles, IList <string> inputBinaryFiles, string configXmlFile, int expectedOperationGenerationResultsCount, IDictionary <DocumentVariantInfo, string> documentVariantInfoToExpectedJsonFileMap, DocumentGenerationDiagnostic expectedDocumentGenerationResult, List <OperationGenerationDiagnostic> expectedOperationGenerationDiagnostic) { _output.WriteLine(testCaseName); // Arrange var documents = new List <XDocument>(); documents.AddRange(inputXmlFiles.Select(XDocument.Load)); var configPath = configXmlFile; var configDocument = XDocument.Load(configPath); var generator = new OpenApiGenerator(); var input = new OpenApiGeneratorConfig(documents, inputBinaryFiles, "1.0.0", FilterSetVersion.V1) { AdvancedConfigurationXmlDocument = configDocument }; GenerationDiagnostic result; // Act var openApiDocuments = generator.GenerateDocuments(input, out result); result.Should().NotBeNull(); // All operation generations should succeed. result.OperationGenerationDiagnostics.Count(r => r.Errors.Count == 0) .Should() .Be(expectedOperationGenerationResultsCount); // Document generation should yield failure as expected in the test cases. result.DocumentGenerationDiagnostic.Should().BeEquivalentTo(expectedDocumentGenerationResult); var failedPaths = result.OperationGenerationDiagnostics.Where( p => p.Errors.Count > 0) .ToList(); failedPaths.Should().BeEquivalentTo(expectedOperationGenerationDiagnostic); openApiDocuments.Keys.Should() .BeEquivalentTo(documentVariantInfoToExpectedJsonFileMap.Keys); var actualDocuments = new List <OpenApiDocument>(); var expectedDocuments = new List <OpenApiDocument>(); foreach (var documentVariantInfoToExpectedJsonFile in documentVariantInfoToExpectedJsonFileMap) { // Verify each document variant against a json file content. var documentVariantInfo = documentVariantInfoToExpectedJsonFile.Key; var expectedJsonFile = documentVariantInfoToExpectedJsonFile.Value; openApiDocuments.TryGetValue(documentVariantInfo, out var specificationDocument); var actualDocumentAsString = specificationDocument.SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); _output.WriteLine(actualDocumentAsString); var openApiStringReader = new OpenApiStringReader(); var actualDeserializedDocument = openApiStringReader.Read( actualDocumentAsString, out OpenApiDiagnostic diagnostic); diagnostic.Errors.Count.Should().Be(0); actualDeserializedDocument .Should() .BeEquivalentTo(openApiStringReader.Read(File.ReadAllText(expectedJsonFile), out var _)); // Bug in fluent assertion method. Comparing the array of documents yields incorrect result. // Root cause unknown. This should be enabled once that bug is resolved. //actualDocuments.Add( // openApiStringReader.Read(actualDocumentAsString, out var _)); //expectedDocuments.Add( // openApiStringReader.Read(File.ReadAllText(expectedJsonFile), out var _)); } //actualDocuments.Should().BeEquivalentTo(expectedDocuments); }