From 774ec4a41d97c684985eb6fd5ef9c2f0274532ba Mon Sep 17 00:00:00 2001
From: etherealjoy <sunn.ssb@live.com>
Date: Sat, 21 Sep 2019 11:00:41 +0200
Subject: [PATCH] Add initial support of Swashbuckle5 with asp.net core 3.0 rc1

---
 docs/generators/aspnetcore.md                 |   8 +-
 .../2.1/Filters/BasePathFilter.5_0.mustache   |  51 ++++++
 ...atePathParamsValidationFilter.5_0.mustache |  98 ++++++++++++
 .../2.1/Project.csproj.3_0.mustache           |  24 +++
 .../aspnetcore/2.1/Project.csproj.mustache    |  10 --
 .../aspnetcore/2.1/Startup.3_0.mustache       | 150 ++++++++++++++++++
 .../resources/aspnetcore/2.1/Startup.mustache |  13 +-
 .../aspnetcore/.openapi-generator/VERSION     |   2 +-
 8 files changed, 330 insertions(+), 26 deletions(-)
 create mode 100644 modules/openapi-generator/src/main/resources/aspnetcore/2.1/Filters/BasePathFilter.5_0.mustache
 create mode 100644 modules/openapi-generator/src/main/resources/aspnetcore/2.1/Filters/GeneratePathParamsValidationFilter.5_0.mustache
 create mode 100644 modules/openapi-generator/src/main/resources/aspnetcore/2.1/Project.csproj.3_0.mustache
 create mode 100644 modules/openapi-generator/src/main/resources/aspnetcore/2.1/Startup.3_0.mustache

diff --git a/docs/generators/aspnetcore.md b/docs/generators/aspnetcore.md
index 387832c1ab2..6ebcfcf0b8f 100644
--- a/docs/generators/aspnetcore.md
+++ b/docs/generators/aspnetcore.md
@@ -17,18 +17,14 @@ sidebar_label: aspnetcore
 |packageGuid|The GUID that will be associated with the C# project| |null|
 |sourceFolder|source folder for generated code| |src|
 |compatibilityVersion|ASP.Net Core CompatibilityVersion| |Version_2_2|
-|aspnetCoreVersion|ASP.NET Core version: 3.0 (preview4 only), 2.2, 2.1, 2.0 (deprecated)| |2.2|
-|swashbuckleVersion|Swashbucke version: 3.0.0, 4.0.0| |3.0.0|
+|aspnetCoreVersion|ASP.NET Core version: 3.0, 2.2, 2.1, 2.0 (deprecated)| |2.2|
+|swashbuckleVersion|Swashbuckle version: 3.0.0, 4.0.0, 5.0.0| |3.0.0|
 |sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true|
 |useDateTimeOffset|Use DateTimeOffset to model date-time properties| |false|
 |useCollection|Deserialize array types to Collection&lt;T&gt; instead of List&lt;T&gt;.| |false|
 |returnICollection|Return ICollection&lt;T&gt; instead of the concrete type.| |false|
 |useSwashbuckle|Uses the Swashbuckle.AspNetCore NuGet package for documentation.| |true|
 |isLibrary|Is the build a library| |false|
-|useFrameworkReference|Use frameworkReference for ASP.NET Core 3.0+ and  PackageReference  ASP.NET Core 2.2 or earlier.| |false|
-|useNewtonsoft|Uses the Newtonsoft JSON library.| |true|
-|newtonsoftVersion|Version for Microsoft.AspNetCore.Mvc.NewtonsoftJson for ASP.NET Core 3.0+| |3.0.0-preview5-19227-01|
-|useDefaultRouting|Use default routing for the  ASP.NET Core version. For 3.0 turn off default because it is not yet supported.| |true|
 |classModifier|Class Modifier can be empty, abstract| ||
 |operationModifier|Operation Modifier can be virtual, abstract or partial| |virtual|
 |buildTarget|Target to build an application or library| |program|
