diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConstants.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConstants.java index 21f921fc6551..609cac2ffdd2 100644 --- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConstants.java +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/CodegenConstants.java @@ -160,6 +160,9 @@ public class CodegenConstants { public static final String NETCORE_PROJECT_FILE = "netCoreProjectFile"; public static final String NETCORE_PROJECT_FILE_DESC = "Use the new format (.NET Core) for .NET project files (.csproj)."; + public static final String OVERRIDE_CONTROLLERS = "overrideControllers"; + public static final String OVERRIDE_CONTROLLERS_DESC = "Make generated controllers for AspNetCoreLibrary abstract and not automatically bound"; + public static final String USE_COLLECTION = "useCollection"; public static final String USE_COLLECTION_DESC = "Deserialize array types to Collection instead of List."; diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AspNetCoreServerLibraryCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AspNetCoreServerLibraryCodegen.java new file mode 100644 index 000000000000..eedc0c63ea56 --- /dev/null +++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/AspNetCoreServerLibraryCodegen.java @@ -0,0 +1,200 @@ +/* + * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) + * Copyright 2018 SmartBear Software + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openapitools.codegen.languages; + +import com.samskivert.mustache.Mustache; +import io.swagger.v3.oas.models.OpenAPI; +import org.openapitools.codegen.CodegenConstants; +import org.openapitools.codegen.CodegenOperation; +import org.openapitools.codegen.CodegenType; +import org.openapitools.codegen.SupportingFile; +import org.openapitools.codegen.utils.URLPathUtils; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.File; +import java.net.URL; +import java.util.Arrays; +import java.util.Locale; +import java.util.Map; + +public class AspNetCoreServerLibraryCodegen extends AbstractCSharpCodegen { + + public static final String ASPNET_CORE_VERSION = "aspnetCoreVersion"; + + @SuppressWarnings("hiding") + protected Logger LOGGER = LoggerFactory.getLogger(AspNetCoreServerLibraryCodegen.class); + + protected int serverPort = 8080; + protected String serverHost = "0.0.0.0"; + protected String aspnetCoreVersion= "2.1"; // default to 2.1 + + public AspNetCoreServerLibraryCodegen() { + super(); + + outputFolder = "generated-code" + File.separator + getName(); + + modelTemplateFiles.put("model.mustache", ".cs"); + apiTemplateFiles.put("controller.mustache", ".cs"); + + embeddedTemplateDir = templateDir = "aspnetcorelib/2.1"; + + // contextually reserved words + // NOTE: C# uses camel cased reserved words, while models are title cased. We don't want lowercase comparisons. + reservedWords.addAll( + Arrays.asList("var", "async", "await", "dynamic", "yield") + ); + + cliOptions.clear(); + + // CLI options + addOption(CodegenConstants.PACKAGE_NAME, + "C# package name (convention: Title.Case).", + packageName); + + addOption(CodegenConstants.PACKAGE_VERSION, + "C# package version.", + packageVersion); + + addOption(CodegenConstants.OPTIONAL_PROJECT_GUID, + CodegenConstants.OPTIONAL_PROJECT_GUID_DESC, + null); + + addOption(CodegenConstants.SOURCE_FOLDER, + CodegenConstants.SOURCE_FOLDER_DESC, + sourceFolder); + + addOption(ASPNET_CORE_VERSION, + "ASP.NET Core version: 2.1 (default), 2.0 (deprecated)", + aspnetCoreVersion); + + // CLI Switches + addSwitch(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG, + CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG_DESC, + sortParamsByRequiredFlag); + + addSwitch(CodegenConstants.USE_DATETIME_OFFSET, + CodegenConstants.USE_DATETIME_OFFSET_DESC, + useDateTimeOffsetFlag); + + addSwitch(CodegenConstants.USE_COLLECTION, + CodegenConstants.USE_COLLECTION_DESC, + useCollection); + + addSwitch(CodegenConstants.RETURN_ICOLLECTION, + CodegenConstants.RETURN_ICOLLECTION_DESC, + returnICollection); + + + addSwitch(CodegenConstants.OVERRIDE_CONTROLLERS, + CodegenConstants.OVERRIDE_CONTROLLERS_DESC, + true); + } + + @Override + public CodegenType getTag() { + return CodegenType.SERVER; + } + + @Override + public String getName() { + return "aspnetcorelib"; + } + + @Override + public String getHelp() { + return "Generates an ASP.NET Core Web API Library."; + } + + @Override + public void preprocessOpenAPI(OpenAPI openAPI) { + super.preprocessOpenAPI(openAPI); + URL url = URLPathUtils.getServerURL(openAPI); + additionalProperties.put("serverHost", url.getHost()); + additionalProperties.put("serverPort", URLPathUtils.getPort(url, 8080)); + } + + @Override + public void processOpts() { + super.processOpts(); + + // determine the ASP.NET core version setting + if (additionalProperties.containsKey(ASPNET_CORE_VERSION)) { + setAspnetCoreVersion((String) additionalProperties.get(ASPNET_CORE_VERSION)); + } + + apiPackage = packageName + ".Controllers"; + modelPackage = packageName + ".Models"; + + String packageFolder = sourceFolder + File.separator + packageName; + + supportingFiles.add(new SupportingFile("README.mustache", packageFolder, "README.md")); + supportingFiles.add(new SupportingFile("gitignore", packageFolder, ".gitignore")); + + supportingFiles.add(new SupportingFile("validateModel.mustache", packageFolder + File.separator + "Attributes", "ValidateModelStateAttribute.cs")); + supportingFiles.add(new SupportingFile("Project.csproj.mustache", packageFolder, packageName + ".csproj")); + } + + public void setAspnetCoreVersion(String aspnetCoreVersion) { + this.aspnetCoreVersion= aspnetCoreVersion; + } + + @Override + public String apiFileFolder() { + return outputFolder + File.separator + sourceFolder + File.separator + packageName + File.separator + "Controllers"; + } + + @Override + public String modelFileFolder() { + return outputFolder + File.separator + sourceFolder + File.separator + packageName + File.separator + "Models"; + } + + @Override + public Map postProcessSupportingFileData(Map objs) { + generateJSONSpecFile(objs); + return super.postProcessSupportingFileData(objs); + } + + @Override + protected void processOperation(CodegenOperation operation) { + super.processOperation(operation); + + // HACK: Unlikely in the wild, but we need to clean operation paths for MVC Routing + if (operation.path != null) { + String original = operation.path; + operation.path = operation.path.replace("?", "/"); + if (!original.equals(operation.path)) { + LOGGER.warn("Normalized " + original + " to " + operation.path + ". Please verify generated source."); + } + } + + // Converts, for example, PUT to HttpPut for controller attributes + operation.httpMethod = "Http" + operation.httpMethod.substring(0, 1) + operation.httpMethod.substring(1).toLowerCase(Locale.ROOT); + } + + @Override + public Mustache.Compiler processCompiler(Mustache.Compiler compiler) { + // To avoid unexpected behaviors when options are passed programmatically such as { "useCollection": "" } + return super.processCompiler(compiler).emptyStringIsFalse(true); + } + + @Override + public String toRegularExpression(String pattern) { + return escapeText(pattern); + } +} diff --git a/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig b/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig index fd1884948ce1..b7267f1a4d59 100644 --- a/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig +++ b/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig @@ -4,6 +4,7 @@ org.openapitools.codegen.languages.AndroidClientCodegen org.openapitools.codegen.languages.Apache2ConfigCodegen org.openapitools.codegen.languages.ApexClientCodegen org.openapitools.codegen.languages.AspNetCoreServerCodegen +org.openapitools.codegen.languages.AspNetCoreServerLibraryCodegen org.openapitools.codegen.languages.BashClientCodegen org.openapitools.codegen.languages.CLibcurlClientCodegen org.openapitools.codegen.languages.ClojureClientCodegen diff --git a/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/Project.csproj.mustache b/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/Project.csproj.mustache new file mode 100644 index 000000000000..ae7fdd806fc6 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/Project.csproj.mustache @@ -0,0 +1,19 @@ + + + + {{packageName}} + {{packageName}} + netstandard2.0 + true + true + {{packageName}} + {{packageName}} + + + + + + + + + diff --git a/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/README.mustache b/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/README.mustache new file mode 100644 index 000000000000..71c4cfebb8b3 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/README.mustache @@ -0,0 +1,27 @@ +# {{packageName}} - ASP.NET Core 2.0 Server + +{{#appDescription}} +{{{appDescription}}} +{{/appDescription}} + +## Run + +Linux/OS X: + +``` +sh build.sh +``` + +Windows: + +``` +build.bat +``` + +## Run in Docker + +``` +cd {{sourceFolder}}/{{packageName}} +docker build -t {{dockerTag}} . +docker run -p 5000:5000 {{dockerTag}} +``` diff --git a/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/abstractConcreteClassName.mustache b/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/abstractConcreteClassName.mustache new file mode 100644 index 000000000000..cd8a9476f581 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/abstractConcreteClassName.mustache @@ -0,0 +1 @@ +{{classname}}{{#overrideControllers}}Abstract{{/overrideControllers}}Controller \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/bodyParam.mustache b/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/bodyParam.mustache new file mode 100644 index 000000000000..02b0fa1d2dea --- /dev/null +++ b/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/bodyParam.mustache @@ -0,0 +1 @@ +{{#isBodyParam}}[FromBody]{{&dataType}} {{paramName}}{{/isBodyParam}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/controller.mustache b/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/controller.mustache new file mode 100644 index 000000000000..6828d62d0422 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/controller.mustache @@ -0,0 +1,65 @@ +{{>partial_header}} +using System; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Net; +using System.Threading.Tasks; +using {{packageName}}.Attributes; +using {{packageName}}.Models; +using Microsoft.AspNetCore.Mvc; + +namespace {{packageName}}.Controllers +{ {{#operations}} + + /// + /// {{description}} + /// {{#description}} + [Description("{{description}}")]{{/description}} + public interface I{{classname}}Implementation + { + {{#operation}} + /// + /// {{#summary}}{{summary}}{{/summary}} + /// {{#notes}} + /// {{notes}}{{/notes}}{{#allParams}} + /// {{description}}{{/allParams}}{{#responses}} + /// {{message}}{{/responses}} + Task<(HttpStatusCode statusCode, object response)> {{operationId}}({{#allParams}}{{&dataType}} {{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}}); + {{/operation}} + } + + /// + /// {{description}} + /// {{#description}} + [Description("{{description}}")]{{/description}} + public {{#overrideControllers}}abstract {{/overrideControllers}}class {{>abstractConcreteClassName}} : ControllerBase + { + private readonly I{{classname}}Implementation _impl; + + /// + /// The IOC injected implementation + /// Controller's implementation + /// + public {{>abstractConcreteClassName}}(I{{classname}}Implementation impl) + { + _impl = impl; + } + + {{#operation}} + /// + /// {{#summary}}{{summary}}{{/summary}} + /// {{#notes}} + /// {{notes}}{{/notes}}{{#allParams}} + /// {{description}}{{/allParams}}{{#responses}} + /// {{message}}{{/responses}} + [{{httpMethod}}] + [Route("{{{basePathWithoutHost}}}{{{path}}}")] + [ValidateModelState] + public virtual async Task {{operationId}}({{#allParams}}{{>pathParam}}{{>queryParam}}{{>bodyParam}}{{>formParam}}{{>headerParam}}{{#hasMore}}, {{/hasMore}}{{/allParams}}) + { + {{>tupleReturn}} + } + {{/operation}} + } +{{/operations}} +} diff --git a/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/enumClass.mustache b/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/enumClass.mustache new file mode 100644 index 000000000000..a8a68b998449 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/enumClass.mustache @@ -0,0 +1,18 @@ + + /// + /// {{^description}}Gets or Sets {{{name}}}{{/description}}{{#description}}{{{description}}}{{/description}} + /// + {{#description}} + /// {{{description}}} + {{/description}} + {{#allowableValues}}{{#enumVars}}{{#-first}}{{#isString}}[JsonConverter(typeof(Newtonsoft.Json.Converters.StringEnumConverter))]{{/isString}}{{/-first}}{{/enumVars}}{{/allowableValues}} + public enum {{#datatypeWithEnum}}{{.}}{{/datatypeWithEnum}}{{^datatypeWithEnum}}{{classname}}{{/datatypeWithEnum}} + { + {{#allowableValues}}{{#enumVars}} + /// + /// Enum {{name}} for {{{value}}} + /// + {{#isString}}[EnumMember(Value = "{{{value}}}")]{{/isString}} + {{name}}{{^isString}} = {{{value}}}{{/isString}}{{#isString}} = {{-index}}{{/isString}}{{^-last}}, + {{/-last}}{{/enumVars}}{{/allowableValues}} + } diff --git a/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/formParam.mustache b/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/formParam.mustache new file mode 100644 index 000000000000..2d42dc2916ba --- /dev/null +++ b/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/formParam.mustache @@ -0,0 +1 @@ +{{#isFormParam}}[FromForm]{{#required}}[Required()]{{/required}}{{#pattern}}[RegularExpression("{{{pattern}}}")]{{/pattern}}{{#minLength}}{{#maxLength}}[StringLength({{maxLength}}, MinimumLength={{minLength}}){{/maxLength}}{{/minLength}}{{#minLength}}{{^maxLength}} [MinLength({{minLength}})]{{/maxLength}}{{/minLength}}{{^minLength}}{{#maxLength}} [MaxLength({{maxLength}})]{{/maxLength}}{{/minLength}}{{#minimum}}{{#maximum}}[Range({{minimum}}, {{maximum}})]{{/maximum}}{{/minimum}}{{&dataType}} {{paramName}}{{/isFormParam}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/gitignore b/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/gitignore new file mode 100644 index 000000000000..cd9b840e5498 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/gitignore @@ -0,0 +1,208 @@ +PID + +## Ignore Visual Studio temporary files, build results, and +## files generated by popular Visual Studio add-ons. + +# User-specific files +*.suo +*.user +*.userosscache +*.sln.docstates + +# User-specific files (MonoDevelop/Xamarin Studio) +*.userprefs + +# Build results +[Dd]ebug/ +[Dd]ebugPublic/ +[Rr]elease/ +[Rr]eleases/ +x64/ +x86/ +build/ +bld/ +[Bb]in/ +[Oo]bj/ + +# Visual Studio 2015 cache/options directory +.vs/ + +# MSTest test Results +[Tt]est[Rr]esult*/ +[Bb]uild[Ll]og.* + +# NUNIT +*.VisualState.xml +TestResult.xml + +# Build Results of an ATL Project +[Dd]ebugPS/ +[Rr]eleasePS/ +dlldata.c + +# DNX +project.lock.json +artifacts/ + +*_i.c +*_p.c +*_i.h +*.ilk +*.meta +*.obj +*.pch +*.pdb +*.pgc +*.pgd +*.rsp +*.sbr +*.tlb +*.tli +*.tlh +*.tmp +*.tmp_proj +*.log +*.vspscc +*.vssscc +.builds +*.pidb +*.svclog +*.scc + +# Chutzpah Test files +_Chutzpah* + +# Visual C++ cache files +ipch/ +*.aps +*.ncb +*.opensdf +*.sdf +*.cachefile + +# Visual Studio profiler +*.psess +*.vsp +*.vspx + +# TFS 2012 Local Workspace +$tf/ + +# Guidance Automation Toolkit +*.gpState + +# ReSharper is a .NET coding add-in +_ReSharper*/ +*.[Rr]e[Ss]harper +*.DotSettings.user + +# JustCode is a .NET coding add-in +.JustCode + +# TeamCity is a build add-in +_TeamCity* + +# DotCover is a Code Coverage Tool +*.dotCover + +# NCrunch +_NCrunch_* +.*crunch*.local.xml + +# MightyMoose +*.mm.* +AutoTest.Net/ + +# Web workbench (sass) +.sass-cache/ + +# Installshield output folder +[Ee]xpress/ + +# DocProject is a documentation generator add-in +DocProject/buildhelp/ +DocProject/Help/*.HxT +DocProject/Help/*.HxC +DocProject/Help/*.hhc +DocProject/Help/*.hhk +DocProject/Help/*.hhp +DocProject/Help/Html2 +DocProject/Help/html + +# Click-Once directory +publish/ + +# Publish Web Output +*.[Pp]ublish.xml +*.azurePubxml +# TODO: Comment the next line if you want to checkin your web deploy settings +# but database connection strings (with potential passwords) will be unencrypted +*.pubxml +*.publishproj + +# NuGet Packages +*.nupkg +# The packages folder can be ignored because of Package Restore +**/packages/* +# except build/, which is used as an MSBuild target. +!**/packages/build/ +# Uncomment if necessary however generally it will be regenerated when needed +#!**/packages/repositories.config + +# Windows Azure Build Output +csx/ +*.build.csdef + +# Windows Store app package directory +AppPackages/ + +# Visual Studio cache files +# files ending in .cache can be ignored +*.[Cc]ache +# but keep track of directories ending in .cache +!*.[Cc]ache/ + +# Others +ClientBin/ +[Ss]tyle[Cc]op.* +~$* +*~ +*.dbmdl +*.dbproj.schemaview +*.pfx +*.publishsettings +node_modules/ +bower_components/ +orleans.codegen.cs + +# RIA/Silverlight projects +Generated_Code/ + +# Backup & report files from converting an old project file +# to a newer Visual Studio version. Backup files are not needed, +# because we have git ;-) +_UpgradeReport_Files/ +Backup*/ +UpgradeLog*.XML +UpgradeLog*.htm + +# SQL Server files +*.mdf +*.ldf + +# Business Intelligence projects +*.rdl.data +*.bim.layout +*.bim_*.settings + +# Microsoft Fakes +FakesAssemblies/ + +# Node.js Tools for Visual Studio +.ntvs_analysis.dat + +# Visual Studio 6 build log +*.plg + +# Visual Studio 6 workspace options file +*.opt diff --git a/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/headerParam.mustache b/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/headerParam.mustache new file mode 100644 index 000000000000..45a5be9d7b5b --- /dev/null +++ b/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/headerParam.mustache @@ -0,0 +1 @@ +{{#isHeaderParam}}[FromHeader]{{#required}}[Required()]{{/required}}{{#pattern}}[RegularExpression("{{{pattern}}}")]{{/pattern}}{{#minLength}}{{#maxLength}}[StringLength({{maxLength}}, MinimumLength={{minLength}}){{/maxLength}}{{/minLength}}{{#minLength}}{{^maxLength}} [MinLength({{minLength}})]{{/maxLength}}{{/minLength}}{{^minLength}}{{#maxLength}} [MaxLength({{maxLength}})]{{/maxLength}}{{/minLength}}{{#minimum}}{{#maximum}}[Range({{minimum}}, {{maximum}})]{{/maximum}}{{/minimum}}{{&dataType}} {{paramName}}{{/isHeaderParam}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/model.mustache b/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/model.mustache new file mode 100644 index 000000000000..4ff19b963b8a --- /dev/null +++ b/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/model.mustache @@ -0,0 +1,136 @@ +{{>partial_header}} +using System; +using System.Linq; +using System.Text; +using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; +using System.Runtime.Serialization; +using Newtonsoft.Json; + +{{#models}} +{{#model}} +namespace {{packageName}}.Models +{ {{#isEnum}}{{>enumClass}}{{/isEnum}}{{^isEnum}} + /// + /// {{description}} + /// + [DataContract] + public partial class {{classname}} : {{#parent}}{{{parent}}}, {{/parent}}IEquatable<{{classname}}> + { {{#vars}}{{#isEnum}}{{>enumClass}}{{/isEnum}}{{#items.isEnum}}{{#items}}{{>enumClass}}{{/items}}{{/items.isEnum}} + /// + /// {{^description}}Gets or Sets {{{name}}}{{/description}}{{#description}}{{description}}{{/description}} + /// + {{#description}} + /// {{description}} + {{/description}} + {{#required}} + [Required] + {{/required}} + [DataMember(Name="{{baseName}}")] + {{#isEnum}} + public {{{datatypeWithEnum}}}{{#isEnum}}{{^isContainer}}?{{/isContainer}}{{/isEnum}} {{name}} { get; set; } + {{/isEnum}} + {{^isEnum}} + public {{{dataType}}} {{name}} { get; {{#isReadOnly}}private {{/isReadOnly}}set; } + {{/isEnum}} + {{#hasMore}} + {{/hasMore}} + {{/vars}} + + /// + /// Returns the string presentation of the object + /// + /// String presentation of the object + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append("class {{classname}} {\n"); + {{#vars}} + sb.Append(" {{name}}: ").Append({{name}}).Append("\n"); + {{/vars}} + sb.Append("}\n"); + return sb.ToString(); + } + + /// + /// Returns the JSON string presentation of the object + /// + /// JSON string presentation of the object + public {{#parent}}{{^isMapModel}}{{^isArrayModel}}new {{/isArrayModel}}{{/isMapModel}}{{/parent}}string ToJson() + { + return JsonConvert.SerializeObject(this, Formatting.Indented); + } + + /// + /// Returns true if objects are equal + /// + /// Object to be compared + /// Boolean + public override bool Equals(object obj) + { + if (obj is null) return false; + if (ReferenceEquals(this, obj)) return true; + return obj.GetType() == GetType() && Equals(({{classname}})obj); + } + + /// + /// Returns true if {{classname}} instances are equal + /// + /// Instance of {{classname}} to be compared + /// Boolean + public bool Equals({{classname}} other) + { + if (other is null) return false; + if (ReferenceEquals(this, other)) return true; + + return {{#vars}}{{^isContainer}} + ( + {{name}} == other.{{name}} || + {{name}} != null && + {{name}}.Equals(other.{{name}}) + ){{#hasMore}} && {{/hasMore}}{{/isContainer}}{{#isContainer}} + ( + {{name}} == other.{{name}} || + {{name}} != null && + {{name}}.SequenceEqual(other.{{name}}) + ){{#hasMore}} && {{/hasMore}}{{/isContainer}}{{/vars}}{{^vars}}false{{/vars}}; + } + + /// + /// Gets the hash code + /// + /// Hash code + public override int GetHashCode() + { + unchecked // Overflow is fine, just wrap + { + var hashCode = 41; + // Suitable nullity checks etc, of course :) + {{#vars}} + if ({{name}} != null) + hashCode = hashCode * 59 + {{name}}.GetHashCode(); + {{/vars}} + return hashCode; + } + } + + #region Operators + #pragma warning disable 1591 + + public static bool operator ==({{classname}} left, {{classname}} right) + { + return Equals(left, right); + } + + public static bool operator !=({{classname}} left, {{classname}} right) + { + return !Equals(left, right); + } + + #pragma warning restore 1591 + #endregion Operators + } +{{/isEnum}} +{{/model}} +{{/models}} +} diff --git a/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/partial_header.mustache b/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/partial_header.mustache new file mode 100644 index 000000000000..4a682818a37a --- /dev/null +++ b/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/partial_header.mustache @@ -0,0 +1,13 @@ +/* + {{#appName}} + * {{{appName}}} + * + {{/appName}} + {{#appDescription}} + * {{{appDescription}}} + * + {{/appDescription}} + * {{#version}}OpenAPI spec version: {{{version}}}{{/version}} + * {{#infoEmail}}Contact: {{{infoEmail}}}{{/infoEmail}} + * Generated by: https://openapi-generator.tech + */ diff --git a/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/pathParam.mustache b/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/pathParam.mustache new file mode 100644 index 000000000000..70303432d48d --- /dev/null +++ b/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/pathParam.mustache @@ -0,0 +1 @@ +{{#isPathParam}}[FromRoute]{{#required}}[Required]{{/required}}{{#pattern}}[RegularExpression("{{{pattern}}}")]{{/pattern}}{{#minLength}}{{#maxLength}}[StringLength({{maxLength}}, MinimumLength={{minLength}}){{/maxLength}}{{/minLength}}{{#minLength}}{{^maxLength}} [MinLength({{minLength}})]{{/maxLength}}{{/minLength}}{{^minLength}}{{#maxLength}} [MaxLength({{maxLength}})]{{/maxLength}}{{/minLength}}{{#minimum}}{{#maximum}}[Range({{minimum}}, {{maximum}})]{{/maximum}}{{/minimum}}{{&dataType}} {{paramName}}{{/isPathParam}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/queryParam.mustache b/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/queryParam.mustache new file mode 100644 index 000000000000..e9fa09b005d3 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/queryParam.mustache @@ -0,0 +1 @@ +{{#isQueryParam}}[FromQuery]{{#required}}[Required()]{{/required}}{{#pattern}}[RegularExpression("{{{pattern}}}")]{{/pattern}}{{#minLength}}{{#maxLength}}[StringLength({{maxLength}}, MinimumLength={{minLength}}){{/maxLength}}{{/minLength}}{{#minLength}}{{^maxLength}} [MinLength({{minLength}})]{{/maxLength}}{{/minLength}}{{^minLength}}{{#maxLength}} [MaxLength({{maxLength}})]{{/maxLength}}{{/minLength}}{{#minimum}}{{#maximum}}[Range({{minimum}}, {{maximum}})]{{/maximum}}{{/minimum}}{{&dataType}} {{paramName}}{{/isQueryParam}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/tags.mustache b/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/tags.mustache new file mode 100644 index 000000000000..c97df19949e6 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/tags.mustache @@ -0,0 +1 @@ +{{!TODO: Need iterable tags object...}}{{#tags}}, Tags = new[] { {{/tags}}"{{#tags}}{{tag}} {{/tags}}"{{#tags}} }{{/tags}} \ No newline at end of file diff --git a/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/tupleReturn.mustache b/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/tupleReturn.mustache new file mode 100644 index 000000000000..9f22ad67167d --- /dev/null +++ b/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/tupleReturn.mustache @@ -0,0 +1,8 @@ +var (statusCode, result) = await _impl.{{operationId}}({{#allParams}}{{paramName}}{{#hasMore}}, {{/hasMore}}{{/allParams}}); + + //TODO: Use spec to validate that the resultType matches the status code and that the status code was declared + + return new ObjectResult(result) + { + StatusCode = (int)statusCode + }; diff --git a/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/validateModel.mustache b/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/validateModel.mustache new file mode 100644 index 000000000000..e11aaa5d2708 --- /dev/null +++ b/modules/openapi-generator/src/main/resources/aspnetcorelib/2.1/validateModel.mustache @@ -0,0 +1,61 @@ +using System.ComponentModel.DataAnnotations; +using System.Reflection; +using Microsoft.AspNetCore.Mvc; +using Microsoft.AspNetCore.Mvc.Controllers; +using Microsoft.AspNetCore.Mvc.Filters; +using Microsoft.AspNetCore.Mvc.ModelBinding; + +namespace {{packageName}}.Attributes +{ + /// + /// Model state validation attribute + /// + public class ValidateModelStateAttribute : ActionFilterAttribute + { + /// + /// Called before the action method is invoked + /// + /// + public override void OnActionExecuting(ActionExecutingContext context) + { + // Per https://blog.markvincze.com/how-to-validate-action-parameters-with-dataannotation-attributes/ + var descriptor = context.ActionDescriptor as ControllerActionDescriptor; + if (descriptor != null) + { + foreach (var parameter in descriptor.MethodInfo.GetParameters()) + { + object args = null; + if (context.ActionArguments.ContainsKey(parameter.Name)) + { + args = context.ActionArguments[parameter.Name]; + } + + ValidateAttributes(parameter, args, context.ModelState); + } + } + + if (!context.ModelState.IsValid) + { + context.Result = new BadRequestObjectResult(context.ModelState); + } + } + + private void ValidateAttributes(ParameterInfo parameter, object args, ModelStateDictionary modelState) + { + foreach (var attributeData in parameter.CustomAttributes) + { + var attributeInstance = parameter.GetCustomAttribute(attributeData.AttributeType); + + var validationAttribute = attributeInstance as ValidationAttribute; + if (validationAttribute != null) + { + var isValid = validationAttribute.IsValid(args); + if (!isValid) + { + modelState.AddModelError(parameter.Name, validationAttribute.FormatErrorMessage(parameter.Name)); + } + } + } + } + } +}