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 _)); }
public void GenerateDocumentWithOperationConfigShouldSucceed( string testCaseName, IList <string> inputXmlFiles, IList <string> inputBinaryFiles, string configXmlFile, int expectedOperationGenerationResultsCount, string expectedJsonFile) { _output.WriteLine(testCaseName); var documents = new List <XDocument>(); documents.AddRange(inputXmlFiles.Select(XDocument.Load)); var configDocument = XDocument.Load(configXmlFile); var generator = new OpenApiGenerator(); var input = new OpenApiGeneratorConfig(documents, inputBinaryFiles, "1.0.0", FilterSetVersion.V1) { AdvancedConfigurationXmlDocument = configDocument }; GenerationDiagnostic result; var openApiDocuments = generator.GenerateDocuments( input, out result); result.Should().NotBeNull(); result.DocumentGenerationDiagnostic.Errors.Count.Should().Be(0); openApiDocuments[DocumentVariantInfo.Default].Should().NotBeNull(); result.OperationGenerationDiagnostics.Count.Should().Be(expectedOperationGenerationResultsCount); var actualDocument = openApiDocuments[DocumentVariantInfo.Default] .SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); var expectedDocument = File.ReadAllText(expectedJsonFile); _output.WriteLine(actualDocument); 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 _), o => o.WithStrictOrdering()); }
public void ValidDocumentationShouldReturnCorrectDocument( string testCaseName, IList <string> inputXmlFiles, IList <string> inputBinaryFiles, string openApiDocumentVersion, int expectedOperationGenerationResultsCount, string expectedJsonFile) { _output.WriteLine(testCaseName); var documents = new List <XDocument>(); documents.AddRange(inputXmlFiles.Select(XDocument.Load)); var input = new OpenApiGeneratorConfig( documents, inputBinaryFiles, openApiDocumentVersion, FilterSetVersion.V1); GenerationDiagnostic result; var generator = new OpenApiGenerator(); var openApiDocuments = generator.GenerateDocuments(input, out result); result.Should().NotBeNull(); result.DocumentGenerationDiagnostic.Errors.Count.Should().Be(0); openApiDocuments[DocumentVariantInfo.Default].Should().NotBeNull(); result.OperationGenerationDiagnostics.Count(p => p.Errors.Count > 0).Should().Be(0); result.OperationGenerationDiagnostics.Count.Should().Be(expectedOperationGenerationResultsCount); var actualDocument = openApiDocuments[DocumentVariantInfo.Default] .SerializeAsJson(OpenApiSpecVersion.OpenApi3_0); var expectedDocument = File.ReadAllText(expectedJsonFile); _output.WriteLine(actualDocument); 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 _)); }
public void BadSchema() { var input = @"openapi: 3.0.0 info: title: foo version: bar paths: '/foo': get: responses: 200: description: ok content: application/json: schema: asdasd "; var reader = new OpenApiStringReader(); reader.Read(input, out var diagnostic); diagnostic.Errors.Should().BeEquivalentTo(new List <OpenApiError>() { new OpenApiError(new OpenApiReaderException("schema must be a map/object") { Pointer = "#/paths/~1foo/get/responses/200/content/application~1json/schema" }) }); }
public OpenApiDocument ConvertToOpenAPISpec(string json) { OpenApiStringReader reader = new OpenApiStringReader(); OpenApiDocument doc = reader.Read(json, out var diagnostic); return(doc); }
private static OpenApiDocument LoadOpenApiDocument(string json) { var reader = new OpenApiStringReader(); OpenApiDocument document = reader.Read(json, out OpenApiDiagnostic _); return(document); }
public void ParseCustomExtension() { var description = @" openapi: 3.0.0 info: title: A doc with an extension version: 1.0.0 x-foo: bar: hey baz: hi! paths: {} "; var settings = new OpenApiReaderSettings() { ExtensionParsers = { { "x-foo", (a, v) => { var fooNode = (OpenApiObject)a; return(new FooExtension() { Bar = (fooNode["bar"] as OpenApiString)?.Value, Baz = (fooNode["baz"] as OpenApiString)?.Value }); } } } }; var reader = new OpenApiStringReader(settings); var diag = new OpenApiDiagnostic(); var doc = reader.Read(description, out diag); var fooExtension = doc.Info.Extensions["x-foo"] as FooExtension; fooExtension.Should().NotBeNull(); fooExtension.Bar.Should().Be("hey"); fooExtension.Baz.Should().Be("hi!"); }
public void ShouldThrowWhenReferenceDoesNotExist() { var input = @" swagger: 2.0 info: title: test version: 1.0.0 paths: '/': get: produces: ['application/json'] responses: '200': description: ok schema: $ref: '#/definitions/doesnotexist' "; var reader = new OpenApiStringReader(); var doc = reader.Read(input, out var diagnostic); diagnostic.Errors.ShouldBeEquivalentTo(new List <OpenApiError> { new OpenApiError(new OpenApiException("Invalid Reference identifier 'doesnotexist'.")) }); doc.Should().NotBeNull(); }
public void ShouldThrowWhenReferenceTypeIsInvalid() { var input = @" swagger: 2.0 info: title: test version: 1.0.0 paths: '/': get: responses: '200': description: ok schema: $ref: '#/defi888nition/does/notexist' "; var reader = new OpenApiStringReader(); var doc = reader.Read(input, out var diagnostic); diagnostic.Errors.ShouldBeEquivalentTo(new List <OpenApiError> { new OpenApiError(new OpenApiException("Unknown reference type 'defi888nition'")) }); doc.Should().NotBeNull(); }
public void MultipleServers() { var input = @" swagger: 2.0 info: title: test version: 1.0.0 schemes: - http - https paths: {} "; var reader = new OpenApiStringReader(new OpenApiReaderSettings() { BaseUrl = new Uri("https://dev.bing.com/api") }); var doc = reader.Read(input, out var diagnostic); var server = doc.Servers.First(); Assert.Equal(2, doc.Servers.Count); Assert.Equal("http://dev.bing.com/api", server.Url); Assert.Equal("https://dev.bing.com/api", doc.Servers.Last().Url); }
public void InvalidHostShouldYieldError() { var input = @" swagger: 2.0 info: title: test version: 1.0.0 host: http://test.microsoft.com paths: {} "; var reader = new OpenApiStringReader(new OpenApiReaderSettings() { BaseUrl = new Uri("https://bing.com") }); var doc = reader.Read(input, out var diagnostic); doc.Servers.Count.Should().Be(0); diagnostic.Should().BeEquivalentTo( new OpenApiDiagnostic { Errors = { new OpenApiError("#/", "Invalid host") }, SpecificationVersion = OpenApiSpecVersion.OpenApi2_0 }); }
private static OpenApiDocument Deserialize(string data) { var reader = new OpenApiStringReader(new OpenApiReaderSettings().AddTypedRest()); var doc = reader.Read(data, out _); doc.GetTypedRest(); // Resolves references return(doc); }
/// <summary> /// Reads json into an OpenApiDocument Object using the OpenApiStringReader from Microsoft.OpenApi.Readers /// </summary> /// <param name="reader">The JsonReader that reads the incoming json</param> /// <returns></returns> public override object ReadJson(JsonReader reader, Type objectType, object existingValue, JsonSerializer serializer) { var openApiStringReader = new OpenApiStringReader(); var openApiString = JObject.Load(reader).ToString(); var openApiDocument = openApiStringReader.Read(openApiString, out var diagnostic); openApiDocument.Tags.Add(new OpenApiTag() { Name = "openapi", Description = diagnostic.SpecificationVersion.ToString() }); return(openApiDocument); }
/// <summary> /// Deserializes the json to a<see cref="OpenApiDocument"/> /// </summary> /// <param name="json"></param> /// <returns></returns> public static OpenApiDocument ReadFromJson(string json) { var openApiReader = new OpenApiStringReader(); var document = openApiReader.Read(json, out var diagnostic); if (diagnostic.Errors.Count > 0) { throw new JsonReaderException("Error reading OpenApi document. " + string.Join(Environment.NewLine, diagnostic.Errors.Select(e => e.Message))); } return(document); }
public void Read(string filename, List <ITransferObject> transferObjects) { if (!FileSystem.FileExists(filename)) { return; } string json = FileSystem.ReadAllText(filename); OpenApiStringReader reader = new OpenApiStringReader(); OpenApiDocument document = reader.Read(json, out OpenApiDiagnostic diagnostic); diagnostic.Errors?.ForEach(error => Logger.Error(error.Message)); transferObjects.Add(TransferObject.Create(document)); }
public void Can_Write_OpenApiDocument_To_Json() { var document = new ApiDocumentBuilder().Build(); var json = JsonConvert.SerializeObject(document, new OpenApiDocumentJsonConverter()); var reader = new OpenApiStringReader(); var newDocument = reader.Read(json, out var diagnostic); diagnostic.Errors.Should().BeEmpty(); newDocument.Should().NotBeNull(); newDocument.Info.Should().NotBeNull(); newDocument.Info.Title.Should().Be("My Api"); }
public async Task CanConvertSpec() { var swaggerJson = await GetSwaggerJson(); var reader = new OpenApiStringReader(); var document = reader.Read(swaggerJson, out _); var openApi3Yaml = document.Serialize(OpenApiSpecVersion.OpenApi3_0, OpenApiFormat.Yaml); var openApi3Json = document.Serialize(OpenApiSpecVersion.OpenApi3_0, OpenApiFormat.Json); var openApi2Yaml = document.Serialize(OpenApiSpecVersion.OpenApi2_0, OpenApiFormat.Json); File.WriteAllText("openapi3-spec.yaml", openApi3Yaml, Encoding.UTF8); File.WriteAllText("openapi3-spec.json", openApi3Json, Encoding.UTF8); File.WriteAllText("openapi2-spec.yaml", openApi2Yaml, Encoding.UTF8); Assert.IsFalse(string.IsNullOrWhiteSpace(openApi3Yaml)); Assert.IsFalse(string.IsNullOrWhiteSpace(openApi2Yaml)); Assert.IsFalse(string.IsNullOrWhiteSpace(openApi3Json)); }
public void NoServer() { var input = @" swagger: 2.0 info: title: test version: 1.0.0 paths: {} "; var reader = new OpenApiStringReader(new OpenApiReaderSettings() { }); var doc = reader.Read(input, out var diagnostic); Assert.Empty(doc.Servers); }
public void JustSchemeNoDefault() { var input = @" swagger: 2.0 info: title: test version: 1.0.0 schemes: - http paths: {} "; var reader = new OpenApiStringReader(new OpenApiReaderSettings() { }); var doc = reader.Read(input, out var diagnostic); Assert.Equal(0, doc.Servers.Count); }
public async Task CanReadSpec() { var swaggerJson = await GetSwaggerJson(); var reader = new OpenApiStringReader(); var document = reader.Read(swaggerJson, out var diagnostic); if (diagnostic.Errors.Count > 0) { TestContext.WriteLine("Dignostic errors:"); foreach (var openApiError in diagnostic.Errors) { TestContext.WriteLine($"{openApiError.Message} ({openApiError.Pointer})"); } Assert.Fail("diagnostic errors"); } Assert.AreEqual(OpenApiSpecVersion.OpenApi2_0, diagnostic.SpecificationVersion); Assert.IsTrue(document.Paths.Count > 0); }
public void JustBasePathNoDefault() { var input = @" swagger: 2.0 info: title: test version: 1.0.0 basePath: /baz paths: {} "; var reader = new OpenApiStringReader(new OpenApiReaderSettings() { }); var doc = reader.Read(input, out var diagnostic); var server = doc.Servers.First(); Assert.Equal(1, doc.Servers.Count); Assert.Equal("/baz", server.Url); }
public void BrokenSimpleList() { var input = @"swagger: 2.0 info: title: hey version: 1.0.0 schemes: [ { ""hello"" }] paths: { }"; var reader = new OpenApiStringReader(); reader.Read(input, out var diagnostic); diagnostic.Errors.Should().BeEquivalentTo(new List <OpenApiError>() { new OpenApiError(new OpenApiReaderException("Expected a value.") { Pointer = "#line=4" }) }); }
public void LocalHostWithCustomHost() { var input = @" swagger: 2.0 info: title: test version: 1.0.0 host: localhost:23232 paths: {} "; var reader = new OpenApiStringReader(new OpenApiReaderSettings() { BaseUrl = new Uri("https://bing.com") }); var doc = reader.Read(input, out var diagnostic); var server = doc.Servers.First(); Assert.Equal(1, doc.Servers.Count); Assert.Equal("https://localhost:23232", server.Url); }
public void Read(string url) { HttpWebRequest request = WebRequest.CreateHttp(url); request.CookieContainer = new CookieContainer(); transferObjects.OfType <TransferObject <Cookie> >().ForEach(x => request.CookieContainer.Add(x.Value)); WebResponse response = request.GetResponse(); string json; using (Stream responseStream = response.GetResponseStream()) using (StreamReader streamReader = new StreamReader(responseStream)) { json = streamReader.ReadToEnd(); } OpenApiStringReader reader = new OpenApiStringReader(); OpenApiDocument document = reader.Read(json, out OpenApiDiagnostic diagnostic); diagnostic.Errors?.ForEach(error => Logger.Error(error.Message)); transferObjects.Add(TransferObject.Create(document)); }
public void JustSchemeWithCustomHostWithEmptyPath() { var input = @" swagger: 2.0 info: title: test version: 1.0.0 schemes: - http paths: {} "; var reader = new OpenApiStringReader(new OpenApiReaderSettings() { BaseUrl = new Uri("https://bing.com") }); var doc = reader.Read(input, out var diagnostic); var server = doc.Servers.First(); Assert.Equal(1, doc.Servers.Count); Assert.Equal("http://bing.com", server.Url); }
public static OpenApiDocument ReadSpec(string spec) { try { var doc = Reader.Read(spec, out var diag); if (diag.Errors.Any()) { throw new AggregateException(diag.Errors.Select(x => new OpenApiFormatException(x))); } return(doc); } catch (ArgumentException) { // Cannot read file. // Probably spec version is not specified. // Each spec must contain either `openapi: \"x.x.x\"` or `swagger: 2.0` return(null); } catch (NullReferenceException) { throw new OpenApiFormatException("Cannot read file. Probably some refs point nowhere."); } }
public static RestApi parseToRestApi(string text) { RestApi api = new RestApi(); OpenApiStringReader reader = new OpenApiStringReader(); var document = reader.Read(text, out var diagnostic); api.Title = document.Info.Title; api.Version = document.Info.Version; api.Description = document.Info.Description; if (document.Servers.Count > 0) { api.ServerUrl = document.Servers[0].Url; } foreach (KeyValuePair <string, OpenApiPathItem> pairPath in document.Paths) { foreach (KeyValuePair <OperationType, OpenApiOperation> pairOperation in pairPath.Value.Operations) { RestApiPath apiPath = new RestApiPath(); apiPath.Path = pairPath.Key; apiPath.Method = pairOperation.Key.ToString(); apiPath.Description = pairOperation.Value.Description; foreach (OpenApiParameter para in pairOperation.Value.Parameters) { RestApiParameter apiParameter = new RestApiParameter(); apiParameter.Id = para.Name; apiParameter.Description = para.Description; apiPath.Parameters.Add(apiParameter); } if (pairOperation.Value.RequestBody != null && pairOperation.Value.RequestBody.Content.Count > 0) { RestApiBody apiBody = new RestApiBody(); foreach (KeyValuePair <string, OpenApiMediaType> bodyContentPair in pairOperation.Value.RequestBody.Content) { apiBody.ContentType = bodyContentPair.Key; var stream = new MemoryStream(); IOpenApiWriter writer = new OpenApiJsonWriter(new StreamWriter(stream)); if (bodyContentPair.Value.Example != null) { writer.WriteAny(bodyContentPair.Value.Example); } else { bodyContentPair.Value.Schema.SerializeAsV2WithoutReference(writer); } writer.Flush(); stream.Position = 0; var value = new StreamReader(stream).ReadToEnd(); apiBody.Example = value; break; } apiPath.Bodys.Add(apiBody); } foreach (KeyValuePair <string, OpenApiResponse> respPair in pairOperation.Value.Responses) { RestApiResponse apiResponse = new RestApiResponse(); apiResponse.Status = respPair.Key; if (respPair.Value.Content.Count > 0) { foreach (KeyValuePair <string, OpenApiMediaType> respBodyPair in respPair.Value.Content) { apiResponse.ContentType = respBodyPair.Key; var stream = new MemoryStream(); IOpenApiWriter writer = new OpenApiJsonWriter(new StreamWriter(stream)); if (respBodyPair.Value.Example != null) { writer.WriteAny(respBodyPair.Value.Example); } else if (respBodyPair.Value.Schema != null) { respBodyPair.Value.Schema.SerializeAsV2WithoutReference(writer); } writer.Flush(); stream.Position = 0; var value = new StreamReader(stream).ReadToEnd(); apiResponse.Example = value; break; } } apiPath.Responses.Add(apiResponse); } api.Paths.Add(apiPath); } } return(api); }
/// <summary> /// Activity function that implements CNCF Serverless workflow RESTful service invocation. /// https://github.com/serverlessworkflow/specification/blob/master/specification.md#using-functions-for-restful-service-invocations /// </summary> public static async Task <JToken> RESTfulServiceInvoker(IDurableActivityContext context, ILogger logger) { InvokeFunctionArgs?args = context.GetInput <InvokeFunctionArgs>(); if (!Uri.TryCreate(args?.Operation, UriKind.Absolute, out Uri target)) { throw new ArgumentException($"Function calls must include an '{nameof(args.Operation).ToLowerInvariant()}' field that is in the form of an absolute URI. Given function operation: '{args?.Operation}'."); } // CONSIDER: Cache the specs to reduce I/O when a particular file is reused multiple times. string specContentText; if (target.IsFile) { // file://myapis/greetingapis.json#greeting -> myapis/greetingapis.json string path = string.Concat(target.Host, target.AbsolutePath); specContentText = await File.ReadAllTextAsync(path); } // TODO: Add support for HTTP and HTTPS else { throw new NotSupportedException($"The scheme '{target.Scheme}' is not supported for function operations."); } string targetOperationId = target.Fragment.TrimStart('#'); var openApiSpecReader = new OpenApiStringReader(); OpenApiDocument?document = openApiSpecReader.Read(specContentText, out _); // TODO: What is the expectation if there are multiple server values? Uri baseUrl = new Uri(document.Servers.FirstOrDefault()?.Url ?? "/"); HttpRequestMessage?request = null; JObject? jsonContent = null; foreach ((string pathKey, OpenApiPathItem pathItem) in document.Paths) { foreach ((OperationType type, OpenApiOperation operation) in pathItem.Operations) { if (targetOperationId.Equals(operation.OperationId, StringComparison.OrdinalIgnoreCase)) { string relativePath = pathKey.TrimStart('/'); foreach ((string name, object?value) in args !.Parameters ?? ImmutableDictionary <string, object> .Empty) { if (operation.Parameters.Any(p => p.In == ParameterLocation.Path && p.Name.Equals(name, StringComparison.OrdinalIgnoreCase))) { // TODO: There are various supported ways of serializing Path parameters // https://swagger.io/docs/specification/describing-parameters/#path-parameters relativePath = relativePath.Replace($"{{{name}}}", $"{value}"); } else { // TODO: Support for more than just JObject if (jsonContent == null) { jsonContent = new JObject(); } jsonContent.Add(name, JToken.FromObject(value)); } } var url = new Uri(baseUrl, relativePath); var method = new HttpMethod(type.ToString()); request = new HttpRequestMessage(method, url); request.Headers.Add("x-ms-workflow-instance-id", context.InstanceId); if (jsonContent != null) { request.Content = new StringContent( jsonContent.ToString(Formatting.None), Encoding.UTF8, "application/json"); } break; } } if (request != null) { break; } } if (request == null) { throw new ArgumentException($"Could not find an operation with ID '{targetOperationId}'."); } using HttpResponseMessage response = await httpClient.SendAsync(request); string responseText = await response.Content.ReadAsStringAsync(); JToken result = JValue.CreateNull(); if (response.Content?.Headers?.ContentType?.MediaType?.StartsWith("application/json", StringComparison.OrdinalIgnoreCase) == true && response.Content?.Headers?.ContentLength != 0) { try { result = JToken.Parse(responseText); } catch (JsonReaderException e) { logger.LogError(e, "The HTTP response contained a payload but it was not valid JSON and will be ignored."); } } return(result); }
public void GenerateDocumentMultipleVariantsShouldSucceed( string testCaseName, IList <string> inputXmlFiles, IList <string> inputBinaryFiles, string configXmlFile, int expectedOperationGenerationResultsCount, IDictionary <DocumentVariantInfo, string> documentVariantInfoToExpectedJsonFileMap) { _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(); result.DocumentGenerationDiagnostic.Errors.Count.Should().Be(0); result.OperationGenerationDiagnostics.Count(r => r.Errors.Count == 0) .Should() .Be(expectedOperationGenerationResultsCount); 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(actualDocument); //expectedDocuments.Add(expectedDocument); } //actualDocuments.Should().BeEquivalentTo(expectedDocuments); }