diff --git a/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Filters/BasePathFilter.5_0.mustache b/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Filters/BasePathFilter.5_0.mustache
new file mode 100644
index 00000000000..fa9c099863a
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Filters/BasePathFilter.5_0.mustache
@@ -0,0 +1,51 @@
+using System.Linq;
+using System.Text.RegularExpressions;
+using Microsoft.OpenApi.Models;
+using Swashbuckle.AspNetCore.Swagger;
+using Swashbuckle.AspNetCore.SwaggerGen;
+
+namespace {{packageName}}.Filters
+{
+    /// <summary>
+    /// BasePath Document Filter sets BasePath property of Swagger and removes it from the individual URL paths
+    /// </summary>
+    public class BasePathFilter : IDocumentFilter
+    {
+        /// <summary>
+        /// Constructor
+        /// </summary>
+        /// <param name="basePath">BasePath to remove from Operations</param>
+        public BasePathFilter(string basePath)
+        {
+            BasePath = basePath;
+        }
+
+        /// <summary>
+        /// Gets the BasePath of the Swagger Doc
+        /// </summary>
+        /// <returns>The BasePath of the Swagger Doc</returns>
+        public string BasePath { get; }
+
+        /// <summary>
+        /// Apply the filter
+        /// </summary>
+        /// <param name="swaggerDoc">SwaggerDocument</param>
+        /// <param name="context">FilterContext</param>
+        public void Apply(OpenApiDocument swaggerDoc, DocumentFilterContext context)
+        {
+            //swaggerDoc.BasePath = BasePath;
+
+            var pathsToModify = swaggerDoc.Paths.Where(p => p.Key.StartsWith(BasePath)).ToList();
+
+            foreach (var path in pathsToModify)
+            {
+                if (path.Key.StartsWith(BasePath))
+                {
+                    string newKey = Regex.Replace(path.Key, $"^{BasePath}", string.Empty);
+                    swaggerDoc.Paths.Remove(path.Key);
+                    swaggerDoc.Paths.Add(newKey, path.Value);
+                }
+            }
+        }
+    }
+}
diff --git a/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Filters/GeneratePathParamsValidationFilter.5_0.mustache b/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Filters/GeneratePathParamsValidationFilter.5_0.mustache
new file mode 100644
index 00000000000..d63969d4788
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Filters/GeneratePathParamsValidationFilter.5_0.mustache
@@ -0,0 +1,98 @@
+using System.ComponentModel.DataAnnotations;
+using System.Linq;
+using Microsoft.AspNetCore.Mvc.Controllers;
+using Microsoft.OpenApi.Models;
+using Swashbuckle.AspNetCore.Swagger;
+using Swashbuckle.AspNetCore.SwaggerGen;
+
+namespace {{packageName}}.Filters
+{
+    /// <summary>
+    /// Path Parameter Validation Rules Filter
+    /// </summary>
+    public class GeneratePathParamsValidationFilter : IOperationFilter
+    {
+        /// <summary>
+        /// Constructor
+        /// </summary>
+        /// <param name="operation">Operation</param>
+        /// <param name="context">OperationFilterContext</param>
+        public void Apply(OpenApiOperation operation, OperationFilterContext context)
+        {
+            var pars = context.ApiDescription.ParameterDescriptions;
+
+            foreach (var par in pars)
+            {
+                var swaggerParam = operation.Parameters.SingleOrDefault(p => p.Name == par.Name);
+
+                var attributes = ((ControllerParameterDescriptor)par.ParameterDescriptor).ParameterInfo.CustomAttributes;
+
+                if (attributes != null && attributes.Count() > 0 && swaggerParam != null)
+                {
+                    // Required - [Required]
+                    var requiredAttr = attributes.FirstOrDefault(p => p.AttributeType == typeof(RequiredAttribute));
+                    if (requiredAttr != null)
+                    {
+                        swaggerParam.Required = true;
+                    }
+
+                    // Regex Pattern [RegularExpression]
+                    var regexAttr = attributes.FirstOrDefault(p => p.AttributeType == typeof(RegularExpressionAttribute));
+                    if (regexAttr != null)
+                    {
+                        //string regex = (string)regexAttr.ConstructorArguments[0].Value;
+                        //if (swaggerParam is NonBodyParameter)
+                        //{
+                        //    ((NonBodyParameter)swaggerParam).Pattern = regex;
+                        //}
+                    }
+
+                    // String Length [StringLength]
+                    int? minLenght = null, maxLength = null;
+                    var stringLengthAttr = attributes.FirstOrDefault(p => p.AttributeType == typeof(StringLengthAttribute));
+                    if (stringLengthAttr != null)
+                    {
+                        if (stringLengthAttr.NamedArguments.Count == 1)
+                        {
+                            minLenght = (int)stringLengthAttr.NamedArguments.Single(p => p.MemberName == "MinimumLength").TypedValue.Value;
+                        }
+                        maxLength = (int)stringLengthAttr.ConstructorArguments[0].Value;
+                    }
+
+                    var minLengthAttr = attributes.FirstOrDefault(p => p.AttributeType == typeof(MinLengthAttribute));
+                    if (minLengthAttr != null)
+                    {
+                        minLenght = (int)minLengthAttr.ConstructorArguments[0].Value;
+                    }
+
+                    var maxLengthAttr = attributes.FirstOrDefault(p => p.AttributeType == typeof(MaxLengthAttribute));
+                    if (maxLengthAttr != null)
+                    {
+                        maxLength = (int)maxLengthAttr.ConstructorArguments[0].Value;
+                    }
+
+                    //if (swaggerParam is NonBodyParameter)
+                    //{
+                    //    ((NonBodyParameter)swaggerParam).MinLength = minLenght;
+                    //    ((NonBodyParameter)swaggerParam).MaxLength = maxLength;
+                    //}
+
+                    // Range [Range]
+                    var rangeAttr = attributes.FirstOrDefault(p => p.AttributeType == typeof(RangeAttribute));
+                    if (rangeAttr != null)
+                    {
+                        int rangeMin = (int)rangeAttr.ConstructorArguments[0].Value;
+                        int rangeMax = (int)rangeAttr.ConstructorArguments[1].Value;
+
+                        //if (swaggerParam is NonBodyParameter)
+                        //{
+                        //    ((NonBodyParameter)swaggerParam).Minimum = rangeMin;
+                        //    ((NonBodyParameter)swaggerParam).Maximum = rangeMax;
+                        //}
+                    }
+                }
+            }
+        }
+    }
+}
+
diff --git a/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Project.csproj.3_0.mustache b/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Project.csproj.3_0.mustache
new file mode 100644
index 00000000000..334d3993214
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Project.csproj.3_0.mustache
@@ -0,0 +1,24 @@
+<Project Sdk="{{projectSdk}}">
+  <PropertyGroup>
+    <Description>{{packageName}}</Description>
+    <Copyright>{{packageName}}</Copyright>
+    <TargetFramework>netcoreapp{{aspnetCoreVersion}}</TargetFramework>
+    <GenerateDocumentationFile>true</GenerateDocumentationFile>
+    <PreserveCompilationContext>true</PreserveCompilationContext>
+{{#isLibrary}}
+    <OutputType>Library</OutputType>
+{{/isLibrary}}
+    <AssemblyName>{{packageName}}</AssemblyName>
+    <PackageId>{{packageName}}</PackageId>
+  </PropertyGroup>
+  <ItemGroup>
+    <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="3.0.0-rc1.19457.4" />
+{{#useSwashbuckle}}
+    <PackageReference Include="Swashbuckle.AspNetCore" Version="5.0.0-rc2"/>
+    <PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="5.0.0-rc2" />
+{{/useSwashbuckle}}
+  </ItemGroup>
+  <ItemGroup>
+    <DotNetCliToolReference Include="Microsoft.VisualStudio.Web.CodeGeneration.Tools" Version="2.0.3" />
+  </ItemGroup>
+</Project>
diff --git a/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Project.csproj.mustache b/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Project.csproj.mustache
index a7b6b35bd78..610c822af0b 100644
--- a/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Project.csproj.mustache
+++ b/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Project.csproj.mustache
@@ -12,17 +12,7 @@
     <PackageId>{{packageName}}</PackageId>
   </PropertyGroup>
   <ItemGroup>
-{{#useFrameworkReference}}
-{{#isLibrary}}
-    <FrameworkReference Include="Microsoft.AspNetCore.App" />
-{{/isLibrary}}
-{{/useFrameworkReference}}
-{{^useFrameworkReference}}
     <PackageReference Include="Microsoft.AspNetCore.App" />
-{{/useFrameworkReference}}
-{{#useNewtonsoft}}
-    <PackageReference Include="Microsoft.AspNetCore.Mvc.NewtonsoftJson" Version="{{newtonsoftVersion}}" />
-{{/useNewtonsoft}}
 {{#useSwashbuckle}}
     <PackageReference Include="Swashbuckle.AspNetCore" Version="{{swashbuckleVersion}}"/>
     <PackageReference Include="Swashbuckle.AspNetCore.Annotations" Version="{{swashbuckleVersion}}" />
diff --git a/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Startup.3_0.mustache b/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Startup.3_0.mustache
new file mode 100644
index 00000000000..59dd846d205
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Startup.3_0.mustache
@@ -0,0 +1,150 @@
+{{>partial_header}}
+using System;
+using System.IO;
+using System.Reflection;
+using Microsoft.AspNetCore.Builder;
+using Microsoft.AspNetCore.Hosting;
+using Microsoft.AspNetCore.Mvc;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Hosting;
+using Newtonsoft.Json.Converters;
+using Newtonsoft.Json.Serialization;{{#useSwashbuckle}}
+using Swashbuckle.AspNetCore.Swagger;
+using Swashbuckle.AspNetCore.SwaggerGen;
+using Microsoft.OpenApi.Models;
+using {{packageName}}.Filters;{{/useSwashbuckle}}
+using {{packageName}}.Authentication;
+using Microsoft.AspNetCore.Authorization;
+
+namespace {{packageName}}
+{
+    /// <summary>
+    /// Startup
+    /// </summary>
+    public class Startup
+    {
+        /// <summary>
+        /// Constructor
+        /// </summary>
+        /// <param name="configuration"></param>
+        public Startup(IConfiguration configuration)
+        {
+            Configuration = configuration;
+        }
+
+        /// <summary>
+            /// The application configuration.
+        /// </summary>
+        public IConfiguration Configuration { get; }
+
+        /// <summary>
+        /// This method gets called by the runtime. Use this method to add services to the container.
+        /// </summary>
+        /// <param name="services"></param>
+        public void ConfigureServices(IServiceCollection services)
+        {
+{{#authMethods}}
+{{#isApiKey}}
+{{#-first}}
+            services.AddTransient<IAuthorizationHandler, ApiKeyRequirementHandler>();
+            services.AddAuthorization(authConfig =>
+            {
+{{/-first}}            
+                authConfig.AddPolicy("{{name}}",
+                    policyBuilder => policyBuilder
+                        .AddRequirements(new ApiKeyRequirement(new[] { "my-secret-key" },"{{name}}")));
+{{#-last}}
+            });
+{{/-last}}
+{{/isApiKey}}
+{{/authMethods}}
+
+            // Add framework services.
+            services
+                .AddControllers()
+                .SetCompatibilityVersion(CompatibilityVersion.Version_3_0)
+                .AddNewtonsoftJson(opts =>
+                    {
+                        opts.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
+                        opts.SerializerSettings.Converters.Add(new StringEnumConverter
+                        {
+                            NamingStrategy = new CamelCaseNamingStrategy()
+                        });
+                    }
+                );
+
+            {{#useSwashbuckle}}
+
+            services
+                .AddSwaggerGen(c =>
+                {
+                    c.SwaggerDoc("{{#version}}{{{version}}}{{/version}}{{^version}}v1{{/version}}", new OpenApiInfo
+                    {
+                        Version = "{{#version}}{{{version}}}{{/version}}{{^version}}v1{{/version}}",
+                        Title = "{{#appName}}{{{appName}}}{{/appName}}{{^appName}}{{packageName}}{{/appName}}",
+                        Description = "{{#appName}}{{{appName}}}{{/appName}}{{^appName}}{{packageName}}{{/appName}} (ASP.NET Core 3.0)",
+                        Contact = new OpenApiContact()
+                        {
+                           Name = "{{#infoName}}{{{infoName}}}{{/infoName}}{{^infoName}}OpenAPI-Generator Contributors{{/infoName}}",
+                           Url = new Uri("{{#infoUrl}}{{{infoUrl}}}{{/infoUrl}}{{^infoUrl}}https://github.com/openapitools/openapi-generator{{/infoUrl}}"),
+                           Email = "{{#infoEmail}}{{{infoEmail}}}{{/infoEmail}}"
+                        },
+                        TermsOfService = "{{#termsOfService}}{{{termsOfService}}}{{/termsOfService}}"
+                    });
+                    c.CustomSchemaIds(type => type.FriendlyId(true));
+                    c.DescribeAllEnumsAsStrings();
+                    c.IncludeXmlComments($"{AppContext.BaseDirectory}{Path.DirectorySeparatorChar}{Assembly.GetEntryAssembly().GetName().Name}.xml");
+                    {{#basePathWithoutHost}}
+                    // Sets the basePath property in the Swagger document generated
+                    c.DocumentFilter<BasePathFilter>("{{{basePathWithoutHost}}}");
+                    {{/basePathWithoutHost}}
+
+                    // Include DataAnnotation attributes on Controller Action parameters as Swagger validation rules (e.g required, pattern, ..)
+                    // Use [ValidateModelState] on Actions to actually validate it in C# as well!
+                    c.OperationFilter<GeneratePathParamsValidationFilter>();
+                });
+            {{/useSwashbuckle}}
+        }
+
+        /// <summary>
+        /// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
+        /// </summary>
+        /// <param name="app"></param>
+        /// <param name="env"></param>
+        public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
+        {
+            app.UseHttpsRedirection();
+            app
+                .UseDefaultFiles()
+                .UseStaticFiles(){{#useSwashbuckle}}
+                .UseSwagger(c =>
+                {
+                    c.RouteTemplate = "swagger/{documentName}/openapi.json";
+                })
+                .UseSwaggerUI(c =>
+                {
+                    //TODO: Either use the SwaggerGen generated Swagger contract (generated from C# classes)
+                    c.SwaggerEndpoint("/swagger/{{#version}}{{{version}}}{{/version}}{{^version}}v1{{/version}}/openapi.json", "{{#appName}}{{{appName}}}{{/appName}}{{^appName}}{{packageName}}{{/appName}}");
+
+                    //TODO: Or alternatively use the original Swagger contract that's included in the static files
+                    // c.SwaggerEndpoint("/openapi-original.json", "{{#appName}}{{{appName}}}{{/appName}}{{^appName}}{{packageName}}{{/appName}} Original");
+                }){{/useSwashbuckle}};
+
+            if (env.IsDevelopment())
+            {
+                app.UseDeveloperExceptionPage();
+            }
+            else
+            {
+                app.UseHsts();
+            }
+            app.UseRouting();
+            app.UseEndpoints(endpoints =>
+            {
+                endpoints.MapControllers();
+
+            });
+        }
+    }
+}
diff --git a/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Startup.mustache b/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Startup.mustache
index 744a282fee1..5883d72bcae 100644
--- a/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Startup.mustache
+++ b/modules/openapi-generator/src/main/resources/aspnetcore/2.1/Startup.mustache
@@ -60,16 +60,16 @@ namespace {{packageName}}
 
             // Add framework services.
             services
-                .AddMvc({{^useDefaultRouting}}opts => opts.EnableEndpointRouting = false{{/useDefaultRouting}})
+                .AddMvc()
                 {{#compatibilityVersion}}
                 .SetCompatibilityVersion(CompatibilityVersion.{{compatibilityVersion}})
                 {{/compatibilityVersion}}
-                .{{#useNewtonsoft}}AddNewtonsoftJson{{/useNewtonsoft}}{{^useNewtonsoft}}AddJsonOptions{{/useNewtonsoft}}(opts =>
+                .AddJsonOptions(opts =>
                 {
                     opts.SerializerSettings.ContractResolver = new CamelCasePropertyNamesContractResolver();
                     opts.SerializerSettings.Converters.Add(new StringEnumConverter
                     {
-                        {{#useNewtonsoft}}NamingStrategy = new CamelCaseNamingStrategy(){{/useNewtonsoft}}{{^useNewtonsoft}}CamelCaseText = true{{/useNewtonsoft}}
+                        CamelCaseText = true
                     });
                 });
             {{#useSwashbuckle}}
@@ -127,12 +127,7 @@ namespace {{packageName}}
 
                     //TODO: Or alternatively use the original Swagger contract that's included in the static files
                     // c.SwaggerEndpoint("/openapi-original.json", "{{#appName}}{{{appName}}}{{/appName}}{{^appName}}{{packageName}}{{/appName}} Original");
-                }){{/useSwashbuckle}};{{^useDefaultRouting}}
-            app.UseRouting();
-            app.UseEndpoints(endpoints =>
-	            {
-	    	        endpoints.MapControllers();
-	            });{{/useDefaultRouting}}
+                }){{/useSwashbuckle}};
 
             if (env.IsDevelopment())
             {
diff --git a/samples/server/petstore/aspnetcore/.openapi-generator/VERSION b/samples/server/petstore/aspnetcore/.openapi-generator/VERSION
index 2f81801b794..0e97bd19efb 100644
--- a/samples/server/petstore/aspnetcore/.openapi-generator/VERSION
+++ b/samples/server/petstore/aspnetcore/.openapi-generator/VERSION
@@ -1 +1 @@
-4.1.1-SNAPSHOT
\ No newline at end of file
+4.1.3-SNAPSHOT
\ No newline at end of file
-- 
GitLab