diff --git a/Utilities/DataLoading/EdFi.LoadTools.Test/SmokeTests/APIGetTests.cs b/Utilities/DataLoading/EdFi.LoadTools.Test/SmokeTests/APIGetTests.cs index 550434e2ab..9eb50cebd8 100644 --- a/Utilities/DataLoading/EdFi.LoadTools.Test/SmokeTests/APIGetTests.cs +++ b/Utilities/DataLoading/EdFi.LoadTools.Test/SmokeTests/APIGetTests.cs @@ -4,22 +4,24 @@ // See the LICENSE and NOTICES files in the project root for more information. using System.Collections.Generic; +using System.IO; using System.Linq; +using System.Text; +using System.Threading.Tasks; using EdFi.LoadTools.ApiClient; using EdFi.LoadTools.Engine; using EdFi.LoadTools.SmokeTest.ApiTests; using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Hosting; using Microsoft.AspNetCore.Http; +using Microsoft.Extensions.Configuration; using Microsoft.Extensions.Hosting; -using NUnit.Framework; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers; using Moq; using Newtonsoft.Json; using Newtonsoft.Json.Linq; -using Swashbuckle.Swagger; -using Microsoft.Extensions.Configuration; -using System.Threading.Tasks; -using Microsoft.Extensions.DependencyInjection; +using NUnit.Framework; namespace EdFi.LoadTools.Test.SmokeTests { @@ -34,24 +36,32 @@ public class ApiGetTests private const string PropertyName = "aProperty"; private const string Swagger = - @"{ - paths: { - ""TestNamespace/TestResource"": { + @"{ + ""openapi"": ""3.0.1"", + ""info"": {}, + ""paths"": { + ""/TestNamespace/TestResource"": { ""get"": { ""parameters"": [ { ""name"": ""TestIdentifier"", ""in"": ""query"", ""required"": false, - ""type"": ""string"" + ""schema"": { + ""type"": ""string"" + } } ], ""responses"": { ""200"": { - ""schema"": { - ""type"": ""array"", - ""items"": { - ""$ref"": ""#/definitions/edFi_academicWeek"" + ""content"":{ + ""application/json"": { + ""schema"": { + ""type"": ""array"", + ""items"": { + ""$ref"": ""#/components/schemas/edFi_academicWeek"" + } + } } } } @@ -71,17 +81,21 @@ public class ApiGetTests new JProperty("aProperty", "b")); private readonly JArray _data = new JArray(Obj1, Obj2); - private readonly IOAuthSessionToken _token = Mock.Of(t => t.SessionToken == "something"); - private readonly SwaggerDocument Doc = JsonConvert.DeserializeObject(Swagger); + private OpenApiDocument _doc; - private readonly IOAuthTokenHandler tokenHandler = Mock.Of(); + private readonly IOAuthTokenHandler _tokenHandler = Mock.Of(); private Resource _resource; [OneTimeSetUp] public async Task Setup() { + using (var ms = new MemoryStream(Encoding.UTF8.GetBytes(Swagger))) + { + _doc = new OpenApiStreamReader().Read(ms, out var diag); + } + var config = new ConfigurationBuilder() .SetBasePath(TestContext.CurrentContext.TestDirectory) .AddJsonFile("appsettings.json", optional: true) @@ -89,12 +103,12 @@ public async Task Setup() .Build(); Address = config.GetSection("TestingWebServerAddress").Value; - + _doc.Servers.Add(new OpenApiServer { Url = $"{Address}data/v3" }); _resource = new Resource { Name = ResourceName, - BasePath = "", - Path = Doc.paths.Values.First() + BasePath = _doc.Servers.First().Url, + Path = _doc.Paths.Values.First() }; // Create and start up the host @@ -172,7 +186,7 @@ public async Task GetAll_should_store_results_in_dictionaryAsync() var configuration = Mock.Of(cfg => cfg.Url == Address); - var subject = new GetAllTest(_resource, dictionary, configuration, tokenHandler); + var subject = new GetAllTest(_resource, dictionary, configuration, _tokenHandler); var result = await subject.PerformTest(); Assert.IsNotNull(dictionary[ResourceName]); @@ -185,7 +199,7 @@ public async Task GetSkipLimitTest_should_retrieve_second_objectAsync() var dictionary = new Dictionary { [ResourceName] = _data }; var configuration = Mock.Of(cfg => cfg.Url == Address); - var subject = new GetAllSkipLimitTest(_resource, dictionary, configuration, tokenHandler); + var subject = new GetAllSkipLimitTest(_resource, dictionary, configuration, _tokenHandler); var result = await subject.PerformTest(); Assert.IsTrue(result); @@ -197,7 +211,7 @@ public async Task GetByIdTest_should_retrieve_single_objectAsync() var dictionary = new Dictionary { [ResourceName] = _data }; var configuration = Mock.Of(cfg => cfg.Url == Address); - var subject = new GetByIdTest(_resource, dictionary, configuration, tokenHandler); + var subject = new GetByIdTest(_resource, dictionary, configuration, _tokenHandler); var result = await subject.PerformTest(); Assert.IsTrue(result); @@ -209,7 +223,7 @@ public async Task GetByExampleTest_should_retrieve_arrayAsync() var dictionary = new Dictionary { [ResourceName] = _data }; var configuration = Mock.Of(cfg => cfg.Url == Address); - var subject = new GetByExampleTest(_resource, dictionary, configuration, tokenHandler); + var subject = new GetByExampleTest(_resource, dictionary, configuration, _tokenHandler); var result = await subject.PerformTest(); Assert.IsTrue(result); diff --git a/Utilities/DataLoading/EdFi.LoadTools.Test/SmokeTests/PropertyBuilderTests.cs b/Utilities/DataLoading/EdFi.LoadTools.Test/SmokeTests/PropertyBuilderTests.cs index 70ad061737..1ab89b6829 100644 --- a/Utilities/DataLoading/EdFi.LoadTools.Test/SmokeTests/PropertyBuilderTests.cs +++ b/Utilities/DataLoading/EdFi.LoadTools.Test/SmokeTests/PropertyBuilderTests.cs @@ -9,9 +9,9 @@ using EdFi.LoadTools.Engine; using EdFi.LoadTools.SmokeTest; using EdFi.LoadTools.SmokeTest.PropertyBuilders; -using NUnit.Framework; +using Microsoft.OpenApi.Models; using Moq; -using Swashbuckle.Swagger; +using NUnit.Framework; // ReSharper disable InconsistentNaming // ReSharper disable UnusedAutoPropertyAccessor.Local @@ -59,9 +59,9 @@ public void ListPropertyBuilder_should_create_empty_list_when_required_is_true() var propInfoMetadataLookup = Mock.Of( - x => x.GetMetadata(propInfo) == new Parameter + x => x.GetMetadata(propInfo) == new OpenApiParameter { - required = true + Required = true }); var builder = new ListPropertyBuilder(propInfoMetadataLookup); @@ -111,9 +111,10 @@ public void StringPropertyBuilder_should_set_string_properties() var lookup = Mock.Of( f => - f.GetMetadata(propInfo) == new Parameter + f.GetMetadata(propInfo) == new OpenApiParameter { - required = true + Required = true, + Schema = new OpenApiSchema() }); var builder = new StringPropertyBuilder(lookup); @@ -135,9 +136,9 @@ public void TimeStringPropertyBuilder_should_set_string_properties() var lookup = Mock.Of( f => - f.GetMetadata(propInfo) == new Parameter + f.GetMetadata(propInfo) == new OpenApiParameter { - required = true + Required = true }); var builder = new TimeStringPropertyBuilder(lookup); @@ -176,9 +177,9 @@ public void DateTimePropertyBuilder_should_generate_a_value_for_a_required_prope var lookup = Mock.Of( f => - f.GetMetadata(propInfo) == new Parameter + f.GetMetadata(propInfo) == new OpenApiParameter { - required = true + Required = true }); var builder = new DateTimePropertyBuilder(lookup); Assert.IsTrue(builder.BuildProperty(obj, propInfo)); @@ -192,9 +193,9 @@ public void DateTimePropertyBuilder_should_not_generate_a_value_for_an_optional_ var propInfo = typeof(Class1).GetProperty("dateTimeProperty1"); var lookup = Mock.Of( f => - f.GetMetadata(propInfo) == new Parameter + f.GetMetadata(propInfo) == new OpenApiParameter { - required = false + Required = false }); var builder = new DateTimePropertyBuilder(lookup); @@ -308,9 +309,9 @@ public void SimplePropertyBuilder_should_ignore_nullable_properties() var lookup = Mock.Of( f => - f.GetMetadata(propInfo) == new Parameter + f.GetMetadata(propInfo) == new OpenApiParameter { - required = false + Required = false }); var builder = new SimplePropertyBuilder(lookup, Mock.Of()); @@ -326,9 +327,10 @@ public void SimplePropertyBuilder_should_generate_a_value_if_required() var lookup = Mock.Of( f => - f.GetMetadata(propInfo) == new Parameter + f.GetMetadata(propInfo) == new OpenApiParameter { - required = true + Required = true, + Schema = new OpenApiSchema() }); var builder = new SimplePropertyBuilder(lookup, Mock.Of()); @@ -344,9 +346,9 @@ public void SimplePropertyBuilder_should_not_generate_a_value_if_optional() var lookup = Mock.Of( f => - f.GetMetadata(propInfo) == new Parameter + f.GetMetadata(propInfo) == new OpenApiParameter { - required = false + Required = false }); var builder = new SimplePropertyBuilder(lookup, Mock.Of()); diff --git a/Utilities/DataLoading/EdFi.LoadTools/ApiClient/ISwaggerRetriever.cs b/Utilities/DataLoading/EdFi.LoadTools/ApiClient/ISwaggerRetriever.cs index ad9aabf7a5..5b9b49ae07 100644 --- a/Utilities/DataLoading/EdFi.LoadTools/ApiClient/ISwaggerRetriever.cs +++ b/Utilities/DataLoading/EdFi.LoadTools/ApiClient/ISwaggerRetriever.cs @@ -5,12 +5,12 @@ using System.Collections.Generic; using System.Threading.Tasks; -using Swashbuckle.Swagger; +using Microsoft.OpenApi.Models; namespace EdFi.LoadTools.ApiClient { public interface ISwaggerRetriever { - Task> LoadMetadata(); + Task> LoadMetadata(); } } diff --git a/Utilities/DataLoading/EdFi.LoadTools/ApiClient/SwaggerDocument.cs b/Utilities/DataLoading/EdFi.LoadTools/ApiClient/SwaggerDocument.cs deleted file mode 100644 index 679c3b8e19..0000000000 --- a/Utilities/DataLoading/EdFi.LoadTools/ApiClient/SwaggerDocument.cs +++ /dev/null @@ -1,326 +0,0 @@ -using System.Collections.Generic; -using Newtonsoft.Json; - -/* - SPDX-License-Identifier: BSD-3-Clause - - Copyright (c) 2013, Richard Morris - All rights reserved. - - Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: - - 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. - - 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer - in the documentation and/or other materials provided with the distribution. - - 3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived - from this software without specific prior written permission. - - THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, - INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. - IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, - OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; - OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, - OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE -*/ - -// NOTE: the original document(s) have a vendorExtensions dictionary that has been removed. -namespace Swashbuckle.Swagger -{ - public class SwaggerDocument - { - public readonly string swagger = "2.0"; - - public string basePath; - - public IList consumes; - - public IDictionary definitions; - - public ExternalDocs externalDocs; - - public string host; - - public Info info; - - public IDictionary parameters; - - public IDictionary paths; - - public IList produces; - - public IDictionary responses; - - public IList schemes; - - public IList>> security; - - public IDictionary securityDefinitions; - - public IList tags; - } - - public class Info - { - public Contact contact; - - public string description; - - public License license; - - public string termsOfService; - - public string title; - public string version; - } - - public class Contact - { - public string email; - public string name; - - public string url; - } - - public class License - { - public string name; - - public string url; - } - - public class PathItem - { - public Operation delete; - - public Operation get; - - public Operation head; - - public Operation options; - - public IList parameters; - - public Operation patch; - - public Operation post; - - public Operation put; - - [JsonProperty("$ref")] - public string @ref; - } - - public class Operation - { - public IList consumes; - - public bool? deprecated; - - public string description; - - public ExternalDocs externalDocs; - - public string operationId; - - public IList parameters; - - public IList produces; - - public IDictionary responses; - - public IList schemes; - - public IList>> security; - - public string summary; - public IList tags; - } - - public class Tag - { - public string description; - - public ExternalDocs externalDocs; - public string name; - } - - public class ExternalDocs - { - public string description; - - public string url; - } - - public class Parameter : PartialSchema - { - public string description; - - public string @in; - - public string name; - - [JsonProperty("$ref")] - public string @ref; - - [JsonProperty("x-Ed-Fi-isIdentity")] - public bool? isIdentity; - - public bool? required; - - public Schema schema; - } - - public class Schema - { - public Schema additionalProperties; - - public IList allOf; - - public object @default; - - public string description; - - public string discriminator; - - public IList @enum; - - public object example; - - public bool? exclusiveMaximum; - - public bool? exclusiveMinimum; - - public ExternalDocs externalDocs; - - public string format; - - [JsonProperty("x-Ed-Fi-isIdentity")] - public bool? isIdentity; - - public Schema items; - - public decimal? maximum; - - public int? maxItems; - - public int? maxLength; - - public int? maxProperties; - - public decimal? minimum; - - public int? minItems; - - public int? minLength; - - public int? minProperties; - - public int? multipleOf; - - public string pattern; - - public IDictionary properties; - - public bool? readOnly; - - [JsonProperty("$ref")] - public string @ref; - - public IList required; - - public string title; - - public string type; - - public bool? uniqueItems; - - public Xml xml; - } - - public class PartialSchema - { - public string collectionFormat; - - public object @default; - - public IList @enum; - - public bool? exclusiveMaximum; - - public bool? exclusiveMinimum; - - public string format; - - public PartialSchema items; - - public decimal? maximum; - - public int? maxItems; - - public int? maxLength; - - public decimal? minimum; - - public int? minItems; - - public int? minLength; - - public int? multipleOf; - - public string pattern; - public string type; - - public bool? uniqueItems; - } - - public class Response - { - public string description; - - public object examples; - - public IDictionary headers; - - [JsonProperty("$ref")] - public string @ref; - - public Schema schema; - } - - public class Header : PartialSchema - { - public string description; - } - - public class Xml - { - public bool? attribute; - public string name; - - public string @namespace; - - public string prefix; - - public bool? wrapped; - } - - public class SecurityScheme - { - public string authorizationUrl; - - public string description; - - public string flow; - - public string @in; - - public string name; - - public IDictionary scopes; - - public string tokenUrl; - public string type; - } -} diff --git a/Utilities/DataLoading/EdFi.LoadTools/ApiClient/SwaggerMetadataRetriever.cs b/Utilities/DataLoading/EdFi.LoadTools/ApiClient/SwaggerMetadataRetriever.cs index 313569da17..3b3d63d4e7 100644 --- a/Utilities/DataLoading/EdFi.LoadTools/ApiClient/SwaggerMetadataRetriever.cs +++ b/Utilities/DataLoading/EdFi.LoadTools/ApiClient/SwaggerMetadataRetriever.cs @@ -10,8 +10,8 @@ using System.Threading.Tasks.Dataflow; using EdFi.LoadTools.Engine; using log4net; +using Microsoft.OpenApi.Models; using Newtonsoft.Json; -using Swashbuckle.Swagger; namespace EdFi.LoadTools.ApiClient { @@ -88,15 +88,15 @@ private async Task LoadMetadata() using (var writer = new StreamWriter(Filename)) { - var metadataBlock = new BufferBlock>(); + var metadataBlock = new BufferBlock>(); var documentsBlock = - new TransformManyBlock, JsonModelMetadata>( + new TransformManyBlock, JsonModelMetadata>( x => { var jsonModelMetadatas = new List(); - foreach (var definition in x.Value.definitions) + foreach (var definition in x.Value.Components.Schemas) { var schema = definition.Value; @@ -125,15 +125,15 @@ await writer.WriteLineAsync(JsonConvert.SerializeObject(x, Formatting.None)) // link blocks metadataBlock.LinkTo( documentsBlock, new DataflowLinkOptions - { - PropagateCompletion = true - }); + { + PropagateCompletion = true + }); documentsBlock.LinkTo( outputBlock, new DataflowLinkOptions - { - PropagateCompletion = true - }); + { + PropagateCompletion = true + }); // prime the pipeline var metadata = await _swaggerRetriever.LoadMetadata(); @@ -151,26 +151,34 @@ await writer.WriteLineAsync(JsonConvert.SerializeObject(x, Formatting.None)) } } - private IEnumerable GetProperties(Schema schema, string category, string model, + private IEnumerable GetProperties(OpenApiSchema schema, string category, string model, string modelSchema) { - return schema.properties + return schema.Properties .Select( p => new JsonModelMetadata - { - Category = category, Resource = p.Value?.@ref, Model = model, Property = p.Key, Type = GetTypeName(p), - Format = p.Value?.format, IsArray = p.Value?.type == "array", - IsRequired = schema.required?.Any(x => x.Equals(p.Key)) ?? false, Description = p.Value?.description, - Schema = modelSchema - }); + { + Category = category, + Resource = p.Value?.Reference?.ReferenceV3, + Model = model, + Property = p.Key, + Type = GetTypeName(p), + Format = p.Value?.Format, + IsArray = p.Value?.Type == "array", + IsRequired = schema.Required?.Any(x => x.Equals(p.Key)) ?? false, + Description = p.Value?.Description, + Schema = modelSchema + }); } - private string GetPathForModel(SwaggerDocument swaggerDocument, string modelName) + private string GetPathForModel(OpenApiDocument swaggerDocument, string modelName) { - return swaggerDocument.paths.FirstOrDefault( - p => p.Value?.post?.parameters.FirstOrDefault() - ?.schema.@ref == $"#/definitions/{modelName}") - .Key; + return swaggerDocument.Paths + .Where(p => p.Value.Operations.Keys.Any(k => k == OperationType.Post)) + .FirstOrDefault( + p => p.Value.Operations[OperationType.Post].Parameters.FirstOrDefault()?.Schema.Reference.ReferenceV3 == $"#/definitions/{modelName}" + ) + .Key; } private string PathToSchema(string path) @@ -180,19 +188,19 @@ private string PathToSchema(string path) .FirstOrDefault(); } - private static string GetTypeName(KeyValuePair parameterInfo) + private static string GetTypeName(KeyValuePair parameterInfo) { var referenceType = string.Empty; var parameter = parameterInfo.Value; - if (parameter?.type == "array") + if (parameter?.Type == "array") { - referenceType = parameter.items.@ref; + referenceType = parameter.Items.Reference.ReferenceV3; } - if (string.IsNullOrEmpty(parameter?.type) && !string.IsNullOrEmpty(parameter?.@ref)) + if (string.IsNullOrEmpty(parameter?.Type) && !string.IsNullOrEmpty(parameter?.Reference.ReferenceV3)) { - referenceType = parameter.@ref; + referenceType = parameter.Reference.ReferenceV3; } var parameterType = !string.IsNullOrEmpty(referenceType) @@ -201,7 +209,7 @@ private static string GetTypeName(KeyValuePair parameterInfo) return !string.IsNullOrEmpty(parameterType) ? parameterType - : parameter?.type; + : parameter?.Type; } } } diff --git a/Utilities/DataLoading/EdFi.LoadTools/ApiClient/SwaggerModels.cs b/Utilities/DataLoading/EdFi.LoadTools/ApiClient/SwaggerModels.cs index 2eb64f27aa..469e5fa628 100644 --- a/Utilities/DataLoading/EdFi.LoadTools/ApiClient/SwaggerModels.cs +++ b/Utilities/DataLoading/EdFi.LoadTools/ApiClient/SwaggerModels.cs @@ -3,7 +3,7 @@ // The Ed-Fi Alliance licenses this file to you under the Apache License, Version 2.0. // See the LICENSE and NOTICES files in the project root for more information. -using Swashbuckle.Swagger; +using Microsoft.OpenApi.Models; namespace EdFi.LoadTools.ApiClient { @@ -22,11 +22,11 @@ public class Resource public string Schema { get; set; } - public PathItem Path { get; set; } + public OpenApiPathItem Path { get; set; } public string BasePath { get; set; } - public Schema Definition { get; set; } + public OpenApiSchema Definition { get; set; } } public class Entity @@ -35,6 +35,6 @@ public class Entity public string Schema { get; set; } - public Schema Definition { get; set; } + public OpenApiSchema Definition { get; set; } } } diff --git a/Utilities/DataLoading/EdFi.LoadTools/ApiClient/SwaggerRetriever.cs b/Utilities/DataLoading/EdFi.LoadTools/ApiClient/SwaggerRetriever.cs index 90463675c7..c268af3fc0 100644 --- a/Utilities/DataLoading/EdFi.LoadTools/ApiClient/SwaggerRetriever.cs +++ b/Utilities/DataLoading/EdFi.LoadTools/ApiClient/SwaggerRetriever.cs @@ -12,8 +12,9 @@ using System.Threading.Tasks.Dataflow; using EdFi.LoadTools.Engine; using log4net; +using Microsoft.OpenApi.Models; +using Microsoft.OpenApi.Readers; using Newtonsoft.Json; -using Swashbuckle.Swagger; namespace EdFi.LoadTools.ApiClient { @@ -26,61 +27,55 @@ public SwaggerRetriever(IApiMetadataConfiguration configuration) _configuration = configuration; } - public async Task> LoadMetadata() + public async Task> LoadMetadata() { var log = LogManager.GetLogger(GetType().Name); - var result = new ConcurrentDictionary(); + var result = new ConcurrentDictionary(); using (ThreadContext.Stacks["NDC"].Push(_configuration.Url)) { log.Info($"Loading API metadata from '{_configuration.Url}'"); var metadataBlock = new BufferBlock(); - var resourcesBlock = new TransformBlock( + var resourcesBlock = new TransformBlock( async metadata => { - var j = await LoadJsonString(metadata.EndpointUri).ConfigureAwait(false); + var openApiDocument = await LoadOpenApiDocument(metadata.EndpointUri); - var doc = JsonConvert.DeserializeObject( - j, - new JsonSerializerSettings - { - PreserveReferencesHandling = PreserveReferencesHandling.Objects, - MetadataPropertyHandling = MetadataPropertyHandling.Ignore - }); - - result[metadata.Name] = doc; - return doc; + result[metadata.Name] = openApiDocument; + return openApiDocument; }); - var operationsBlack = new TransformManyBlock( + var operationsBlack = new TransformManyBlock( doc => { - return doc.paths - .SelectMany( - p => new[] - { - p.Value.get, p.Value.delete, p.Value.post, p.Value.put - }) - .Where(o => o != null) + return doc.Paths + .SelectMany(p => p.Value.Operations) + .Where(o => + o.Key == OperationType.Get || + o.Key == OperationType.Delete || + o.Key == OperationType.Post || + o.Key == OperationType.Put + ) .Select( o => new OperationRef - { - Operation = o, References = doc.parameters - }); + { + Operation = o.Value, + References = doc.Components.Parameters + }); }); var referencesBlock = new ActionBlock( or => { - for (var i = 0; i < or.Operation.parameters.Count; i++) + for (var i = 0; i < or.Operation.Parameters?.Count; i++) { - var parameter = or.Operation.parameters[i]; + var parameter = or.Operation.Parameters[i]; - if (!string.IsNullOrEmpty(parameter.@ref)) + if (!string.IsNullOrEmpty(parameter.Reference?.ReferenceV3)) { - or.Operation.parameters[i] = - or.References.First(r => r.Key == parameter.@ref.Split('/').Last()).Value; + or.Operation.Parameters[i] = + or.References.First(r => r.Key == parameter.Reference.ReferenceV3.Split('/').Last()).Value; } } }); @@ -88,21 +83,21 @@ public async Task> LoadMetadata() //link blocks metadataBlock.LinkTo( resourcesBlock, new DataflowLinkOptions - { - PropagateCompletion = true - }); + { + PropagateCompletion = true + }); resourcesBlock.LinkTo( operationsBlack, new DataflowLinkOptions - { - PropagateCompletion = true - }); + { + PropagateCompletion = true + }); operationsBlack.LinkTo( referencesBlock, new DataflowLinkOptions - { - PropagateCompletion = true - }); + { + PropagateCompletion = true + }); //prime the pipeline var json = await LoadJsonString(string.Empty).ConfigureAwait(false); @@ -142,11 +137,29 @@ private async Task LoadJsonString(string localUrl) return await response.Content.ReadAsStringAsync().ConfigureAwait(false); } + private static async Task LoadOpenApiDocument(string endpointUri) + { + HttpClientHandler handler = new HttpClientHandler + { + ServerCertificateCustomValidationCallback = (sender, cert, chain, sslPolicyErrors) => true + }; + + using var client = new HttpClient(handler) + { + Timeout = new TimeSpan(0, 0, 5, 0) + }; + + var stream = await client.GetStreamAsync(endpointUri); + var openApiDocument = new OpenApiStreamReader().Read(stream, out var diagnostic); + + return openApiDocument; + } + private class OperationRef { - public Operation Operation { get; set; } + public OpenApiOperation Operation { get; set; } - public IDictionary References { get; set; } + public IDictionary References { get; set; } } } } diff --git a/Utilities/DataLoading/EdFi.LoadTools/EdFi.LoadTools.csproj b/Utilities/DataLoading/EdFi.LoadTools/EdFi.LoadTools.csproj index e3a85e8119..e5ffa8b976 100644 --- a/Utilities/DataLoading/EdFi.LoadTools/EdFi.LoadTools.csproj +++ b/Utilities/DataLoading/EdFi.LoadTools/EdFi.LoadTools.csproj @@ -26,6 +26,8 @@ + + diff --git a/Utilities/DataLoading/EdFi.LoadTools/SmokeTest/ApiTests/GetAllSkipLimitTest.cs b/Utilities/DataLoading/EdFi.LoadTools/SmokeTest/ApiTests/GetAllSkipLimitTest.cs index 3a0002c872..03a4fd577f 100644 --- a/Utilities/DataLoading/EdFi.LoadTools/SmokeTest/ApiTests/GetAllSkipLimitTest.cs +++ b/Utilities/DataLoading/EdFi.LoadTools/SmokeTest/ApiTests/GetAllSkipLimitTest.cs @@ -8,6 +8,7 @@ using System.Net.Http; using EdFi.LoadTools.ApiClient; using EdFi.LoadTools.Engine; +using Microsoft.OpenApi.Models; using Newtonsoft.Json.Linq; namespace EdFi.LoadTools.SmokeTest.ApiTests @@ -23,7 +24,7 @@ public GetAllSkipLimitTest( protected override bool ShouldPerformTest() { - return !Operation.parameters.Any(p => p.name == "id" && p.required == true && p.@in == "path"); + return !Operation.Parameters.Any(p => p.Name == "id" && p.Required == true && p.In.Value == ParameterLocation.Path); } protected override string OnGetPath(string path) diff --git a/Utilities/DataLoading/EdFi.LoadTools/SmokeTest/ApiTests/GetAllTest.cs b/Utilities/DataLoading/EdFi.LoadTools/SmokeTest/ApiTests/GetAllTest.cs index caaf3ac3e2..28d0c1841f 100644 --- a/Utilities/DataLoading/EdFi.LoadTools/SmokeTest/ApiTests/GetAllTest.cs +++ b/Utilities/DataLoading/EdFi.LoadTools/SmokeTest/ApiTests/GetAllTest.cs @@ -8,6 +8,7 @@ using System.Linq; using EdFi.LoadTools.ApiClient; using EdFi.LoadTools.Engine; +using Microsoft.OpenApi.Models; using Newtonsoft.Json.Linq; namespace EdFi.LoadTools.SmokeTest.ApiTests @@ -26,11 +27,11 @@ public GetAllTest( protected override bool ShouldPerformTest() { return !Operation - .parameters + .Parameters .Any( - p => "id".Equals(p.name, StringComparison.CurrentCultureIgnoreCase) - && p.required == true - && "path".Equals(p.@in, StringComparison.CurrentCultureIgnoreCase)); + p => "id".Equals(p.Name, StringComparison.CurrentCultureIgnoreCase) + && p.Required == true + && p.In == ParameterLocation.Path); } } } diff --git a/Utilities/DataLoading/EdFi.LoadTools/SmokeTest/ApiTests/GetBaseTest.cs b/Utilities/DataLoading/EdFi.LoadTools/SmokeTest/ApiTests/GetBaseTest.cs index c3553b4a7a..fb70b469b7 100644 --- a/Utilities/DataLoading/EdFi.LoadTools/SmokeTest/ApiTests/GetBaseTest.cs +++ b/Utilities/DataLoading/EdFi.LoadTools/SmokeTest/ApiTests/GetBaseTest.cs @@ -15,8 +15,8 @@ using EdFi.LoadTools.Common; using EdFi.LoadTools.Engine; using log4net; +using Microsoft.OpenApi.Models; using Newtonsoft.Json.Linq; -using Swashbuckle.Swagger; namespace EdFi.LoadTools.SmokeTest.ApiTests { @@ -47,7 +47,7 @@ protected GetBaseTest( protected ILog Log => LogManager.GetLogger(GetType().Name); - protected Operation Operation => Resource.Path.get; + protected OpenApiOperation Operation => Resource.Path.Operations[OperationType.Get]; protected virtual bool NoDataAvailableForTheResource => GetResult() == null; @@ -84,7 +84,7 @@ public virtual async Task PerformTest() { var jsonString = await response.Content.ReadAsStringAsync(); - if (Resource.Path.get.responses["200"].schema.type == "array") + if (Resource.Path.Operations[OperationType.Get].Responses["200"].Content[response.Content.Headers.ContentType.MediaType].Schema.Type == "array") { results = JArray.Parse(jsonString); ResultsDictionary[Resource.Name] = results; @@ -158,7 +158,7 @@ private Uri GetPath() path = OnGetPath(path); - var uri = new Uri(new Uri(Configuration.Url), path); + var uri = new Uri(path); return uri; } diff --git a/Utilities/DataLoading/EdFi.LoadTools/SmokeTest/ApiTests/GetByExampleTest.cs b/Utilities/DataLoading/EdFi.LoadTools/SmokeTest/ApiTests/GetByExampleTest.cs index efa4fac45a..67c60c5252 100644 --- a/Utilities/DataLoading/EdFi.LoadTools/SmokeTest/ApiTests/GetByExampleTest.cs +++ b/Utilities/DataLoading/EdFi.LoadTools/SmokeTest/ApiTests/GetByExampleTest.cs @@ -8,6 +8,7 @@ using System.Linq; using EdFi.LoadTools.ApiClient; using EdFi.LoadTools.Engine; +using Microsoft.OpenApi.Models; using Newtonsoft.Json.Linq; namespace EdFi.LoadTools.SmokeTest.ApiTests @@ -24,11 +25,11 @@ public GetByExampleTest( protected override bool ShouldPerformTest() { return !Operation - .parameters + .Parameters .Any( - p => "id".Equals(p.name, StringComparison.CurrentCultureIgnoreCase) && - p.required == true && - "path".Equals(p.@in, StringComparison.CurrentCultureIgnoreCase)); + p => "id".Equals(p.Name, StringComparison.CurrentCultureIgnoreCase) && + p.Required == true && + p.In == ParameterLocation.Path); } protected override string OnGetPath(string path) @@ -44,13 +45,13 @@ protected override string OnGetPath(string path) return path + "?" + string.Join( "&", - Operation.parameters.Where(x => x.@in == "query" && x.name != "id") + Operation.Parameters.Where(x => x.In == ParameterLocation.Query && x.Name != "id") .Select( - x => jobj[x.name] == null + x => jobj[x.Name] == null ? null - : x.type == "date-time" - ? $"{x.name}={jobj[x.name]:yyyy-MM-dd}" - : $"{x.name}={Uri.EscapeDataString(jobj[x.name].ToString())}") + : x.Schema.Type == "date-time" + ? $"{x.Name}={jobj[x.Name]:yyyy-MM-dd}" + : $"{x.Name}={Uri.EscapeDataString(jobj[x.Name].ToString())}") .Where(x => !string.IsNullOrEmpty(x))); } } diff --git a/Utilities/DataLoading/EdFi.LoadTools/SmokeTest/ApiTests/GetByIdTest.cs b/Utilities/DataLoading/EdFi.LoadTools/SmokeTest/ApiTests/GetByIdTest.cs index f18152b23e..07a5b801dc 100644 --- a/Utilities/DataLoading/EdFi.LoadTools/SmokeTest/ApiTests/GetByIdTest.cs +++ b/Utilities/DataLoading/EdFi.LoadTools/SmokeTest/ApiTests/GetByIdTest.cs @@ -9,8 +9,8 @@ using System.Linq; using EdFi.LoadTools.ApiClient; using EdFi.LoadTools.Engine; +using Microsoft.OpenApi.Models; using Newtonsoft.Json.Linq; -using Swashbuckle.Swagger; namespace EdFi.LoadTools.SmokeTest.ApiTests { @@ -30,7 +30,7 @@ protected override bool ShouldPerformTest() protected override string OnGetPath(string path) { - var name = GetParameters().First().name; + var name = GetParameters().First().Name; var obj = GetResult()?[name]; @@ -42,9 +42,9 @@ protected override string OnGetPath(string path) return Path.Combine(path, obj.ToString()); } - private IEnumerable GetParameters() + private IEnumerable GetParameters() { - return Operation.parameters.Where(p => p.name == "id" && p.required == true && p.@in == "path"); + return Operation.Parameters.Where(p => p.Name == "id" && p.Required == true && p.In == ParameterLocation.Path); } } } diff --git a/Utilities/DataLoading/EdFi.LoadTools/SmokeTest/ApiTests/GetSwaggerMetadataGenerator.cs b/Utilities/DataLoading/EdFi.LoadTools/SmokeTest/ApiTests/GetSwaggerMetadataGenerator.cs index 94d47a314c..8e1834e4a0 100644 --- a/Utilities/DataLoading/EdFi.LoadTools/SmokeTest/ApiTests/GetSwaggerMetadataGenerator.cs +++ b/Utilities/DataLoading/EdFi.LoadTools/SmokeTest/ApiTests/GetSwaggerMetadataGenerator.cs @@ -9,11 +9,11 @@ using System.Reflection; using System.Text; using System.Threading.Tasks; +using EdFi.Common.Inflection; using EdFi.LoadTools.ApiClient; using EdFi.LoadTools.Common; -using EdFi.Common.Inflection; using log4net; -using Swashbuckle.Swagger; +using Microsoft.OpenApi.Models; namespace EdFi.LoadTools.SmokeTest.ApiTests { @@ -24,13 +24,13 @@ public class GetSwaggerMetadataGenerator : ITestGenerator, ITest private readonly ISwaggerRetriever _retriever; private readonly List _schemaNames; - private readonly Dictionary _swaggerDocuments; + private readonly Dictionary _swaggerDocuments; public GetSwaggerMetadataGenerator( ISwaggerRetriever retriever, Dictionary resources, List schemaNames, - Dictionary swaggerDocuments, + Dictionary swaggerDocuments, Dictionary entities) { _retriever = retriever; @@ -62,12 +62,12 @@ public async Task PerformTest() var doc = metadata[key]; _swaggerDocuments[key] = doc; - var uniqueSchemaNames = doc.paths.Keys + var uniqueSchemaNames = doc.Paths.Keys .Select(GetSchemaNameFromPath) .Distinct() .ToList(); - foreach (var path in doc.paths) + foreach (var path in doc.Paths) { if (_resources.ContainsKey(path.Key)) continue; @@ -75,18 +75,18 @@ public async Task PerformTest() _resources[path.Key] = new Resource { Name = GetResoucePath(path.Key, path.Value), - BasePath = doc.basePath, + BasePath = ReplaceVariablesInServer(doc.Servers.First()), Path = path.Value, Schema = GetSchemaNameFromPath(path.Key), - Definition = doc - .definitions + Definition = doc.Components + .Schemas .FirstOrDefault( d => TypeNameHelper.CompareTypeNames(path.Key, d.Key, "_", uniqueSchemaNames)) .Value }; } - foreach (var definition in doc.definitions) + foreach (var definition in doc.Components.Schemas) { if (_entities.ContainsKey(definition.Key)) { @@ -128,13 +128,13 @@ public IEnumerable GenerateTests() yield return this; } - private static string GetResoucePath(string path, PathItem pathItem) + private static string GetResoucePath(string path, OpenApiPathItem pathItem) { var resoucePath = path; - foreach (var parameter in pathItem.get.parameters) + foreach (var parameter in pathItem.Operations[OperationType.Get].Parameters) { - resoucePath = resoucePath.Replace($"{{{parameter.name}}}", string.Empty); + resoucePath = resoucePath.Replace($"{{{parameter.Name}}}", string.Empty); } return resoucePath.TrimEnd('/'); @@ -168,5 +168,22 @@ private static string GetSchemaNameFromPath(string path) ? GetSchemaNameParts(schemaNameParts) : string.Empty); } + + private static string ReplaceVariablesInServer(OpenApiServer server) + { + var variablesToReplace = server.Variables.Select(x => new + { + Name = x.Key, + DefaultValue = x.Value.Default.Replace("/", string.Empty) + }); + var serverUrl = server.Url; + + foreach (var variable in variablesToReplace) + { + serverUrl = serverUrl.Replace($"{{{variable.Name}}}", variable.DefaultValue); + } + + return serverUrl; + } } } diff --git a/Utilities/DataLoading/EdFi.LoadTools/SmokeTest/Interfaces.cs b/Utilities/DataLoading/EdFi.LoadTools/SmokeTest/Interfaces.cs index da219b65e5..506be74299 100644 --- a/Utilities/DataLoading/EdFi.LoadTools/SmokeTest/Interfaces.cs +++ b/Utilities/DataLoading/EdFi.LoadTools/SmokeTest/Interfaces.cs @@ -8,7 +8,7 @@ using System.Reflection; using System.Threading.Tasks; using EdFi.LoadTools.ApiClient; -using Swashbuckle.Swagger; +using Microsoft.OpenApi.Models; namespace EdFi.LoadTools.SmokeTest { @@ -78,6 +78,6 @@ public interface IPropertyBuilder public interface IPropertyInfoMetadataLookup { - Parameter GetMetadata(PropertyInfo propInfo); + OpenApiParameter GetMetadata(PropertyInfo propInfo); } } diff --git a/Utilities/DataLoading/EdFi.LoadTools/SmokeTest/PropertyBuilders/BaseBuilder.cs b/Utilities/DataLoading/EdFi.LoadTools/SmokeTest/PropertyBuilders/BaseBuilder.cs index cf1406d47a..bb8653649a 100644 --- a/Utilities/DataLoading/EdFi.LoadTools/SmokeTest/PropertyBuilders/BaseBuilder.cs +++ b/Utilities/DataLoading/EdFi.LoadTools/SmokeTest/PropertyBuilders/BaseBuilder.cs @@ -3,12 +3,12 @@ // The Ed-Fi Alliance licenses this file to you under the Apache License, Version 2.0. // See the LICENSE and NOTICES files in the project root for more information. -using log4net; using System; using System.Diagnostics; using System.Reflection; using System.Security.Cryptography; using System.Text; +using log4net; namespace EdFi.LoadTools.SmokeTest.PropertyBuilders { @@ -71,8 +71,8 @@ protected string BuildRandomString(int length) protected string BuildRandomString(PropertyInfo propertyInfo) { var parameter = _metadataLookup.GetMetadata(propertyInfo); - var length = Math.Max(parameter.minLength.HasValue ? parameter.minLength.Value: 0, _defaultStringLength); - length = Math.Min(parameter.maxLength.HasValue ? parameter.maxLength.Value : _defaultStringLength, length); + var length = Math.Max(parameter.Schema.MinLength.HasValue ? parameter.Schema.MinLength.Value: 0, _defaultStringLength); + length = Math.Min(parameter.Schema.MaxLength.HasValue ? parameter.Schema.MaxLength.Value : _defaultStringLength, length); return BuildRandomString(length); } @@ -87,25 +87,25 @@ protected bool IsRequired(PropertyInfo propertyInfo) { var parameter = _metadataLookup.GetMetadata(propertyInfo); - return parameter.required.HasValue && parameter.required.Value; + return parameter.Required; } protected int BuildRandomNumber(PropertyInfo propertyInfo) { var parameter = _metadataLookup.GetMetadata(propertyInfo); - if (parameter.minimum.HasValue && parameter.maximum.HasValue) + if (parameter.Schema.Minimum.HasValue && parameter.Schema.Maximum.HasValue) { - return Random.Next((int)parameter.minimum.Value, (int)parameter.maximum.Value); + return Random.Next((int)parameter.Schema.Minimum.Value, (int)parameter.Schema.Maximum.Value); } - else if (parameter.minimum.HasValue) + else if (parameter.Schema.Minimum.HasValue) { - var minVal = (int)parameter.minimum.Value == 0 ? _counter++ : (int)parameter.minimum.Value; + var minVal = (int)parameter.Schema.Minimum.Value == 0 ? _counter++ : (int)parameter.Schema.Minimum.Value; return minVal; } - else if(parameter.maximum.HasValue) + else if(parameter.Schema.Maximum.HasValue) { - return (int)parameter.maximum.Value; + return (int)parameter.Schema.Maximum.Value; } else { diff --git a/Utilities/DataLoading/EdFi.LoadTools/SmokeTest/PropertyInfoMetadataLookup.cs b/Utilities/DataLoading/EdFi.LoadTools/SmokeTest/PropertyInfoMetadataLookup.cs index 383406309f..427ae7f15c 100644 --- a/Utilities/DataLoading/EdFi.LoadTools/SmokeTest/PropertyInfoMetadataLookup.cs +++ b/Utilities/DataLoading/EdFi.LoadTools/SmokeTest/PropertyInfoMetadataLookup.cs @@ -4,15 +4,14 @@ // See the LICENSE and NOTICES files in the project root for more information. using System; -using System.Collections; using System.Collections.Generic; using System.Linq; using System.Reflection; using System.Text.RegularExpressions; +using EdFi.Common.Inflection; using EdFi.LoadTools.ApiClient; using EdFi.LoadTools.Common; -using EdFi.Common.Inflection; -using Swashbuckle.Swagger; +using Microsoft.OpenApi.Models; namespace EdFi.LoadTools.SmokeTest { @@ -29,7 +28,7 @@ public PropertyInfoMetadataLookup(Dictionary entityDictionary, D _schemaNames = schemaNames; } - public Parameter GetMetadata(PropertyInfo propInfo) + public OpenApiParameter GetMetadata(PropertyInfo propInfo) { string parentTypeName = Inflector.MakeInitialLowerCase(propInfo.DeclaringType?.Name); @@ -38,7 +37,7 @@ public Parameter GetMetadata(PropertyInfo propInfo) TypeNameHelper.CompareTypeNames(r.Name, parentTypeName, string.Empty, _schemaNames)) ?.Definition; - var property = resource?.properties.FirstOrDefault(x => x.Key.Equals( + var property = resource?.Properties.FirstOrDefault(x => x.Key.Equals( Regex.Replace(propInfo.Name, @"_", string.Empty), StringComparison.InvariantCultureIgnoreCase)).Value; @@ -48,18 +47,22 @@ public Parameter GetMetadata(PropertyInfo propInfo) resource = _entityDictionary.Values .FirstOrDefault(r => r.Name.Equals(parentTypeName, StringComparison.InvariantCultureIgnoreCase)) ?.Definition; - property = resource?.properties.FirstOrDefault(x => x.Key.Equals( + property = resource?.Properties.FirstOrDefault(x => x.Key.Equals( Regex.Replace(propInfo.Name, @"_", string.Empty), StringComparison.InvariantCultureIgnoreCase)).Value; } - var required = resource?.required?.FirstOrDefault( + var required = resource?.Required?.FirstOrDefault( p => p.Equals( Regex.Replace(propInfo.Name, @"_", string.Empty), StringComparison.InvariantCultureIgnoreCase)); - return new Parameter { required = required != null, minimum = property?.minimum, maximum = property?.maximum, minLength = property?.minLength, maxLength = property?.maxLength }; + return new OpenApiParameter + { + Required = required != null, + Schema = new OpenApiSchema { Minimum = property?.Minimum, Maximum = property?.Maximum, MinLength = property?.MinLength, MaxLength = property?.MaxLength } + }; } } } diff --git a/Utilities/DataLoading/EdFi.SmokeTest.Console/Application/SmokeTestsConfiguration.cs b/Utilities/DataLoading/EdFi.SmokeTest.Console/Application/SmokeTestsConfiguration.cs index 07fcb7639e..566a33bda5 100644 --- a/Utilities/DataLoading/EdFi.SmokeTest.Console/Application/SmokeTestsConfiguration.cs +++ b/Utilities/DataLoading/EdFi.SmokeTest.Console/Application/SmokeTestsConfiguration.cs @@ -119,7 +119,7 @@ public static SmokeTestsConfiguration Create(IConfigurationRoot configuration) return new SmokeTestsConfiguration { ApiUrl = ResolvedUrl(configuration.GetValue("OdsApi:ApiUrl")), - MetadataUrl = $"{ResolvedUrl(configuration.GetValue("OdsApi:MetadataUrl"))}?version=2", + MetadataUrl = $"{ResolvedUrl(configuration.GetValue("OdsApi:MetadataUrl"))}", XsdMetadataUrl = ResolvedUrl(configuration.GetValue("OdsApi:XsdMetadataUrl")), DependenciesUrl = ResolvedUrl(configuration.GetValue("OdsApi:DependenciesUrl")), OAuthUrl = ResolvedUrl(configuration.GetValue("OdsApi:OAuthUrl")), diff --git a/Utilities/DataLoading/EdFi.SmokeTest.Console/SmokeTestsApiModule.cs b/Utilities/DataLoading/EdFi.SmokeTest.Console/SmokeTestsApiModule.cs index 2c3a0e1107..a40b15e959 100644 --- a/Utilities/DataLoading/EdFi.SmokeTest.Console/SmokeTestsApiModule.cs +++ b/Utilities/DataLoading/EdFi.SmokeTest.Console/SmokeTestsApiModule.cs @@ -11,8 +11,8 @@ using EdFi.LoadTools.SmokeTest.ApiTests; using EdFi.LoadTools.SmokeTest.CommonTests; using EdFi.SmokeTest.Console.Application; +using Microsoft.OpenApi.Models; using Newtonsoft.Json.Linq; -using Swashbuckle.Swagger; namespace EdFi.SmokeTest.Console { @@ -56,7 +56,7 @@ protected override void Load(ContainerBuilder builder) builder.RegisterInstance(new Dictionary()) .SingleInstance(); - builder.RegisterInstance(new Dictionary()) + builder.RegisterInstance(new Dictionary()) .SingleInstance(); builder.RegisterInstance(new Dictionary()) diff --git a/Utilities/DataLoading/EdFi.SmokeTest.Console/SmokeTestsDestructiveSdkModule.cs b/Utilities/DataLoading/EdFi.SmokeTest.Console/SmokeTestsDestructiveSdkModule.cs index e3e2bcb8c1..32b12d0dca 100644 --- a/Utilities/DataLoading/EdFi.SmokeTest.Console/SmokeTestsDestructiveSdkModule.cs +++ b/Utilities/DataLoading/EdFi.SmokeTest.Console/SmokeTestsDestructiveSdkModule.cs @@ -16,7 +16,7 @@ using EdFi.LoadTools.SmokeTest.PropertyBuilders; using EdFi.LoadTools.SmokeTest.SdkTests; using EdFi.SmokeTest.Console.Application; -using Swashbuckle.Swagger; +using Microsoft.OpenApi.Models; using GetAllTest = EdFi.LoadTools.SmokeTest.SdkTests.GetAllTest; namespace EdFi.SmokeTest.Console @@ -137,7 +137,7 @@ protected override void Load(ContainerBuilder builder) .SingleInstance(); // Holds OpenAPI metadata - builder.RegisterInstance(new Dictionary()) + builder.RegisterInstance(new Dictionary()) .SingleInstance(); // Holds the OpenAPI Model Definitions