Merge remote-tracking branch 'origin/master' into 6.0.x

This commit is contained in:
William Cheng 2021-05-13 18:44:58 +08:00
commit 5e29d61639
692 changed files with 28026 additions and 3166 deletions

View File

@ -26,6 +26,7 @@ jobs:
- name: Set up JDK ${{ matrix.java }}
uses: actions/setup-java@v2
with:
distribution: 'adopt'
java-version: ${{ matrix.java }}
- uses: actions/cache@v2.1.5

View File

@ -16,6 +16,7 @@ jobs:
- name: Set up JDK 11
uses: actions/setup-java@v2
with:
distribution: 'adopt'
java-version: 11
- name: Compile with Maven
run: mvn -B -q clean install jacoco:report

View File

@ -1,6 +1,5 @@
kind: pipeline
name: default
steps:
# test Java 11 HTTP client
- name: java11-test
@ -53,4 +52,12 @@ steps:
- name: haskell-client-test
image: haskell:8.6.5
commands:
- (cd samples/client/petstore/haskell-http-client/ && stack --install-ghc --no-haddock-deps haddock --fast && stack test --fast)
- (cd samples/client/petstore/haskell-http-client/ && stack --allow-different-user --install-ghc --no-haddock-deps haddock --fast && stack --allow-different-user test --fast)
# test erlang client and server
- name: erlang
image: erlang:alpine
commands:
- (cd samples/client/petstore/erlang-client && rebar3 compile)
- (cd samples/client/petstore/erlang-proper && rebar3 compile)
# comment out as the tests pass locally but not in the CI
#- (cd samples/server/petstore/erlang-server && rebar3 compile)

View File

@ -48,6 +48,7 @@ Code change should conform to the programming style guide of the respective lang
- C#: https://msdn.microsoft.com/en-us/library/vstudio/ff926074.aspx
- C++: https://google.github.io/styleguide/cppguide.html
- C++ (Tizen): https://wiki.tizen.org/Native_Platform_Coding_Idiom_and_Style_Guide#C.2B.2B_Coding_Style
- C++ (Unreal Engine 4): https://docs.unrealengine.com/en-US/ProductionPipelines/DevelopmentSetup/CodingStandard/index.html
- Clojure: https://github.com/bbatsov/clojure-style-guide
- Crystal: https://crystal-lang.org/reference/conventions/coding_style.html
- Dart: https://www.dartlang.org/guides/language/effective-dart/style

View File

@ -9,27 +9,17 @@
<div align="center">
[Master](https://github.com/OpenAPITools/openapi-generator/tree/master) (`5.1.1`):
[Master](https://github.com/OpenAPITools/openapi-generator/tree/master) (`5.2.0`):
[![Build Status](https://img.shields.io/travis/OpenAPITools/openapi-generator/master.svg?label=Integration%20Test)](https://travis-ci.org/OpenAPITools/openapi-generator)
[![Integration Test2](https://circleci.com/gh/OpenAPITools/openapi-generator.svg?style=shield)](https://circleci.com/gh/OpenAPITools/openapi-generator)
[![Run Status](https://api.shippable.com/projects/5af6bf74e790f4070084a115/badge?branch=master)](https://app.shippable.com/github/OpenAPITools/openapi-generator)
[![Windows Test](https://ci.appveyor.com/api/projects/status/github/openapitools/openapi-generator?branch=master&svg=true&passingText=Windows%20Test%20-%20OK&failingText=Windows%20Test%20-%20Fails)](https://ci.appveyor.com/project/WilliamCheng/openapi-generator-wh2wu)
[![JDK11 Build](https://cloud.drone.io/api/badges/OpenAPITools/openapi-generator/status.svg?ref=refs/heads/master)](https://cloud.drone.io/OpenAPITools/openapi-generator)
[![Bitrise](https://img.shields.io/bitrise/4a2b10a819d12b67/master?label=bitrise%3A%20Swift+4,5&token=859FMDR8QHwabCzwvZK6vQ)](https://app.bitrise.io/app/4a2b10a819d12b67)
[![GitHub Workflow Status (branch)](https://img.shields.io/github/workflow/status/openapitools/openapi-generator/Check%20Supported%20Java%20Versions/master?label=Check%20Supported%20Java%20Versions&logo=github&logoColor=green)](https://github.com/OpenAPITools/openapi-generator/actions?query=workflow%3A%22Check+Supported+Java+Versions%22)
[5.2.x](https://github.com/OpenAPITools/openapi-generator/tree/5.2.x) (`5.2.x`):
[![Build Status](https://img.shields.io/travis/OpenAPITools/openapi-generator/5.2.x.svg?label=Integration%20Test)](https://travis-ci.org/OpenAPITools/openapi-generator)
[![Integration Test2](https://circleci.com/gh/OpenAPITools/openapi-generator/tree/5.2.x.svg?style=shield)](https://circleci.com/gh/OpenAPITools/openapi-generator)
[![Run Status](https://api.shippable.com/projects/5af6bf74e790f4070084a115/badge?branch=5.2.x)](https://app.shippable.com/github/OpenAPITools/openapi-generator)
[![Windows Test](https://ci.appveyor.com/api/projects/status/github/openapitools/openapi-generator?branch=5.2.x&svg=true&passingText=Windows%20Test%20-%20OK&failingText=Windows%20Test%20-%20Fails)](https://ci.appveyor.com/project/WilliamCheng/openapi-generator-wh2wu)
[![JDK11 Build](https://cloud.drone.io/api/badges/OpenAPITools/openapi-generator/status.svg?ref=refs/heads/5.2.x)](https://cloud.drone.io/OpenAPITools/openapi-generator)
[![Bitrise](https://img.shields.io/bitrise/4a2b10a819d12b67/5.2.x?label=bitrise%3A%20Swift+4,5&token=859FMDR8QHwabCzwvZK6vQ)](https://app.bitrise.io/app/4a2b10a819d12b67)
[6.0.x](https://github.com/OpenAPITools/openapi-generator/tree/6.0.x) (`6.0.x`):
[![Build Status](https://img.shields.io/travis/OpenAPITools/openapi-generator/6.0.x.svg?label=Integration%20Test)](https://travis-ci.org/OpenAPITools/openapi-generator)
[![Integration Test2](https://circleci.com/gh/OpenAPITools/openapi-generator/tree/6.0.x.svg?style=shield)](https://circleci.com/gh/OpenAPITools/openapi-generator)
[![Run Status](https://api.shippable.com/projects/5af6bf74e790f4070084a115/badge?branch=6.0.x)](https://app.shippable.com/github/OpenAPITools/openapi-generator)
[![Windows Test](https://ci.appveyor.com/api/projects/status/github/openapitools/openapi-generator?branch=6.0.x&svg=true&passingText=Windows%20Test%20-%20OK&failingText=Windows%20Test%20-%20Fails)](https://ci.appveyor.com/project/WilliamCheng/openapi-generator-wh2wu)
[![JDK11 Build](https://cloud.drone.io/api/badges/OpenAPITools/openapi-generator/status.svg?ref=refs/heads/6.0.x)](https://cloud.drone.io/OpenAPITools/openapi-generator)
[![Bitrise](https://img.shields.io/bitrise/4a2b10a819d12b67/6.0.x?label=bitrise%3A%20Swift+4,5&token=859FMDR8QHwabCzwvZK6vQ)](https://app.bitrise.io/app/4a2b10a819d12b67)
@ -120,9 +110,8 @@ The OpenAPI Specification has undergone 3 revisions since initial creation in 20
| OpenAPI Generator Version | Release Date | Notes |
| --------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ------------------------------------------------- |
| 6.0.0 (upcoming major release) [SNAPSHOT](https://oss.sonatype.org/content/repositories/snapshots/org/openapitools/openapi-generator-cli/6.0.0-SNAPSHOT/) | Nov/Dec 2021 | Minor release with breaking changes (no fallback) |
| 5.2.0 (upcoming minor release) [SNAPSHOT](https://oss.sonatype.org/content/repositories/snapshots/org/openapitools/openapi-generator-cli/5.2.0-SNAPSHOT/) | May/Jun 2021 | Minor release with breaking changes (with fallback) |
| 5.1.1 (upcoming patch release) [SNAPSHOT](https://oss.sonatype.org/content/repositories/snapshots/org/openapitools/openapi-generator-cli/5.1.1-SNAPSHOT/) | Apr/May 2021 | Patch release (enhancements, bug fixes, etc) |
| [5.1.0](https://github.com/OpenAPITools/openapi-generator/releases/tag/v5.1.0) (latest stable release) | 20.03.2021 | Minor release with breaking changes (with fallback) |
| 5.2.0 (upcoming minor release) [SNAPSHOT](https://oss.sonatype.org/content/repositories/snapshots/org/openapitools/openapi-generator-cli/5.2.0-SNAPSHOT/) | Jun/Jul 2021 | Minor release with breaking changes (with fallback) |
| [5.1.1](https://github.com/OpenAPITools/openapi-generator/releases/tag/v5.1.1) (latest stable release) | 07.05.2021 | Patch release (enhancements, bug fixes, etc) |
| [4.3.1](https://github.com/OpenAPITools/openapi-generator/releases/tag/v4.3.1) | 06.05.2020 | Patch release (enhancements, bug fixes, etc) |
OpenAPI Spec compatibility: 1.0, 1.1, 1.2, 2.0, 3.0
@ -179,16 +168,16 @@ See the different versions of the [openapi-generator-cli](https://mvnrepository.
<!-- RELEASE_VERSION -->
If you're looking for the latest stable version, you can grab it directly from Maven.org (Java 8 runtime at a minimum):
JAR location: `https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/5.1.0/openapi-generator-cli-5.1.0.jar`
JAR location: `https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/5.1.1/openapi-generator-cli-5.1.1.jar`
For **Mac/Linux** users:
```sh
wget https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/5.1.0/openapi-generator-cli-5.1.0.jar -O openapi-generator-cli.jar
wget https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/5.1.1/openapi-generator-cli-5.1.1.jar -O openapi-generator-cli.jar
```
For **Windows** users, you will need to install [wget](http://gnuwin32.sourceforge.net/packages/wget.htm) or you can use Invoke-WebRequest in PowerShell (3.0+), e.g.
```
Invoke-WebRequest -OutFile openapi-generator-cli.jar https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/5.1.0/openapi-generator-cli-5.1.0.jar
Invoke-WebRequest -OutFile openapi-generator-cli.jar https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/5.1.1/openapi-generator-cli-5.1.1.jar
```
After downloading the JAR, run `java -jar openapi-generator-cli.jar help` to show the usage.
@ -413,7 +402,7 @@ openapi-generator-cli version
To use a specific version of "openapi-generator-cli"
```sh
openapi-generator-cli version-manager set 5.1.0
openapi-generator-cli version-manager set 5.1.1
```
Or install it as dev-dependency:
@ -437,7 +426,7 @@ java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar generat
(if you're on Windows, replace the last command with `java -jar modules\openapi-generator-cli\target\openapi-generator-cli.jar generate -i https://raw.githubusercontent.com/openapitools/openapi-generator/master/modules/openapi-generator/src/test/resources/3_0/petstore.yaml -g php -o c:\temp\php_api_client`)
<!-- RELEASE_VERSION -->
You can also download the JAR (latest release) directly from [maven.org](https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/5.1.0/openapi-generator-cli-5.1.0.jar)
You can also download the JAR (latest release) directly from [maven.org](https://repo1.maven.org/maven2/org/openapitools/openapi-generator-cli/5.1.1/openapi-generator-cli-5.1.1.jar)
<!-- /RELEASE_VERSION -->
To get a list of **general** options available, please run `java -jar modules/openapi-generator-cli/target/openapi-generator-cli.jar help generate`
@ -562,7 +551,7 @@ When code is generated from this project, it shall be considered **AS IS** and o
### [3.5 - IDE Integration](#table-of-contents)
Here is a list of community-conitributed IDE plug-ins that integrate with OpenAPI Generator:
Here is a list of community-contributed IDE plug-ins that integrate with OpenAPI Generator:
- Eclipse: [Codewind OpenAPI Tools for Eclipse](https://www.eclipse.org/codewind/open-api-tools-for-eclipse.html) by [IBM](https://www.ibm.com)
- IntelliJ IDEA: [OpenAPI Generator](https://plugins.jetbrains.com/plugin/8433-openapi-generator) by [Jim Schubert](https://jimschubert.us/#/)
@ -666,6 +655,7 @@ Here are some companies/projects (alphabetical order) using OpenAPI Generator in
- [Twitter](https://twitter.com)
- [unblu inc.](https://www.unblu.com/)
- [Veamly](https://www.veamly.com/)
- [VMWare](https://www.vmware.com/)
- [wbt-solutions](https://www.wbt-solutions.de/)
- [Woleet](https://www.woleet.io/)
- [WSO2](https://wso2.com/)
@ -821,8 +811,8 @@ Here are some companies/projects (alphabetical order) using OpenAPI Generator in
- 2021-03-28 - [Trying out NestJS part 4: Generate Typescript clients from OpenAPI documents](https://dev.to/arnaudcortisse/trying-out-nestjs-part-4-generate-typescript-clients-from-openapi-documents-28mk) by [Arnaud Cortisse](https://dev.to/arnaudcortisse)
- 2021-03-31 - [Open API Server Implementation Using OpenAPI Generator](https://www.baeldung.com/java-openapi-generator-server) at [Baeldung](https://www.baeldung.com/)
- 2021-03-31 - [使用OpenAPI Generator實現Open API Server](https://www.1ju.org/article/java-openapi-generator-server) at [億聚網](https://www.1ju.org/)
- 2022-04-19 - [Introducing Twilios OpenAPI Specification Beta](https://www.twilio.com/blog/introducing-twilio-open-api-specification-beta) by [GARETH PAUL JONES](https://www.twilio.com/blog/author/gpj) at [Twilio Blog](https://www.twilio.com/blog)
- 2022-04-22 - [Leveraging OpenApi strengths in a Micro-Service environment](https://medium.com/unibuddy-technology-blog/leveraging-openapi-strengths-in-a-micro-service-environment-3d7f9e7c26ff) by Nicolas Jellab at [Unibuddy Technology Blog](https://medium.com/unibuddy-technology-blog)
- 2021-04-19 - [Introducing Twilios OpenAPI Specification Beta](https://www.twilio.com/blog/introducing-twilio-open-api-specification-beta) by [GARETH PAUL JONES](https://www.twilio.com/blog/author/gpj) at [Twilio Blog](https://www.twilio.com/blog)
- 2021-04-22 - [Leveraging OpenApi strengths in a Micro-Service environment](https://medium.com/unibuddy-technology-blog/leveraging-openapi-strengths-in-a-micro-service-environment-3d7f9e7c26ff) by Nicolas Jellab at [Unibuddy Technology Blog](https://medium.com/unibuddy-technology-blog)
## [6 - About Us](#table-of-contents)

View File

@ -5,3 +5,4 @@ templateDir: modules/openapi-generator/src/main/resources/kotlin-client
additionalProperties:
requestDateConverter: toString
artifactId: kotlin-petstore-json-request-string
parcelizeModels: true

View File

@ -1,6 +1,6 @@
generatorName: ktorm-schema
outputDir: samples/schema/petstore/ktorm
inputSpec: modules/openapi-generator/src/test/resources/2_0/petstore.yaml
inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore.yaml
templateDir: modules/openapi-generator/src/main/resources/ktorm-schema
additionalProperties:
hideGenerationTimestamp: true

View File

@ -1,6 +1,6 @@
generatorName: nim
outputDir: samples/client/petstore/nim
inputSpec: modules/openapi-generator/src/test/resources/2_0/petstore.yaml
inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore.yaml
templateDir: modules/openapi-generator/src/main/resources/nim-client
additionalProperties:
packageName: petstore

View File

@ -52,6 +52,7 @@ Code change should conform to the programming style guide of the respective lang
- C#: https://msdn.microsoft.com/en-us/library/vstudio/ff926074.aspx
- C++: https://google.github.io/styleguide/cppguide.html
- C++ (Tizen): https://wiki.tizen.org/Native_Platform_Coding_Idiom_and_Style_Guide#C.2B.2B_Coding_Style
- C++ (Unreal Engine 4): https://docs.unrealengine.com/en-US/ProductionPipelines/DevelopmentSetup/CodingStandard/index.html
- Clojure: https://github.com/bbatsov/clojure-style-guide
- Crystal: https://crystal-lang.org/reference/conventions/coding_style.html
- Dart: https://www.dartlang.org/guides/language/effective-dart/style

View File

@ -106,6 +106,7 @@ The following generators are available:
* [jaxrs-resteasy-eap](generators/jaxrs-resteasy-eap.md)
* [jaxrs-spec](generators/jaxrs-spec.md)
* [kotlin-server](generators/kotlin-server.md)
* [kotlin-server-deprecated (deprecated)](generators/kotlin-server-deprecated.md)
* [kotlin-spring](generators/kotlin-spring.md)
* [kotlin-vertx (beta)](generators/kotlin-vertx.md)
* [nodejs-express-server (beta)](generators/nodejs-express-server.md)

View File

@ -91,6 +91,7 @@ The following generators are available:
* [jaxrs-resteasy-eap](jaxrs-resteasy-eap.md)
* [jaxrs-spec](jaxrs-spec.md)
* [kotlin-server](kotlin-server.md)
* [kotlin-server-deprecated](kotlin-server-deprecated.md)
* [kotlin-spring](kotlin-spring.md)
* [kotlin-vertx (beta)](kotlin-vertx.md)
* [nodejs-express-server (beta)](nodejs-express-server.md)

View File

@ -7,9 +7,9 @@ These options may be applied as additional-properties (cli) or configOptions (pl
| Option | Description | Values | Default |
| ------ | ----------- | ------ | ------- |
|aspnetCoreVersion|ASP.NET Core version: 5.0, 3.1, 3.0, 2.2, 2.1, 2.0 (deprecated)| |3.1|
|buildTarget|Target to build an application or library| |program|
|classModifier|Class Modifier for controller classes: Empty string or abstract.| ||
|aspnetCoreVersion|ASP.NET Core version: 5.0, 3.1, 3.0, 2.2, 2.1, 2.0 (deprecated)|<dl><dt>**2.0**</dt><dd>ASP.NET Core 2.0</dd><dt>**2.1**</dt><dd>ASP.NET Core 2.1</dd><dt>**2.2**</dt><dd>ASP.NET Core 2.2</dd><dt>**3.0**</dt><dd>ASP.NET Core 3.0</dd><dt>**3.1**</dt><dd>ASP.NET Core 3.1</dd><dt>**5.0**</dt><dd>ASP.NET Core 5.0</dd></dl>|3.1|
|buildTarget|Target to build an application or library|<dl><dt>**program**</dt><dd>Generate code for a standalone server</dd><dt>**library**</dt><dd>Generate code for a server abstract class library</dd></dl>|program|
|classModifier|Class Modifier for controller classes: Empty string or abstract.|<dl><dt>****</dt><dd>Keep class default with no modifier</dd><dt>**abstract**</dt><dd>Make class abstract</dd></dl>||
|compatibilityVersion|ASP.Net Core CompatibilityVersion| |Version_2_2|
|enumNameSuffix|Suffix that will be appended to all enum names.| |Enum|
|enumValueSuffix|Suffix that will be appended to all enum values.| |Enum|
@ -17,10 +17,10 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|isLibrary|Is the build a library| |false|
|licenseName|The name of the license| |NoLicense|
|licenseUrl|The URL of the license| |http://localhost|
|modelClassModifier|Model Class Modifier can be nothing or partial| |partial|
|modelClassModifier|Model Class Modifier can be nothing or partial|<dl><dt>****</dt><dd>Keep model class default with no modifier</dd><dt>**partial**</dt><dd>Make model class partial</dd></dl>|partial|
|newtonsoftVersion|Version for Microsoft.AspNetCore.Mvc.NewtonsoftJson for ASP.NET Core 3.0+| |3.0.0|
|operationIsAsync|Set methods to async or sync (default).| |false|
|operationModifier|Operation Modifier can be virtual or abstract| |virtual|
|operationModifier|Operation Modifier can be virtual or abstract|<dl><dt>**virtual**</dt><dd>Keep method virtual</dd><dt>**abstract**</dt><dd>Make method abstract</dd></dl>|virtual|
|operationResultTask|Set methods result to Task&lt;&gt;.| |false|
|packageAuthors|Specifies Authors property in the .NET Core project file.| |OpenAPI|
|packageCopyright|Specifies an AssemblyCopyright for the .NET Framework global assembly attributes stored in the AssemblyInfo file.| |No Copyright|
@ -32,7 +32,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|returnICollection|Return ICollection&lt;T&gt; instead of the concrete type.| |false|
|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true|
|sourceFolder|source folder for generated code| |src|
|swashbuckleVersion|Swashbuckle version: 3.0.0, 4.0.0, 5.0.0| |3.0.0|
|swashbuckleVersion|Swashbuckle version: 3.0.0, 4.0.0, 5.0.0|<dl><dt>**3.0.0**</dt><dd>Swashbuckle 3.0.0</dd><dt>**4.0.0**</dt><dd>Swashbuckle 4.0.0</dd><dt>**5.0.0**</dt><dd>Swashbuckle 5.0.0</dd></dl>|3.0.0|
|useCollection|Deserialize array types to Collection&lt;T&gt; instead of List&lt;T&gt;.| |false|
|useDateTimeOffset|Use DateTimeOffset to model date-time properties| |false|
|useDefaultRouting|Use default routing for the ASP.NET Core version.| |true|

View File

@ -0,0 +1,214 @@
---
title: Config Options for kotlin-server-deprecated
sidebar_label: kotlin-server-deprecated
---
These options may be applied as additional-properties (cli) or configOptions (plugins). Refer to [configuration docs](https://openapi-generator.tech/docs/configuration) for more details.
| Option | Description | Values | Default |
| ------ | ----------- | ------ | ------- |
|apiSuffix|suffix for api classes| |Api|
|artifactId|Generated artifact id (name of jar).| |kotlin-server-deprecated|
|artifactVersion|Generated artifact's package version.| |1.0.0|
|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', and 'original'| |camelCase|
|featureAutoHead|Automatically provide responses to HEAD requests for existing routes that have the GET verb defined.| |true|
|featureCORS|Ktor by default provides an interceptor for implementing proper support for Cross-Origin Resource Sharing (CORS). See enable-cors.org.| |false|
|featureCompression|Adds ability to compress outgoing content using gzip, deflate or custom encoder and thus reduce size of the response.| |true|
|featureConditionalHeaders|Avoid sending content if client already has same content, by checking ETag or LastModified properties.| |false|
|featureHSTS|Avoid sending content if client already has same content, by checking ETag or LastModified properties.| |true|
|groupId|Generated artifact package's organization (i.e. maven groupId).| |org.openapitools|
|library|library template (sub-template)|<dl><dt>**ktor**</dt><dd>ktor framework</dd></dl>|ktor|
|modelMutable|Create mutable models| |false|
|packageName|Generated artifact package name.| |org.openapitools.server|
|parcelizeModels|toggle &quot;@Parcelize&quot; for generated models| |null|
|serializableModel|boolean - toggle &quot;implements Serializable&quot; for generated models| |null|
|serializationLibrary|What serialization library to use: 'moshi' (default), or 'gson' or 'jackson'| |moshi|
|sortModelPropertiesByRequiredFlag|Sort model properties to place required parameters before optional parameters.| |null|
|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |null|
|sourceFolder|source folder for generated code| |src/main/kotlin|
## IMPORT MAPPING
| Type/Alias | Imports |
| ---------- | ------- |
|BigDecimal|java.math.BigDecimal|
|Date|java.time.LocalDate|
|DateTime|java.time.OffsetDateTime|
|File|java.io.File|
|LocalDate|java.time.LocalDate|
|LocalDateTime|java.time.LocalDateTime|
|LocalTime|java.time.LocalTime|
|Timestamp|java.sql.Timestamp|
|URI|java.net.URI|
|UUID|java.util.UUID|
## INSTANTIATION TYPES
| Type/Alias | Instantiated By |
| ---------- | --------------- |
|array|kotlin.collections.ArrayList|
|list|kotlin.collections.ArrayList|
|map|kotlin.collections.HashMap|
## LANGUAGE PRIMITIVES
<ul class="column-ul">
<li>kotlin.Array</li>
<li>kotlin.Boolean</li>
<li>kotlin.Byte</li>
<li>kotlin.ByteArray</li>
<li>kotlin.Char</li>
<li>kotlin.Double</li>
<li>kotlin.Float</li>
<li>kotlin.Int</li>
<li>kotlin.Long</li>
<li>kotlin.Short</li>
<li>kotlin.String</li>
<li>kotlin.collections.List</li>
<li>kotlin.collections.Map</li>
<li>kotlin.collections.Set</li>
</ul>
## RESERVED WORDS
<ul class="column-ul">
<li>as</li>
<li>break</li>
<li>class</li>
<li>continue</li>
<li>do</li>
<li>else</li>
<li>false</li>
<li>for</li>
<li>fun</li>
<li>if</li>
<li>in</li>
<li>interface</li>
<li>is</li>
<li>null</li>
<li>object</li>
<li>package</li>
<li>return</li>
<li>super</li>
<li>this</li>
<li>throw</li>
<li>true</li>
<li>try</li>
<li>typealias</li>
<li>typeof</li>
<li>val</li>
<li>var</li>
<li>when</li>
<li>while</li>
</ul>
## FEATURE SET
### Client Modification Feature
| Name | Supported | Defined By |
| ---- | --------- | ---------- |
|BasePath|✗|ToolingExtension
|Authorizations|✗|ToolingExtension
|UserAgent|✗|ToolingExtension
|MockServer|✗|ToolingExtension
### Data Type Feature
| Name | Supported | Defined By |
| ---- | --------- | ---------- |
|Custom|✗|OAS2,OAS3
|Int32|✓|OAS2,OAS3
|Int64|✓|OAS2,OAS3
|Float|✓|OAS2,OAS3
|Double|✓|OAS2,OAS3
|Decimal|✓|ToolingExtension
|String|✓|OAS2,OAS3
|Byte|✓|OAS2,OAS3
|Binary|✓|OAS2,OAS3
|Boolean|✓|OAS2,OAS3
|Date|✓|OAS2,OAS3
|DateTime|✓|OAS2,OAS3
|Password|✓|OAS2,OAS3
|File|✓|OAS2
|Array|✓|OAS2,OAS3
|Maps|✓|ToolingExtension
|CollectionFormat|✓|OAS2
|CollectionFormatMulti|✓|OAS2
|Enum|✓|OAS2,OAS3
|ArrayOfEnum|✓|ToolingExtension
|ArrayOfModel|✓|ToolingExtension
|ArrayOfCollectionOfPrimitives|✓|ToolingExtension
|ArrayOfCollectionOfModel|✓|ToolingExtension
|ArrayOfCollectionOfEnum|✓|ToolingExtension
|MapOfEnum|✓|ToolingExtension
|MapOfModel|✓|ToolingExtension
|MapOfCollectionOfPrimitives|✓|ToolingExtension
|MapOfCollectionOfModel|✓|ToolingExtension
|MapOfCollectionOfEnum|✓|ToolingExtension
### Documentation Feature
| Name | Supported | Defined By |
| ---- | --------- | ---------- |
|Readme|✓|ToolingExtension
|Model|✓|ToolingExtension
|Api|✓|ToolingExtension
### Global Feature
| Name | Supported | Defined By |
| ---- | --------- | ---------- |
|Host|✓|OAS2,OAS3
|BasePath|✓|OAS2,OAS3
|Info|✓|OAS2,OAS3
|Schemes|✗|OAS2,OAS3
|PartialSchemes|✓|OAS2,OAS3
|Consumes|✓|OAS2
|Produces|✓|OAS2
|ExternalDocumentation|✓|OAS2,OAS3
|Examples|✓|OAS2,OAS3
|XMLStructureDefinitions|✗|OAS2,OAS3
|MultiServer|✗|OAS3
|ParameterizedServer|✗|OAS3
|ParameterStyling|✗|OAS3
|Callbacks|✗|OAS3
|LinkObjects|✗|OAS3
### Parameter Feature
| Name | Supported | Defined By |
| ---- | --------- | ---------- |
|Path|✓|OAS2,OAS3
|Query|✓|OAS2,OAS3
|Header|✓|OAS2,OAS3
|Body|✓|OAS2
|FormUnencoded|✓|OAS2
|FormMultipart|✓|OAS2
|Cookie|✗|OAS3
### Schema Support Feature
| Name | Supported | Defined By |
| ---- | --------- | ---------- |
|Simple|✓|OAS2,OAS3
|Composite|✓|OAS2,OAS3
|Polymorphism|✗|OAS2,OAS3
|Union|✗|OAS3
### Security Feature
| Name | Supported | Defined By |
| ---- | --------- | ---------- |
|BasicAuth|✓|OAS2,OAS3
|ApiKey|✓|OAS2,OAS3
|OpenIDConnect|✗|OAS3
|BearerToken|✗|OAS3
|OAuth2_Implicit|✓|OAS2,OAS3
|OAuth2_Password|✗|OAS2,OAS3
|OAuth2_ClientCredentials|✗|OAS2,OAS3
|OAuth2_AuthorizationCode|✗|OAS2,OAS3
### Wire Format Feature
| Name | Supported | Defined By |
| ---- | --------- | ---------- |
|JSON|✓|OAS2,OAS3
|XML|✓|OAS2,OAS3
|PROTOBUF|✗|ToolingExtension
|Custom|✗|OAS2,OAS3

View File

@ -16,6 +16,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|featureCompression|Adds ability to compress outgoing content using gzip, deflate or custom encoder and thus reduce size of the response.| |true|
|featureConditionalHeaders|Avoid sending content if client already has same content, by checking ETag or LastModified properties.| |false|
|featureHSTS|Avoid sending content if client already has same content, by checking ETag or LastModified properties.| |true|
|featureLocations|Generates routes in a typed way, for both: constructing URLs and reading the parameters.| |true|
|groupId|Generated artifact package's organization (i.e. maven groupId).| |org.openapitools|
|library|library template (sub-template)|<dl><dt>**ktor**</dt><dd>ktor framework</dd></dl>|ktor|
|modelMutable|Create mutable models| |false|

View File

@ -16,6 +16,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|groupId|Generated artifact package's organization (i.e. maven groupId).| |org.openapitools|
|library|Library template (sub-template) to use|<dl><dt>**jvm-okhttp4**</dt><dd>[DEFAULT] Platform: Java Virtual Machine. HTTP client: OkHttp 4.2.0 (Android 5.0+ and Java 8+). JSON processing: Moshi 1.8.0.</dd><dt>**jvm-okhttp3**</dt><dd>Platform: Java Virtual Machine. HTTP client: OkHttp 3.12.4 (Android 2.3+ and Java 7+). JSON processing: Moshi 1.8.0.</dd><dt>**jvm-retrofit2**</dt><dd>Platform: Java Virtual Machine. HTTP client: Retrofit 2.6.2.</dd><dt>**multiplatform**</dt><dd>Platform: Kotlin multiplatform. HTTP client: Ktor 1.2.4. JSON processing: Kotlinx Serialization: 0.12.0.</dd></dl>|jvm-okhttp4|
|modelMutable|Create mutable models| |false|
|moshiCodeGen|Whether to enable codegen with the Moshi library. Refer to the [official Moshi doc](https://github.com/square/moshi#codegen) for more info.| |false|
|packageName|Generated artifact package name.| |org.openapitools.client|
|parcelizeModels|toggle &quot;@Parcelize&quot; for generated models| |null|
|requestDateConverter|JVM-Option. Defines in how to handle date-time objects that are used for a request (as query or parameter)|<dl><dt>**toJson**</dt><dd>[DEFAULT] Date formater option using a json converter.</dd><dt>**toString**</dt><dd>Use the 'toString'-method of the date-time object to retrieve the related string representation.</dd></dl>|toJson|

View File

@ -13,8 +13,8 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|dateLibrary|Option. Date library to use|<dl><dt>**joda**</dt><dd>Joda (for legacy app)</dd><dt>**java8**</dt><dd>Java 8 native JSR310 (prefered for JDK 1.8+)</dd></dl>|java8|
|disallowAdditionalPropertiesIfNotPresent|If false, the 'additionalProperties' implementation (set to true by default) is compliant with the OAS and JSON schema specifications. If true (default), keep the old (incorrect) behaviour that 'additionalProperties' is set to false by default.|<dl><dt>**false**</dt><dd>The 'additionalProperties' implementation is compliant with the OAS and JSON schema specifications.</dd><dt>**true**</dt><dd>Keep the old (incorrect) behaviour that 'additionalProperties' is set to false by default.</dd></dl>|true|
|ensureUniqueParams|Whether to ensure parameter names are unique in an operation (rename parameters that are not).| |true|
|jodaTimeVersion|The version of joda-time library| |2.10.6|
|json4sVersion|The version of json4s library| |3.6.8|
|jodaTimeVersion|The version of joda-time library| |2.10.10|
|json4sVersion|The version of json4s library| |3.6.11|
|jsonLibrary|Json library to use. Possible values are: json4s and circe.| |json4s|
|legacyDiscriminatorBehavior|Set to false for generators with better support for discriminators. (Python, Java, Go, PowerShell, C#have this enabled by default).|<dl><dt>**true**</dt><dd>The mapping in the discriminator includes descendent schemas that allOf inherit from self and the discriminator mapping schemas in the OAS document.</dd><dt>**false**</dt><dd>The mapping in the discriminator includes any descendent schemas that allOf inherit from self, any oneOf schemas, any anyOf schemas, any x-discriminator-values, and the discriminator mapping schemas in the OAS document AND Codegen validates that oneOf and anyOf schemas contain the required discriminator and throws an error if the discriminator is missing.</dd></dl>|true|
|mainPackage|Top-level package name, which defines 'apiPackage', 'modelPackage', 'invokerPackage'| |org.openapitools.client|
@ -25,7 +25,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|sortModelPropertiesByRequiredFlag|Sort model properties to place required parameters before optional parameters.| |true|
|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true|
|sourceFolder|source folder for generated code| |null|
|sttpClientVersion|The version of sttp client| |2.2.0|
|sttpClientVersion|The version of sttp client| |2.2.9|
## IMPORT MAPPING

View File

@ -199,14 +199,14 @@ public class AspNetCoreServerCodegen extends AbstractCSharpCodegen {
aspnetCoreVersion.addEnum("5.0", "ASP.NET Core 5.0");
aspnetCoreVersion.setDefault("3.1");
aspnetCoreVersion.setOptValue(aspnetCoreVersion.getDefault());
addOption(aspnetCoreVersion.getOpt(), aspnetCoreVersion.getDescription(), aspnetCoreVersion.getOptValue());
cliOptions.add(aspnetCoreVersion);
swashbuckleVersion.addEnum("3.0.0", "Swashbuckle 3.0.0");
swashbuckleVersion.addEnum("4.0.0", "Swashbuckle 4.0.0");
swashbuckleVersion.addEnum("5.0.0", "Swashbuckle 5.0.0");
swashbuckleVersion.setDefault("3.0.0");
swashbuckleVersion.setOptValue(swashbuckleVersion.getDefault());
addOption(swashbuckleVersion.getOpt(), swashbuckleVersion.getDescription(), swashbuckleVersion.getOptValue());
cliOptions.add(swashbuckleVersion);
// CLI Switches
addSwitch(CodegenConstants.SORT_PARAMS_BY_REQUIRED_FLAG,
@ -261,19 +261,19 @@ public class AspNetCoreServerCodegen extends AbstractCSharpCodegen {
classModifier.addEnum("abstract", "Make class abstract");
classModifier.setDefault("");
classModifier.setOptValue(classModifier.getDefault());
addOption(classModifier.getOpt(), classModifier.getDescription(), classModifier.getOptValue());
cliOptions.add(classModifier);
operationModifier.addEnum("virtual", "Keep method virtual");
operationModifier.addEnum("abstract", "Make method abstract");
operationModifier.setDefault("virtual");
operationModifier.setOptValue(operationModifier.getDefault());
addOption(operationModifier.getOpt(), operationModifier.getDescription(), operationModifier.getOptValue());
cliOptions.add(operationModifier);
buildTarget.addEnum("program", "Generate code for a standalone server");
buildTarget.addEnum("library", "Generate code for a server abstract class library");
buildTarget.setDefault("program");
buildTarget.setOptValue(buildTarget.getDefault());
addOption(buildTarget.getOpt(), buildTarget.getDescription(), buildTarget.getOptValue());
cliOptions.add(buildTarget);
addSwitch(GENERATE_BODY,
"Generates method body.",
@ -292,7 +292,7 @@ public class AspNetCoreServerCodegen extends AbstractCSharpCodegen {
modelClassModifier.addEnum("partial", "Make model class partial");
modelClassModifier.setDefault("partial");
modelClassModifier.setOptValue(modelClassModifier.getDefault());
addOption(modelClassModifier.getOpt(), modelClassModifier.getDescription(), modelClassModifier.getOptValue());
cliOptions.add(modelClassModifier);
}
@Override
@ -592,6 +592,7 @@ public class AspNetCoreServerCodegen extends AbstractCSharpCodegen {
private void setBuildTarget() {
setCliOption(buildTarget);
if ("library".equals(buildTarget.getOptValue())) {
LOGGER.warn("buildTarget is {} so changing default isLibrary to true", buildTarget.getOptValue());
isLibrary = true;
projectSdk = SDK_LIB;
additionalProperties.put(CLASS_MODIFIER, "abstract");
@ -636,7 +637,7 @@ public class AspNetCoreServerCodegen extends AbstractCSharpCodegen {
private void setUseSwashbuckle() {
if (isLibrary) {
LOGGER.warn("buildTarget is " + buildTarget.getOptValue() + " so changing default isLibrary to false ");
LOGGER.warn("isLibrary is true so changing default useSwashbuckle to false");
useSwashbuckle = false;
} else {
useSwashbuckle = true;

View File

@ -128,6 +128,7 @@ public class CppPistacheServerCodegen extends AbstractCppCodegen {
typeMapping.put("boolean", "bool");
typeMapping.put("array", "std::vector");
typeMapping.put("map", "std::map");
typeMapping.put("set", "std::vector");
typeMapping.put("file", "std::string");
typeMapping.put("object", "Object");
typeMapping.put("binary", "std::string");

View File

@ -61,6 +61,8 @@ public class KotlinClientCodegen extends AbstractKotlinCodegen {
public static final String REQUEST_DATE_CONVERTER = "requestDateConverter";
public static final String COLLECTION_TYPE = "collectionType";
public static final String MOSHI_CODE_GEN = "moshiCodeGen";
protected static final String VENDOR_EXTENSION_BASE_NAME_LITERAL = "x-base-name-literal";
protected String dateLibrary = DateLibrary.JAVA8.value;
@ -204,6 +206,8 @@ public class KotlinClientCodegen extends AbstractKotlinCodegen {
cliOptions.add(CliOption.newBoolean(USE_RX_JAVA2, "Whether to use the RxJava2 adapter with the retrofit2 library."));
cliOptions.add(CliOption.newBoolean(USE_RX_JAVA3, "Whether to use the RxJava3 adapter with the retrofit2 library."));
cliOptions.add(CliOption.newBoolean(USE_COROUTINES, "Whether to use the Coroutines adapter with the retrofit2 library."));
cliOptions.add(CliOption.newBoolean(MOSHI_CODE_GEN, "Whether to enable codegen with the Moshi library. Refer to the [official Moshi doc](https://github.com/square/moshi#codegen) for more info."));
}
public CodegenType getTag() {

View File

@ -42,15 +42,17 @@ public class KotlinServerCodegen extends AbstractKotlinCodegen {
private Boolean hstsFeatureEnabled = true;
private Boolean corsFeatureEnabled = false;
private Boolean compressionFeatureEnabled = true;
private Boolean locationsFeatureEnabled = true;
// This is here to potentially warn the user when an option is not supoprted by the target framework.
// This is here to potentially warn the user when an option is not supported by the target framework.
private Map<String, List<String>> optionsSupportedPerFramework = new ImmutableMap.Builder<String, List<String>>()
.put(Constants.KTOR, Arrays.asList(
Constants.AUTOMATIC_HEAD_REQUESTS,
Constants.CONDITIONAL_HEADERS,
Constants.HSTS,
Constants.CORS,
Constants.COMPRESSION
Constants.COMPRESSION,
Constants.LOCATIONS
))
.build();
@ -85,6 +87,8 @@ public class KotlinServerCodegen extends AbstractKotlinCodegen {
artifactId = "kotlin-server";
packageName = "org.openapitools.server";
typeMapping.put("array", "kotlin.collections.List");
// cliOptions default redefinition need to be updated
updateOption(CodegenConstants.ARTIFACT_ID, this.artifactId);
updateOption(CodegenConstants.PACKAGE_NAME, this.packageName);
@ -110,6 +114,7 @@ public class KotlinServerCodegen extends AbstractKotlinCodegen {
addSwitch(Constants.HSTS, Constants.HSTS_DESC, getHstsFeatureEnabled());
addSwitch(Constants.CORS, Constants.CORS_DESC, getCorsFeatureEnabled());
addSwitch(Constants.COMPRESSION, Constants.COMPRESSION_DESC, getCompressionFeatureEnabled());
addSwitch(Constants.LOCATIONS, Constants.LOCATIONS_DESC, getLocationsFeatureEnabled());
}
public Boolean getAutoHeadFeatureEnabled() {
@ -156,6 +161,14 @@ public class KotlinServerCodegen extends AbstractKotlinCodegen {
this.hstsFeatureEnabled = hstsFeatureEnabled;
}
public Boolean getLocationsFeatureEnabled() {
return locationsFeatureEnabled;
}
public void setLocationsFeatureEnabled(Boolean locationsFeatureEnabled) {
this.locationsFeatureEnabled = locationsFeatureEnabled;
}
public String getName() {
return "kotlin-server";
}
@ -209,6 +222,12 @@ public class KotlinServerCodegen extends AbstractKotlinCodegen {
additionalProperties.put(Constants.COMPRESSION, getCompressionFeatureEnabled());
}
if (additionalProperties.containsKey(Constants.LOCATIONS)) {
setLocationsFeatureEnabled(convertPropertyToBooleanAndWriteBack(Constants.LOCATIONS));
} else {
additionalProperties.put(Constants.LOCATIONS, getLocationsFeatureEnabled());
}
boolean generateApis = additionalProperties.containsKey(CodegenConstants.GENERATE_APIS) && (Boolean) additionalProperties.get(CodegenConstants.GENERATE_APIS);
String packageFolder = (sourceFolder + File.separator + packageName).replace(".", File.separator);
String resourcesFolder = "src/main/resources"; // not sure this can be user configurable.
@ -223,7 +242,7 @@ public class KotlinServerCodegen extends AbstractKotlinCodegen {
supportingFiles.add(new SupportingFile("AppMain.kt.mustache", packageFolder, "AppMain.kt"));
supportingFiles.add(new SupportingFile("Configuration.kt.mustache", packageFolder, "Configuration.kt"));
if (generateApis) {
if (generateApis && locationsFeatureEnabled) {
supportingFiles.add(new SupportingFile("Paths.kt.mustache", packageFolder, "Paths.kt"));
}
@ -247,6 +266,8 @@ public class KotlinServerCodegen extends AbstractKotlinCodegen {
public final static String CORS_DESC = "Ktor by default provides an interceptor for implementing proper support for Cross-Origin Resource Sharing (CORS). See enable-cors.org.";
public final static String COMPRESSION = "featureCompression";
public final static String COMPRESSION_DESC = "Adds ability to compress outgoing content using gzip, deflate or custom encoder and thus reduce size of the response.";
public final static String LOCATIONS = "featureLocations";
public final static String LOCATIONS_DESC = "Generates routes in a typed way, for both: constructing URLs and reading the parameters.";
}
@Override

View File

@ -0,0 +1,270 @@
/*
* 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
*
* https://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.google.common.collect.ImmutableMap;
import org.apache.commons.lang3.StringUtils;
import org.openapitools.codegen.CliOption;
import org.openapitools.codegen.CodegenConstants;
import org.openapitools.codegen.CodegenType;
import org.openapitools.codegen.SupportingFile;
import org.openapitools.codegen.meta.GeneratorMetadata;
import org.openapitools.codegen.meta.Stability;
import org.openapitools.codegen.meta.features.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
public class KotlinServerDeprecatedCodegen extends AbstractKotlinCodegen {
public static final String DEFAULT_LIBRARY = Constants.KTOR;
private final Logger LOGGER = LoggerFactory.getLogger(KotlinServerDeprecatedCodegen.class);
private Boolean autoHeadFeatureEnabled = true;
private Boolean conditionalHeadersFeatureEnabled = false;
private Boolean hstsFeatureEnabled = true;
private Boolean corsFeatureEnabled = false;
private Boolean compressionFeatureEnabled = true;
// This is here to potentially warn the user when an option is not supported by the target framework.
private Map<String, List<String>> optionsSupportedPerFramework = new ImmutableMap.Builder<String, List<String>>()
.put(Constants.KTOR, Arrays.asList(
Constants.AUTOMATIC_HEAD_REQUESTS,
Constants.CONDITIONAL_HEADERS,
Constants.HSTS,
Constants.CORS,
Constants.COMPRESSION
))
.build();
/**
* Constructs an instance of `KotlinServerDeprecatedCodegen`.
*/
public KotlinServerDeprecatedCodegen() {
super();
modifyFeatureSet(features -> features
.includeDocumentationFeatures(DocumentationFeature.Readme)
.wireFormatFeatures(EnumSet.of(WireFormatFeature.JSON, WireFormatFeature.XML))
.securityFeatures(EnumSet.of(
SecurityFeature.BasicAuth,
SecurityFeature.ApiKey,
SecurityFeature.OAuth2_Implicit
))
.excludeGlobalFeatures(
GlobalFeature.XMLStructureDefinitions,
GlobalFeature.Callbacks,
GlobalFeature.LinkObjects,
GlobalFeature.ParameterStyling
)
.excludeSchemaSupportFeatures(
SchemaSupportFeature.Polymorphism
)
.excludeParameterFeatures(
ParameterFeature.Cookie
)
);
generatorMetadata = GeneratorMetadata.newBuilder(generatorMetadata)
.stability(Stability.DEPRECATED)
.build();
artifactId = "kotlin-server-deprecated";
packageName = "org.openapitools.server";
// cliOptions default redefinition need to be updated
updateOption(CodegenConstants.ARTIFACT_ID, this.artifactId);
updateOption(CodegenConstants.PACKAGE_NAME, this.packageName);
outputFolder = "generated-code" + File.separator + "kotlin-server-deprecated";
modelTemplateFiles.put("model.mustache", ".kt");
apiTemplateFiles.put("api.mustache", ".kt");
embeddedTemplateDir = templateDir = "kotlin-server-deprecated";
apiPackage = packageName + ".apis";
modelPackage = packageName + ".models";
supportedLibraries.put(Constants.KTOR, "ktor framework");
// TODO: Configurable server engine. Defaults to netty in build.gradle.
CliOption library = new CliOption(CodegenConstants.LIBRARY, CodegenConstants.LIBRARY_DESC);
library.setDefault(DEFAULT_LIBRARY);
library.setEnum(supportedLibraries);
cliOptions.add(library);
addSwitch(Constants.AUTOMATIC_HEAD_REQUESTS, Constants.AUTOMATIC_HEAD_REQUESTS_DESC, getAutoHeadFeatureEnabled());
addSwitch(Constants.CONDITIONAL_HEADERS, Constants.CONDITIONAL_HEADERS_DESC, getConditionalHeadersFeatureEnabled());
addSwitch(Constants.HSTS, Constants.HSTS_DESC, getHstsFeatureEnabled());
addSwitch(Constants.CORS, Constants.CORS_DESC, getCorsFeatureEnabled());
addSwitch(Constants.COMPRESSION, Constants.COMPRESSION_DESC, getCompressionFeatureEnabled());
}
public Boolean getAutoHeadFeatureEnabled() {
return autoHeadFeatureEnabled;
}
public void setAutoHeadFeatureEnabled(Boolean autoHeadFeatureEnabled) {
this.autoHeadFeatureEnabled = autoHeadFeatureEnabled;
}
public Boolean getCompressionFeatureEnabled() {
return compressionFeatureEnabled;
}
public void setCompressionFeatureEnabled(Boolean compressionFeatureEnabled) {
this.compressionFeatureEnabled = compressionFeatureEnabled;
}
public Boolean getConditionalHeadersFeatureEnabled() {
return conditionalHeadersFeatureEnabled;
}
public void setConditionalHeadersFeatureEnabled(Boolean conditionalHeadersFeatureEnabled) {
this.conditionalHeadersFeatureEnabled = conditionalHeadersFeatureEnabled;
}
public Boolean getCorsFeatureEnabled() {
return corsFeatureEnabled;
}
public void setCorsFeatureEnabled(Boolean corsFeatureEnabled) {
this.corsFeatureEnabled = corsFeatureEnabled;
}
public String getHelp() {
return "Generates a Kotlin server (Ktor v1.1.3). IMPORTANT: this generator has been deprecated." +
" Please migrate to `kotlin-server` which supports Ktor v1.5.2+.";
}
public Boolean getHstsFeatureEnabled() {
return hstsFeatureEnabled;
}
public void setHstsFeatureEnabled(Boolean hstsFeatureEnabled) {
this.hstsFeatureEnabled = hstsFeatureEnabled;
}
public String getName() {
return "kotlin-server-deprecated";
}
public CodegenType getTag() {
return CodegenType.SERVER;
}
@Override
public void processOpts() {
super.processOpts();
if (additionalProperties.containsKey(CodegenConstants.LIBRARY)) {
this.setLibrary((String) additionalProperties.get(CodegenConstants.LIBRARY));
}
// set default library to "ktor"
if (StringUtils.isEmpty(library)) {
this.setLibrary(DEFAULT_LIBRARY);
additionalProperties.put(CodegenConstants.LIBRARY, DEFAULT_LIBRARY);
LOGGER.info("`library` option is empty. Default to " + DEFAULT_LIBRARY);
}
if (additionalProperties.containsKey(Constants.AUTOMATIC_HEAD_REQUESTS)) {
setAutoHeadFeatureEnabled(convertPropertyToBooleanAndWriteBack(Constants.AUTOMATIC_HEAD_REQUESTS));
} else {
additionalProperties.put(Constants.AUTOMATIC_HEAD_REQUESTS, getAutoHeadFeatureEnabled());
}
if (additionalProperties.containsKey(Constants.CONDITIONAL_HEADERS)) {
setConditionalHeadersFeatureEnabled(convertPropertyToBooleanAndWriteBack(Constants.CONDITIONAL_HEADERS));
} else {
additionalProperties.put(Constants.CONDITIONAL_HEADERS, getConditionalHeadersFeatureEnabled());
}
if (additionalProperties.containsKey(Constants.HSTS)) {
setHstsFeatureEnabled(convertPropertyToBooleanAndWriteBack(Constants.HSTS));
} else {
additionalProperties.put(Constants.HSTS, getHstsFeatureEnabled());
}
if (additionalProperties.containsKey(Constants.CORS)) {
setCorsFeatureEnabled(convertPropertyToBooleanAndWriteBack(Constants.CORS));
} else {
additionalProperties.put(Constants.CORS, getCorsFeatureEnabled());
}
if (additionalProperties.containsKey(Constants.COMPRESSION)) {
setCompressionFeatureEnabled(convertPropertyToBooleanAndWriteBack(Constants.COMPRESSION));
} else {
additionalProperties.put(Constants.COMPRESSION, getCompressionFeatureEnabled());
}
boolean generateApis = additionalProperties.containsKey(CodegenConstants.GENERATE_APIS) && (Boolean) additionalProperties.get(CodegenConstants.GENERATE_APIS);
String packageFolder = (sourceFolder + File.separator + packageName).replace(".", File.separator);
String resourcesFolder = "src/main/resources"; // not sure this can be user configurable.
supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
supportingFiles.add(new SupportingFile("Dockerfile.mustache", "", "Dockerfile"));
supportingFiles.add(new SupportingFile("build.gradle.mustache", "", "build.gradle"));
supportingFiles.add(new SupportingFile("settings.gradle.mustache", "", "settings.gradle"));
supportingFiles.add(new SupportingFile("gradle.properties", "", "gradle.properties"));
supportingFiles.add(new SupportingFile("AppMain.kt.mustache", packageFolder, "AppMain.kt"));
supportingFiles.add(new SupportingFile("Configuration.kt.mustache", packageFolder, "Configuration.kt"));
if (generateApis) {
supportingFiles.add(new SupportingFile("Paths.kt.mustache", packageFolder, "Paths.kt"));
}
supportingFiles.add(new SupportingFile("application.conf.mustache", resourcesFolder, "application.conf"));
supportingFiles.add(new SupportingFile("logback.xml", resourcesFolder, "logback.xml"));
final String infrastructureFolder = (sourceFolder + File.separator + packageName + File.separator + "infrastructure").replace(".", File.separator);
supportingFiles.add(new SupportingFile("ApiKeyAuth.kt.mustache", infrastructureFolder, "ApiKeyAuth.kt"));
}
public static class Constants {
public final static String KTOR = "ktor";
public final static String AUTOMATIC_HEAD_REQUESTS = "featureAutoHead";
public final static String AUTOMATIC_HEAD_REQUESTS_DESC = "Automatically provide responses to HEAD requests for existing routes that have the GET verb defined.";
public final static String CONDITIONAL_HEADERS = "featureConditionalHeaders";
public final static String CONDITIONAL_HEADERS_DESC = "Avoid sending content if client already has same content, by checking ETag or LastModified properties.";
public final static String HSTS = "featureHSTS";
public final static String HSTS_DESC = "Avoid sending content if client already has same content, by checking ETag or LastModified properties.";
public final static String CORS = "featureCORS";
public final static String CORS_DESC = "Ktor by default provides an interceptor for implementing proper support for Cross-Origin Resource Sharing (CORS). See enable-cors.org.";
public final static String COMPRESSION = "featureCompression";
public final static String COMPRESSION_DESC = "Adds ability to compress outgoing content using gzip, deflate or custom encoder and thus reduce size of the response.";
}
@Override
public void postProcess() {
System.out.println("################################################################################");
System.out.println("# Thanks for using OpenAPI Generator. #");
System.out.println("# Please consider donation to help us maintain this project \uD83D\uDE4F #");
System.out.println("# https://opencollective.com/openapi_generator/donate #");
System.out.println("# #");
System.out.println("# This generator's contributed by Jim Schubert (https://github.com/jimschubert)#");
System.out.println("# Please support his work directly via https://patreon.com/jimschubert \uD83D\uDE4F #");
System.out.println("################################################################################");
}
}

View File

@ -166,8 +166,8 @@ public class ScalaAkkaClientCodegen extends AbstractScalaCodegen implements Code
additionalProperties.put(CodegenConstants.INVOKER_PACKAGE, invokerPackage);
supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
supportingFiles.add(new SupportingFile("pom.mustache", "", "pom.xml"));
supportingFiles.add(new SupportingFile("build.sbt.mustache", "", "build.sbt"));
supportingFiles.add(new SupportingFile("pom.mustache", "", "pom.xml"));
supportingFiles.add(new SupportingFile("reference.mustache", resourcesFolder, "reference.conf"));
final String invokerFolder = (sourceFolder + File.separator + invokerPackage).replace(".", File.separator);
supportingFiles.add(new SupportingFile("apiRequest.mustache", invokerFolder, "ApiRequest.scala"));
@ -175,6 +175,7 @@ public class ScalaAkkaClientCodegen extends AbstractScalaCodegen implements Code
supportingFiles.add(new SupportingFile("requests.mustache", invokerFolder, "requests.scala"));
supportingFiles.add(new SupportingFile("apiSettings.mustache", invokerFolder, "ApiSettings.scala"));
final String apiFolder = (sourceFolder + File.separator + apiPackage).replace(".", File.separator);
supportingFiles.add(new SupportingFile("project/build.properties.mustache", "project", "build.properties"));
supportingFiles.add(new SupportingFile("enumsSerializers.mustache", apiFolder, "EnumsSerializers.scala"));
supportingFiles.add(new SupportingFile("serializers.mustache", invokerFolder, "Serializers.scala"));
}

View File

@ -37,20 +37,22 @@ import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import static org.openapitools.codegen.utils.StringUtils.camelize;
public class ScalaSttpClientCodegen extends AbstractScalaCodegen implements CodegenConfig {
private static final StringProperty STTP_CLIENT_VERSION = new StringProperty("sttpClientVersion", "The version of " +
"sttp client", "2.2.0");
"sttp client", "2.2.9");
private static final BooleanProperty USE_SEPARATE_ERROR_CHANNEL = new BooleanProperty("separateErrorChannel",
"Whether to return response as " +
"F[Either[ResponseError[ErrorType], ReturnType]]] or to flatten " +
"response's error raising them through enclosing monad (F[ReturnType]).", true);
private static final StringProperty JODA_TIME_VERSION = new StringProperty("jodaTimeVersion", "The version of " +
"joda-time library", "2.10.6");
"joda-time library", "2.10.10");
private static final StringProperty JSON4S_VERSION = new StringProperty("json4sVersion", "The version of json4s " +
"library", "3.6.8");
"library", "3.6.11");
private static final StringProperty CIRCE_VERSION = new StringProperty("circeVersion", "The version of circe " +
"library", "0.13.0");
private static final JsonLibraryProperty JSON_LIBRARY_PROPERTY = new JsonLibraryProperty();
@ -179,8 +181,17 @@ public class ScalaSttpClientCodegen extends AbstractScalaCodegen implements Code
@Override
public String encodePath(String input) {
String result = super.encodePath(input);
return result.replace("{", "${");
String path = super.encodePath(input);
// The parameter names in the URI must be converted to the same case as
// the method parameter.
StringBuffer buf = new StringBuffer(path.length());
Matcher matcher = Pattern.compile("[{](.*?)[}]").matcher(path);
while (matcher.find()) {
matcher.appendReplacement(buf, "\\${" + toParamName(matcher.group(0)) + "}");
}
matcher.appendTail(buf);
return buf.toString();
}
@Override

View File

@ -10,8 +10,8 @@ lazy val root = (project in file(".")).
resolvers += Resolver.mavenLocal,
libraryDependencies ++= Seq(
"io.swagger" % "swagger-annotations" % "1.5.24",
"com.squareup.okhttp3" % "okhttp" % "3.14.7",
"com.squareup.okhttp3" % "logging-interceptor" % "3.14.7",
"com.squareup.okhttp3" % "okhttp" % "4.9.1",
"com.squareup.okhttp3" % "logging-interceptor" % "4.9.1",
"com.google.code.gson" % "gson" % "2.8.6",
"org.apache.commons" % "commons-lang3" % "3.10",
{{#hasOAuthMethods}}

View File

@ -59,6 +59,9 @@ import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.TimeZone;
import javax.annotation.Nullable;
{{#jsr310}}
{{#threetenbp}}
import org.threeten.bp.OffsetDateTime;
@ -98,12 +101,47 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} {
private final WebClient webClient;
private final DateFormat dateFormat;
private final ObjectMapper objectMapper;
private Map<String, Authentication> authentications;
public ApiClient() {
this.dateFormat = createDefaultDateFormat();
this.objectMapper = createDefaultObjectMapper(this.dateFormat);
this.webClient = buildWebClient(this.objectMapper);
this.init();
}
public ApiClient(WebClient webClient) {
this(Optional.ofNullable(webClient).orElseGet(() -> buildWebClient()), createDefaultDateFormat());
}
public ApiClient(ObjectMapper mapper, DateFormat format) {
this(buildWebClient(mapper.copy()), format);
}
public ApiClient(WebClient webClient, ObjectMapper mapper, DateFormat format) {
this(Optional.ofNullable(webClient).orElseGet(() -> buildWebClient(mapper.copy())), format);
}
private ApiClient(WebClient webClient, DateFormat format) {
this.webClient = webClient;
this.dateFormat = format;
this.objectMapper = createDefaultObjectMapper(format);
this.init();
}
public static DateFormat createDefaultDateFormat() {
DateFormat dateFormat = new RFC3339DateFormat();
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
return dateFormat;
}
public static ObjectMapper createDefaultObjectMapper(@Nullable DateFormat dateFormat) {
if (null == dateFormat) {
dateFormat = createDefaultDateFormat();
}
ObjectMapper mapper = new ObjectMapper();
mapper.setDateFormat(dateFormat);
mapper.registerModule(new JavaTimeModule());
@ -112,29 +150,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} {
JsonNullableModule jnm = new JsonNullableModule();
mapper.registerModule(jnm);
{{/openApiNullable}}
this.webClient = buildWebClient(mapper);
this.init();
}
public ApiClient(ObjectMapper mapper, DateFormat format) {
this(buildWebClient(mapper.copy()), format);
}
public ApiClient(WebClient webClient, ObjectMapper mapper, DateFormat format) {
this(Optional.ofNullable(webClient).orElseGet(() ->buildWebClient(mapper.copy())), format);
}
private ApiClient(WebClient webClient, DateFormat format) {
this.webClient = webClient;
this.dateFormat = format;
this.init();
}
public DateFormat createDefaultDateFormat() {
DateFormat dateFormat = new RFC3339DateFormat();
dateFormat.setTimeZone(TimeZone.getTimeZone("UTC"));
return dateFormat;
return mapper;
}
protected void init() {
@ -149,20 +165,45 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} {
}
/**
* Build the WebClient used to make HTTP requests.
* Build the WebClientBuilder used to make WebClient.
* @param mapper ObjectMapper used for serialize/deserialize
* @return WebClient
*/
public static WebClient buildWebClient(ObjectMapper mapper) {
public static WebClient.Builder buildWebClientBuilder(ObjectMapper mapper) {
ExchangeStrategies strategies = ExchangeStrategies
.builder()
.codecs(clientDefaultCodecsConfigurer -> {
clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonEncoder(new Jackson2JsonEncoder(mapper, MediaType.APPLICATION_JSON));
clientDefaultCodecsConfigurer.defaultCodecs().jackson2JsonDecoder(new Jackson2JsonDecoder(mapper, MediaType.APPLICATION_JSON));
}).build();
WebClient.Builder webClient = WebClient.builder().exchangeStrategies(strategies);
return webClient.build();
WebClient.Builder webClientBuilder = WebClient.builder().exchangeStrategies(strategies);
return webClientBuilder;
}
/**
* Build the WebClientBuilder used to make WebClient.
* @return WebClient
*/
public static WebClient.Builder buildWebClientBuilder() {
return buildWebClientBuilder(createDefaultObjectMapper(null));
}
/**
* Build the WebClient used to make HTTP requests.
* @param mapper ObjectMapper used for serialize/deserialize
* @return WebClient
*/
public static WebClient buildWebClient(ObjectMapper mapper) {
return buildWebClientBuilder(mapper).build();
}
/**
* Build the WebClient used to make HTTP requests.
* @return WebClient
*/
public static WebClient buildWebClient() {
return buildWebClientBuilder(createDefaultObjectMapper(null)).build();
}
/**
* Get the current base path
@ -352,6 +393,22 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} {
return dateFormat.format(date);
}
/**
* Get the ObjectMapper used to make HTTP requests.
* @return ObjectMapper objectMapper
*/
public ObjectMapper getObjectMapper() {
return objectMapper;
}
/**
* Get the WebClient used to make HTTP requests.
* @return WebClient webClient
*/
public WebClient getWebClient() {
return webClient;
}
/**
* Format the given parameter object into string.
* @param param the object to convert
@ -655,10 +712,10 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} {
}
// collectionFormat is assumed to be "csv" by default
if(collectionFormat == null) {
collectionFormat = CollectionFormat.CSV;
}
if(collectionFormat == null) {
collectionFormat = CollectionFormat.CSV;
}
return collectionFormat.collectionToString(values);
return collectionFormat.collectionToString(values);
}
}

View File

@ -36,7 +36,6 @@ export type FetchArgs = {
options: {};
}
/**
*
* @export
@ -79,12 +78,12 @@ export const {{classname}}FetchParamCreator = function (configuration?: Configur
{{/summary}}
* @throws {RequiredError}
*/
{{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/allParams}}options: RequestOptions): FetchArgs {
{{operationId}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/allParams}}options: RequestOptions): FetchArgs {
{{#allParams}}
{{#required}}
// verify required parameter '{{paramName}}' is not null or undefined
if ({{paramName}} === null || {{paramName}} === undefined) {
throw new RequiredError('{{paramName}}','Required parameter {{paramName}} was null or undefined when calling {{nickname}}.');
throw new RequiredError('{{paramName}}','Required parameter {{paramName}} was null or undefined when calling {{operationId}}.');
}
{{/required}}
{{/allParams}}
@ -104,16 +103,16 @@ export const {{classname}}FetchParamCreator = function (configuration?: Configur
{{#isKeyInHeader}}
if (configuration && configuration.apiKey) {
const localVarApiKeyValue = typeof configuration.apiKey === 'function'
? configuration.apiKey("{{keyParamName}}")
: configuration.apiKey;
? configuration.apiKey("{{keyParamName}}")
: configuration.apiKey;
localVarHeaderParameter["{{keyParamName}}"] = localVarApiKeyValue;
}
{{/isKeyInHeader}}
{{#isKeyInQuery}}
if (configuration && configuration.apiKey) {
const localVarApiKeyValue = typeof configuration.apiKey === 'function'
? configuration.apiKey("{{keyParamName}}")
: configuration.apiKey;
? configuration.apiKey("{{keyParamName}}")
: configuration.apiKey;
localVarQueryParameter["{{keyParamName}}"] = localVarApiKeyValue;
}
{{/isKeyInQuery}}
@ -127,9 +126,9 @@ export const {{classname}}FetchParamCreator = function (configuration?: Configur
{{#isOAuth}}
// oauth required
if (configuration && configuration.accessToken) {
const localVarAccessTokenValue = typeof configuration.accessToken === 'function'
? configuration.accessToken("{{name}}", [{{#scopes}}"{{{scope}}}"{{^-last}}, {{/-last}}{{/scopes}}])
: configuration.accessToken;
const localVarAccessTokenValue = typeof configuration.accessToken === 'function'
? configuration.accessToken("{{name}}", [{{#scopes}}"{{{scope}}}"{{^-last}}, {{/-last}}{{/scopes}}])
: configuration.accessToken;
localVarHeaderParameter["Authorization"] = "Bearer " + localVarAccessTokenValue;
}
{{/isOAuth}}
@ -142,7 +141,7 @@ export const {{classname}}FetchParamCreator = function (configuration?: Configur
localVarQueryParameter['{{baseName}}'] = {{paramName}};
{{/isCollectionFormatMulti}}
{{^isCollectionFormatMulti}}
localVarQueryParameter['{{baseName}}'] = {{paramName}}.join(COLLECTION_FORMATS["{{collectionFormat}}"]);
localVarQueryParameter['{{baseName}}'] = {{#uniqueItems}}Array.from({{/uniqueItems}}{{paramName}}{{#uniqueItems}}){{/uniqueItems}}.join(COLLECTION_FORMATS["{{collectionFormat}}"]);
{{/isCollectionFormatMulti}}
}
{{/isArray}}
@ -166,7 +165,7 @@ export const {{classname}}FetchParamCreator = function (configuration?: Configur
{{#headerParams}}
{{#isArray}}
if ({{paramName}}) {
localVarHeaderParameter['{{baseName}}'] = {{paramName}}.join(COLLECTION_FORMATS["{{collectionFormat}}"]);
localVarHeaderParameter['{{baseName}}'] = {{#uniqueItems}}Array.from({{/uniqueItems}}{{paramName}}{{#uniqueItems}}){{/uniqueItems}}.join(COLLECTION_FORMATS["{{collectionFormat}}"]);
}
{{/isArray}}
{{^isArray}}
@ -185,7 +184,7 @@ export const {{classname}}FetchParamCreator = function (configuration?: Configur
})
{{/isCollectionFormatMulti}}
{{^isCollectionFormatMulti}}
localVarFormParams.set('{{baseName}}', {{paramName}}.join(COLLECTION_FORMATS["{{collectionFormat}}"]));
localVarFormParams.set('{{baseName}}', {{#uniqueItems}}Array.from({{/uniqueItems}}{{paramName}}{{#uniqueItems}}){{/uniqueItems}}.join(COLLECTION_FORMATS["{{collectionFormat}}"]));
{{/isCollectionFormatMulti}}
}
{{/isArray}}
@ -227,7 +226,7 @@ export const {{classname}}FetchParamCreator = function (configuration?: Configur
};
export type {{classname}}Type = { {{#operation}}
{{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/allParams}}options?: RequestOptions): Promise<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Response{{/returnType}}>,
{{operationId}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/allParams}}options?: RequestOptions): Promise<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Response{{/returnType}}>,
{{/operation}}
}
@ -247,8 +246,8 @@ export const {{classname}} = function(configuration?: Configuration, fetch: Fetc
{{/summary}}
* @throws {RequiredError}
*/
{{nickname}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/allParams}}options?: RequestOptions = {}): Promise<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Response{{/returnType}}> {
const localVarFetchArgs = {{classname}}FetchParamCreator(configuration).{{nickname}}({{#allParams}}{{paramName}}, {{/allParams}}options);
{{operationId}}({{#allParams}}{{paramName}}{{^required}}?{{/required}}: {{{dataType}}}, {{/allParams}}options?: RequestOptions = {}): Promise<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Response{{/returnType}}> {
const localVarFetchArgs = {{classname}}FetchParamCreator(configuration).{{operationId}}({{#allParams}}{{paramName}}, {{/allParams}}options);
return fetch(basePath + localVarFetchArgs.url, localVarFetchArgs.options).then((response) => {
if (response.status >= 200 && response.status < 300) {
return response{{#returnType}}.json(){{/returnType}};
@ -260,6 +259,7 @@ export const {{classname}} = function(configuration?: Configuration, fetch: Fetc
{{/operation}}
}
};
{{/operations}}{{/apis}}{{/apiInfo}}
export type ApiTypes = { {{#apiInfo}}{{#apis}}{{#operations}}
{{classname}}: {{classname}}Type,

View File

@ -44,6 +44,7 @@ org.openapitools.codegen.languages.GraphQLNodeJSExpressServerCodegen
org.openapitools.codegen.languages.GroovyClientCodegen
org.openapitools.codegen.languages.KotlinClientCodegen
org.openapitools.codegen.languages.KotlinServerCodegen
org.openapitools.codegen.languages.KotlinServerDeprecatedCodegen
org.openapitools.codegen.languages.KotlinSpringServerCodegen
org.openapitools.codegen.languages.KotlinVertxServerCodegen
org.openapitools.codegen.languages.KtormSchemaCodegen

View File

@ -979,7 +979,7 @@ case $key in
body_parameters[${body_key}]=${body_value}
fi
;;
+\([^=]\):*)
+([^=]):*)
# Parse header arguments and convert them into curl
# only after the operation argument
if [[ "$operation" ]]; then

View File

@ -14,24 +14,21 @@
#include <pistache/http_headers.h>
#include <pistache/optional.h>
{{^hasModelImport}}#include <nlohmann/json.hpp>{{/hasModelImport}}
#include <utility>
{{#imports}}{{{import}}}
{{/imports}}
{{#apiNamespaceDeclarations}}
namespace {{this}} {
{{/apiNamespaceDeclarations}}
{{#hasModelImport}}
using namespace {{modelNamespace}};{{/hasModelImport}}
namespace {{apiNamespace}}
{
class {{declspec}} {{classname}} {
public:
{{classname}}(std::shared_ptr<Pistache::Rest::Router>);
virtual ~{{classname}}() {}
explicit {{classname}}(const std::shared_ptr<Pistache::Rest::Router>& rtr);
virtual ~{{classname}}() = default;
void init();
const std::string base = "{{basePathWithoutHost}}";
static const std::string base;
private:
void setupRoutes();
@ -41,9 +38,21 @@ private:
{{/operation}}
void {{classnameSnakeLowerCase}}_default_handler(const Pistache::Rest::Request &request, Pistache::Http::ResponseWriter response);
std::shared_ptr<Pistache::Rest::Router> router;
{{#operation}}
const std::shared_ptr<Pistache::Rest::Router> router;
/// <summary>
/// Helper function to handle unexpected Exceptions during Parameter parsing and validation.
/// May be overriden to return custom error formats.
/// </summary>
virtual std::pair<Pistache::Http::Code, std::string> handleParsingException(const std::exception& ex) const noexcept;
/// <summary>
/// Helper function to handle unexpected Exceptions during processing of the request in handler functions.
/// May be overriden to return custom error formats.
/// </summary>
virtual std::pair<Pistache::Http::Code, std::string> handleOperationException(const std::exception& ex) const noexcept;
{{#operation}}
/// <summary>
/// {{summary}}
/// </summary>
@ -54,7 +63,7 @@ private:
{{#allParams}}
/// <param name="{{paramName}}">{{description}}{{^required}} (optional{{#defaultValue}}, default to {{.}}{{/defaultValue}}){{/required}}</param>
{{/allParams}}
virtual void {{operationIdSnakeCase}}({{#allParams}}const {{{dataType}}} &{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}Pistache::Http::ResponseWriter &response) = 0;
virtual void {{operationIdSnakeCase}}({{#allParams}}const {{#isModel}}{{modelNamespace}}::{{/isModel}}{{{dataType}}} &{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}Pistache::Http::ResponseWriter &response) = 0;
{{/vendorExtensions.x-codegen-pistache-is-parsing-supported}}
{{^vendorExtensions.x-codegen-pistache-is-parsing-supported}}
virtual void {{operationIdSnakeCase}}(const Pistache::Rest::Request &request, Pistache::Http::ResponseWriter &response) = 0;
@ -63,9 +72,7 @@ private:
};
{{#apiNamespaceDeclarations}}
}
{{/apiNamespaceDeclarations}}
} // namespace {{apiNamespace}}
#endif /* {{classname}}_H_ */

View File

@ -22,17 +22,16 @@
{{#imports}}{{{import}}}
{{/imports}}
{{#apiNamespaceDeclarations}}
namespace {{this}} {
{{/apiNamespaceDeclarations}}
namespace {{apiNamespace}}
{
{{#hasModelImport}}
using namespace {{modelNamespace}};{{/hasModelImport}}
class {{classname}}Impl : public {{apiNamespace}}::{{classname}} {
public:
{{classname}}Impl(std::shared_ptr<Pistache::Rest::Router>);
~{{classname}}Impl() {}
explicit {{classname}}Impl(const std::shared_ptr<Pistache::Rest::Router>& rtr);
~{{classname}}Impl() override = default;
{{#operation}}
{{#vendorExtensions.x-codegen-pistache-is-parsing-supported}}
@ -45,9 +44,7 @@ public:
};
{{#apiNamespaceDeclarations}}
}
{{/apiNamespaceDeclarations}}
} // namespace {{apiNamespace}}
{{/operations}}

View File

@ -10,9 +10,10 @@ namespace {{this}} {
{{#hasModelImport}}
using namespace {{modelNamespace}};{{/hasModelImport}}
{{classname}}Impl::{{classname}}Impl(std::shared_ptr<Pistache::Rest::Router> rtr)
{{classname}}Impl::{{classname}}Impl(const std::shared_ptr<Pistache::Rest::Router>& rtr)
: {{classname}}(rtr)
{ }
{
}
{{#operation}}
{{#vendorExtensions.x-codegen-pistache-is-parsing-supported}}

View File

@ -4,16 +4,18 @@
#include "{{classname}}.h"
#include "{{prefix}}Helpers.h"
{{#apiNamespaceDeclarations}}
namespace {{this}} {
{{/apiNamespaceDeclarations}}
namespace {{apiNamespace}}
{
using namespace {{helpersNamespace}};
{{#hasModelImport}}
using namespace {{modelNamespace}};{{/hasModelImport}}
{{classname}}::{{classname}}(std::shared_ptr<Pistache::Rest::Router> rtr) {
router = rtr;
const std::string {{classname}}::base = "{{basePathWithoutHost}}";
{{classname}}::{{classname}}(const std::shared_ptr<Pistache::Rest::Router>& rtr)
: router(rtr)
{
}
void {{classname}}::init() {
@ -31,8 +33,26 @@ void {{classname}}::setupRoutes() {
router->addCustomHandler(Routes::bind(&{{classname}}::{{classnameSnakeLowerCase}}_default_handler, this));
}
std::pair<Pistache::Http::Code, std::string> {{classname}}::handleParsingException(const std::exception& ex) const noexcept
{
try {
throw ex;
} catch (nlohmann::detail::exception &e) {
return std::make_pair(Pistache::Http::Code::Bad_Request, e.what());
} catch ({{helpersNamespace}}::ValidationException &e) {
return std::make_pair(Pistache::Http::Code::Bad_Request, e.what());
}
}
std::pair<Pistache::Http::Code, std::string> {{classname}}::handleOperationException(const std::exception& ex) const noexcept
{
return std::make_pair(Pistache::Http::Code::Internal_Server_Error, ex.what());
}
{{#operation}}
void {{classname}}::{{operationIdSnakeCase}}_handler(const Pistache::Rest::Request &{{#hasParams}}request{{/hasParams}}, Pistache::Http::ResponseWriter response) {
try {
{{#vendorExtensions.x-codegen-pistache-is-parsing-supported}}
{{#hasPathParams}}
// Getting the path params
@ -71,32 +91,40 @@ void {{classname}}::{{operationIdSnakeCase}}_handler(const Pistache::Rest::Reque
{{#hasBodyParam}}
{{#bodyParam}}
{{^isPrimitiveType}}
nlohmann::json::parse(request.body()).get_to({{paramName}});
nlohmann::json::parse(request.body()).get_to({{paramName}});
{{paramName}}.validate();
{{/isPrimitiveType}}
{{#isPrimitiveType}}
{{paramName}} = request.body();
{{paramName}} = request.body();
{{/isPrimitiveType}}
} catch (std::exception &e) {
const std::pair<Pistache::Http::Code, std::string> errorInfo = this->handleParsingException(e);
response.send(errorInfo.first, errorInfo.second);
return;
}
try {
{{/bodyParam}}
{{/hasBodyParam}}
this->{{operationIdSnakeCase}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}response);
this->{{operationIdSnakeCase}}({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}response);
{{/vendorExtensions.x-codegen-pistache-is-parsing-supported}}
{{^vendorExtensions.x-codegen-pistache-is-parsing-supported}}
try {
this->{{operationIdSnakeCase}}(request, response);
{{/vendorExtensions.x-codegen-pistache-is-parsing-supported}}
} catch (nlohmann::detail::exception &e) {
//send a 400 error
response.send(Pistache::Http::Code::Bad_Request, e.what());
return;
} catch (Pistache::Http::HttpError &e) {
response.send(static_cast<Pistache::Http::Code>(e.code()), e.what());
return;
} catch (std::exception &e) {
//send a 500 error
response.send(Pistache::Http::Code::Internal_Server_Error, e.what());
const std::pair<Pistache::Http::Code, std::string> errorInfo = this->handleOperationException(e);
response.send(errorInfo.first, errorInfo.second);
return;
}
} catch (std::exception &e) {
response.send(Pistache::Http::Code::Internal_Server_Error, e.what());
}
}
{{/operation}}
@ -104,8 +132,6 @@ void {{classname}}::{{classnameSnakeLowerCase}}_default_handler(const Pistache::
response.send(Pistache::Http::Code::Not_Found, "The requested method does not exist");
}
{{#apiNamespaceDeclarations}}
}
{{/apiNamespaceDeclarations}}
} // namespace {{apiNamespace}}
{{/operations}}

View File

@ -14,16 +14,80 @@
#include <vector>
#include <map>
{{#helpersNamespaceDeclarations}}
namespace {{this}} {
{{/helpersNamespaceDeclarations}}
namespace {{helpersNamespace}}
{
class ValidationException : public std::runtime_error
{
public:
explicit ValidationException(const std::string& what)
: std::runtime_error(what)
{ }
~ValidationException() override = default;
};
/// <summary>
/// Validate a string against the full-date definition of RFC 3339, section 5.6.
/// </summary>
bool validateRfc3339_date(const std::string& str);
/// <summary>
/// Validate a string against the date-time definition of RFC 3339, section 5.6.
/// </summary>
bool validateRfc3339_date_time(const std::string& str);
namespace sfinae_helpers
{
struct NoType {};
template <typename T1, typename T2> NoType operator==(const T1&, const T2&);
template <typename T1, typename T2> class EqualsOperatorAvailable
{
public:
enum
{
value = !std::is_same< decltype(std::declval<T1>() == std::declval<T2>()), NoType >::value
};
};
} // namespace sfinae_helpers
/// <summary>
/// Determine if the given vector<T> only has unique elements. T must provide the == operator.
/// </summary>
template <typename T>
bool hasOnlyUniqueItems(const std::vector<T>& vec)
{
static_assert(sfinae_helpers::EqualsOperatorAvailable<T, T>::value,
"hasOnlyUniqueItems<T> cannot be called, passed template type does not provide == operator.");
if (vec.size() <= 1)
{
return true;
}
// Compare every element of vec to every other element of vec.
// This isn't an elegant way to do this, since it's O(n^2),
// but it's the best solution working only with the == operator.
// This could be greatly improved if our models provided a valid hash
// and/or the < operator
for (size_t i = 0; i < vec.size() - 1; i++)
{
for (size_t j = i + 1; j < vec.size(); j++)
{
if (vec[i] == vec[j])
{
return false;
}
}
}
return true;
}
std::string toStringValue(const std::string &value);
std::string toStringValue(const int32_t &value);
std::string toStringValue(const int64_t &value);
std::string toStringValue(const bool &value);
std::string toStringValue(const float &value);
std::string toStringValue(const double &value);
std::string toStringValue(const int32_t value);
std::string toStringValue(const int64_t value);
std::string toStringValue(const bool value);
std::string toStringValue(const float value);
std::string toStringValue(const double value);
bool fromStringValue(const std::string &inStr, std::string &value);
bool fromStringValue(const std::string &inStr, int32_t &value);
@ -57,8 +121,6 @@ namespace {{this}} {
return fromStringValue(inStrings, value);
}
{{#helpersNamespaceDeclarations}}
}
{{/helpersNamespaceDeclarations}}
} // namespace {{helpersNamespace}}
#endif // {{prefix}}Helpers_H_

View File

@ -1,32 +1,74 @@
{{>licenseInfo}}
#include "{{prefix}}Helpers.h"
#include <regex>
{{#helpersNamespaceDeclarations}}
namespace {{this}} {
{{/helpersNamespaceDeclarations}}
namespace {{helpersNamespace}}
{
const std::regex regexRfc3339_date(R"(^(\d{4})\-(\d{2})\-(\d{2})$)");
const std::regex regexRfc3339_date_time(
R"(^(\d{4})\-(\d{2})\-(\d{2})[Tt](\d{2}):(\d{2}):(\d{2})(\.\d+)?([Zz]|([\+\-])(\d{2}):(\d{2}))$)"
);
namespace
{
// Determine if given year is a leap year
// See RFC 3339, Appendix C https://tools.ietf.org/html/rfc3339#appendix-C
bool isLeapYear(const uint16_t year) {
return (year % 4 == 0) && ((year % 100 != 0) || (year % 400 == 0));
}
bool validateDateValues(const uint16_t year, const uint16_t month, const uint16_t day) {
return !(
(month == 0 || month > 12)
|| (day == 0)
|| (month == 2 && day > (28 + (isLeapYear(year) ? 1 : 0)))
|| (month <= 7 && day > (30 + month % 2))
|| (month >= 8 && day > (31 - month % 2))
);
}
bool validateTimeValues(const uint16_t hours, const uint16_t minutes, const uint16_t seconds) {
return (hours <= 23) && (minutes <= 59) && (seconds <= 60);
}
}
bool validateRfc3339_date(const std::string& str) {
std::smatch match;
const bool found = std::regex_search(str, match, regexRfc3339_date);
return found && validateDateValues(std::stoi(match[1]), std::stoi(match[2]), std::stoi(match[3]));
}
bool validateRfc3339_date_time(const std::string& str) {
std::smatch match;
const bool found = std::regex_search(str, match, regexRfc3339_date_time);
return found
&& validateDateValues(std::stoi(match[1]), std::stoi(match[2]), std::stoi(match[3]))
&& validateTimeValues(std::stoi(match[4]), std::stoi(match[5]), std::stoi(match[6]));
}
std::string toStringValue(const std::string &value){
return std::string(value);
}
std::string toStringValue(const int32_t &value){
std::string toStringValue(const int32_t value){
return std::to_string(value);
}
std::string toStringValue(const int64_t &value){
std::string toStringValue(const int64_t value){
return std::to_string(value);
}
std::string toStringValue(const bool &value){
return value?std::string("true"):std::string("false");
std::string toStringValue(const bool value){
return value ? std::string("true") : std::string("false");
}
std::string toStringValue(const float &value){
std::string toStringValue(const float value){
return std::to_string(value);
}
std::string toStringValue(const double &value){
std::string toStringValue(const double value){
return std::to_string(value);
}
@ -56,9 +98,15 @@ bool fromStringValue(const std::string &inStr, int64_t &value){
}
bool fromStringValue(const std::string &inStr, bool &value){
bool result = true;
inStr == "true"?value = true: inStr == "false"?value = false: result = false;
return result;
if (inStr == "true") {
value = true;
return true;
}
if (inStr == "false") {
value = false;
return true;
}
return false;
}
bool fromStringValue(const std::string &inStr, float &value){
@ -81,6 +129,4 @@ bool fromStringValue(const std::string &inStr, double &value){
return true;
}
{{#helpersNamespaceDeclarations}}
}
{{/helpersNamespaceDeclarations}}
} // namespace {{helpersNamespace}}

View File

@ -13,9 +13,8 @@
{{/imports}}
#include <nlohmann/json.hpp>
{{#modelNamespaceDeclarations}}
namespace {{this}} {
{{/modelNamespaceDeclarations}}
namespace {{modelNamespace}}
{
/// <summary>
/// {{description}}
@ -24,7 +23,7 @@ class {{declspec}} {{classname}}
{
public:
{{classname}}();
virtual ~{{classname}}();
virtual ~{{classname}}() = default;
{{#isEnum}}{{#allowableValues}}
enum class e{{classname}} {
// To have a valid default value.
@ -35,7 +34,20 @@ public:
{{{name}}}{{^-last}}, {{/-last}}
{{/enumVars}}
};{{/allowableValues}}{{/isEnum}}
void validate();
/// <summary>
/// Validate the current data in the model. Throws a ValidationException on failure.
/// </summary>
void validate() const;
/// <summary>
/// Validate the current data in the model. Returns false on error and writes an error
/// message into the given stringstream.
/// </summary>
bool validate(std::stringstream& msg) const;
bool operator==(const {{classname}}& rhs) const;
bool operator!=(const {{classname}}& rhs) const;
/////////////////////////////////////////////
/// {{classname}} members
@ -44,7 +56,7 @@ public:
/// <summary>
/// {{description}}
/// </summary>
{{{dataType}}}{{#isContainer}}&{{/isContainer}} {{getter}}(){{^isContainer}} const{{/isContainer}};
{{{dataType}}} {{getter}}() const;
void {{setter}}({{{dataType}}} const{{^isPrimitiveType}}&{{/isPrimitiveType}} value);{{^required}}
bool {{nameInCamelCase}}IsSet() const;
void unset{{name}}();{{/required}}
@ -65,11 +77,12 @@ protected:
{{#isEnum}}
{{classname}}::e{{classname}} m_value = {{classname}}::e{{classname}}::INVALID_VALUE_OPENAPI_GENERATED;
{{/isEnum}}
// Helper overload for validate. Used when one model stores another model and calls it's validate.
bool validate(std::stringstream& msg, const std::string& pathPrefix) const;
};
{{#modelNamespaceDeclarations}}
}
{{/modelNamespaceDeclarations}}
} // namespace {{modelNamespace}}
#endif /* {{classname}}_H_ */
{{/model}}

View File

@ -2,12 +2,12 @@
{{#models}}{{#model}}
#include "{{classname}}.h"
{{#isEnum}}#include <stdexcept>
#include <sstream>{{/isEnum}}
#include "{{prefix}}Helpers.h"
{{#isEnum}}#include <stdexcept>{{/isEnum}}
#include <sstream>
{{#modelNamespaceDeclarations}}
namespace {{this}} {
{{/modelNamespaceDeclarations}}
namespace {{modelNamespace}}
{
{{classname}}::{{classname}}()
{
@ -18,13 +18,69 @@ namespace {{this}} {
{{/required}}{{/vars}}
}
{{classname}}::~{{classname}}()
void {{classname}}::validate() const
{
std::stringstream msg;
if (!validate(msg))
{
throw {{helpersNamespace}}::ValidationException(msg.str());
}
}
void {{classname}}::validate()
bool {{classname}}::validate(std::stringstream& msg) const
{
// TODO: implement validation
return validate(msg, "");
}
bool {{classname}}::validate(std::stringstream& msg, const std::string& pathPrefix) const
{
bool success = true;
const std::string _pathPrefix = pathPrefix.empty() ? "{{classname}}" : pathPrefix;
{{#isEnum}}{{! Special case for enum types }}
if (m_value == {{classname}}::e{{classname}}::INVALID_VALUE_OPENAPI_GENERATED)
{
success = false;
msg << _pathPrefix << ": has no value;";
}
{{/isEnum}}
{{^isEnum}}
{{#vars}}
{{#isArray}} {{! Always generate validation body for array types }}
{{^required}}if ({{nameInCamelCase}}IsSet()){{/required}}
{{#required}}/* {{name}} */ {{/required}}{
const {{{dataType}}}& value = m_{{name}};
const std::string currentValuePath = _pathPrefix + ".{{nameInCamelCase}}";
{{> model-validation-body }}
}
{{/isArray}}{{^isArray}}{{#hasValidation}} {{! Only generate validation if necessary }}
{{^required}}if ({{nameInCamelCase}}IsSet()){{/required}}
{{#required}}/* {{name}} */ {{/required}}{
const {{{dataType}}}& value = m_{{name}};
const std::string currentValuePath = _pathPrefix + ".{{nameInCamelCase}}";
{{> model-validation-body }}
}
{{/hasValidation}}{{/isArray}}
{{/vars}}
{{/isEnum}}
return success;
}
bool {{classname}}::operator==(const {{classname}}& rhs) const
{
return
{{#isEnum}}getValue() == rhs.getValue(){{/isEnum}}
{{^isEnum}}{{#vars}}
{{#required}}({{getter}}() == rhs.{{getter}}()){{/required}}
{{^required}}((!{{nameInCamelCase}}IsSet() && !rhs.{{nameInCamelCase}}IsSet()) || ({{nameInCamelCase}}IsSet() && rhs.{{nameInCamelCase}}IsSet() && {{getter}}() == rhs.{{getter}}())){{/required}}{{^-last}} &&{{/-last}}
{{/vars}}{{/isEnum}}
;
}
bool {{classname}}::operator!=(const {{classname}}& rhs) const
{
return !(*this == rhs);
}
void to_json(nlohmann::json& j, const {{classname}}& o)
@ -74,7 +130,7 @@ void from_json(const nlohmann::json& j, {{classname}}& o)
{{/enumVars}}{{/allowableValues}}{{/isEnum}}
}
{{#vars}}{{{dataType}}}{{#isContainer}}&{{/isContainer}} {{classname}}::{{getter}}(){{^isContainer}} const{{/isContainer}}
{{#vars}}{{{dataType}}} {{classname}}::{{getter}}() const
{
return m_{{name}};
}
@ -102,9 +158,7 @@ void {{classname}}::setValue({{classname}}::e{{classname}} value)
m_value = value;
}{{/isEnum}}
{{#modelNamespaceDeclarations}}
}
{{/modelNamespaceDeclarations}}
} // namespace {{modelNamespace}}
{{/model}}
{{/models}}

View File

@ -14,9 +14,8 @@
#include <nlohmann/json.hpp>
{{#hasOptional}}#include <pistache/optional.h>{{/hasOptional}}
{{#modelNamespaceDeclarations}}
namespace {{this}} {
{{/modelNamespaceDeclarations}}
namespace {{modelNamespace}}
{
struct {{classname}}
{
@ -34,9 +33,9 @@ struct {{classname}}
void to_json(nlohmann::json& j, const {{classname}}& o);
void from_json(const nlohmann::json& j, {{classname}}& o);
{{#modelNamespaceDeclarations}}
} // {{this}}
{{/modelNamespaceDeclarations}}
} // namespace {{modelNamespace}}
#endif /* {{classname}}_H_ */
{{/model}}

View File

@ -3,9 +3,8 @@
#include "{{classname}}.h"
{{#modelNamespaceDeclarations}}
namespace {{this}} {
{{/modelNamespaceDeclarations}}
namespace {{modelNamespace}}
{
nlohmann::json {{classname}}::to_json() const
{
@ -51,9 +50,7 @@ void from_json(const nlohmann::json& j, {{classname}}& o)
{{/vars}}
}
{{#modelNamespaceDeclarations}}
} // {{this}}
{{/modelNamespaceDeclarations}}
} // namespace {{modelNamespace}}
{{/model}}
{{/models}}

View File

@ -0,0 +1,124 @@
{{!
This template generates the validation logic for a model.
This template file calls itself recursively for arrays.
}}
{{! Check for eunm properties that are their own schema }}
{{! These are in their own class with a validate function, the way to check for this is quite a hack
since isEnum, isString and isModel are all false. }}
{{^isString}}{{#allowableValues.enumVars.0.value}}
success = value.validate(msg, currentValuePath) && success;
{{/allowableValues.enumVars.0.value}}{{/isString}}
{{#isModel}}success = value.validate(msg, currentValuePath + ".{{nameInCamelCase}}") && success;{{/isModel}}
{{! Date validation }}
{{#isDate}}
if (!{{helpersNamespace}}::validateRfc3339_date(value))
{
success = false;
msg << currentValuePath << ": must be a valid RFC 3339 date-full string;";
}
{{/isDate}}
{{! Date-Time validation }}
{{#isDateTime}}
if (!{{helpersNamespace}}::validateRfc3339_date_time(value))
{
success = false;
msg << currentValuePath << ": must be a valid RFC 3339 date-time string;";
}
{{/isDateTime}}
{{! string validation }}
{{#isString}}
{{#minLength}}
if (value.length() < {{minLength}})
{
success = false;
msg << currentValuePath << ": must be at least {{minLength}} characters long;";
}
{{/minLength}}
{{#maxLength}}
if (value.length() > {{maxLength}})
{
success = false;
msg << currentValuePath << ": must be at most {{maxLength}} characters long;";
}
{{/maxLength}}
{{!
TODO validate regex of string using pattern variable. This has two challenges
- Is compatibility with the given regex pattern guaranteed?
- Creating the std::regex on every validation would be rather slow. Ideally one would
initialize them for the class once as a static const and use them.
}}
{{! string encoded enum validation }}
{{#isEnum}}
{{#allowableValues}}
if ({{#enumVars}}
value != "{{value}}"{{^-last}} &&{{/-last}}{{/enumVars}}
) {
success = false;
msg << currentValuePath << ": has invalid value \"" << value << "\";";
}
{{/allowableValues}}
{{/isEnum}}
{{/isString}}
{{! numeric validation }}
{{#isNumeric}}
{{#minimum}}
if (value <{{#exclusiveMinimum}}={{/exclusiveMinimum}} {{#isFloat}}static_cast<float>({{/isFloat}}{{minimum}}{{#isFloat}}){{/isFloat}}{{#isLong}}ll{{/isLong}})
{
success = false;
msg << currentValuePath << ": must be greater than{{^exclusiveMinimum}} or equal to{{/exclusiveMinimum}} {{minimum}};";
}
{{/minimum}}
{{#maximum}}
if (value >{{#exclusiveMaximum}}={{/exclusiveMaximum}} {{#isFloat}}static_cast<float>({{/isFloat}}{{maximum}}{{#isFloat}}){{/isFloat}}{{#isLong}}ll{{/isLong}})
{
success = false;
msg << currentValuePath << ": must be less than{{^exclusiveMaximum}} or equal to{{/exclusiveMaximum}} {{maximum}};";
}
{{/maximum}}
{{#multipleOf}}
{{#isInteger}}if (value % {{multipleOf}}{{#isLong}}ll{{/isLong}} != 0){{/isInteger}}
{{#isFloat}}if (std::fmod(value, static_cast<float>({{multipleOf}})) != 0){{/isFloat}}
{{#isDouble}}if (std::fmod(value, {{multipleOf}}) != 0){{/isDouble}}
{
success = false;
msg << currentValuePath << ": must be a multiple of {{multipleOf}};";
}
{{/multipleOf}}
{{/isNumeric}}
{{! Array validation }}
{{#isArray}}
{{#minItems}}
if (value.size() < {{minItems}})
{
success = false;
msg << currentValuePath << ": must have at least {{minItems}} elements;";
}
{{/minItems}}
{{#maxItems}}
if (value.size() > {{maxItems}})
{
success = false;
msg << currentValuePath << ": must have at most {{maxItems}} elements;";
}
{{/maxItems}}
{{#uniqueItems}}
if (!{{helpersNamespace}}::hasOnlyUniqueItems(value))
{
success = false;
msg << currentValuePath << ": may not contain the same item more than once;";
}
{{/uniqueItems}}
{ // Recursive validation of array elements
const std::string oldValuePath = currentValuePath;
int i = 0;
{{! the element var has the same name as the vector, so that the recursive template works - what a wonderful hack }}
for (const {{{items.dataType}}}& value : value)
{ {{! and I do a similar hack with currentValuePath... }}
const std::string currentValuePath = oldValuePath + "[" + std::to_string(i) + "]";
{{#items}}
{{> model-validation-body }} {{! Recursively apply template to array - this is where things will probbaly go wrong }}
{{/items}}
i++;
}
}
{{/isArray}}

View File

@ -15,10 +15,20 @@ public:
{{classname}}();
~{{classname}}();
/* Sets the URL Endpoint.
* Note: several fallback endpoints can be configured in request retry policies, see Request::SetShouldRetry */
void SetURL(const FString& Url);
/* Adds global header params to all requests */
void AddHeaderParam(const FString& Key, const FString& Value);
void ClearHeaderParams();
/* Sets the retry manager to the user-defined retry manager. User must manage the lifetime of the retry manager.
* If no retry manager is specified and a request needs retries, a default retry manager will be used.
* See also: Request::SetShouldRetry */
void SetHttpRetryManager(FHttpRetrySystem::FManager& RetryManager);
FHttpRetrySystem::FManager& GetHttpRetryManager();
{{#operations}}{{#operation}}class {{operationIdCamelCase}}Request;
class {{operationIdCamelCase}}Response;
{{/operation}}{{/operations}}
@ -27,15 +37,17 @@ public:
{{#operations}}{{#operation}}{{#description}}/* {{{description}}} */
{{/description}}bool {{operationIdCamelCase}}(const {{operationIdCamelCase}}Request& Request, const F{{operationIdCamelCase}}Delegate& Delegate = F{{operationIdCamelCase}}Delegate()) const;
{{/operation}}{{/operations}}
private:
{{#operations}}{{#operation}}void On{{operationIdCamelCase}}Response(FHttpRequestPtr HttpRequest, FHttpResponsePtr HttpResponse, bool bSucceeded, F{{operationIdCamelCase}}Delegate Delegate, int AutoRetryCount) const;
{{#operations}}{{#operation}}void On{{operationIdCamelCase}}Response(FHttpRequestPtr HttpRequest, FHttpResponsePtr HttpResponse, bool bSucceeded, F{{operationIdCamelCase}}Delegate Delegate) const;
{{/operation}}{{/operations}}
FHttpRequestRef CreateHttpRequest(const Request& Request) const;
bool IsValid() const;
void HandleResponse(FHttpResponsePtr HttpResponse, bool bSucceeded, Response& InOutResponse) const;
FString Url;
TMap<FString,FString> AdditionalHeaderParams;
mutable FHttpRetrySystem::FManager* RetryManager = nullptr;
mutable TUniquePtr<HttpRetryManager> DefaultRetryManager;
};
{{#cppNamespaceDeclarations}}

View File

@ -45,6 +45,40 @@ bool {{classname}}::IsValid() const
return true;
}
void {{classname}}::SetHttpRetryManager(FHttpRetrySystem::FManager& InRetryManager)
{
if(RetryManager != &GetHttpRetryManager())
{
DefaultRetryManager.Reset();
RetryManager = &InRetryManager;
}
}
FHttpRetrySystem::FManager& {{classname}}::GetHttpRetryManager()
{
return *RetryManager;
}
FHttpRequestRef {{classname}}::CreateHttpRequest(const Request& Request) const
{
if (!Request.GetRetryParams().IsSet())
{
return FHttpModule::Get().CreateRequest();
}
else
{
if (!RetryManager)
{
// Create default retry manager if none was specified
DefaultRetryManager = MakeUnique<HttpRetryManager>(6, 60);
RetryManager = DefaultRetryManager.Get();
}
const HttpRetryParams& Params = Request.GetRetryParams().GetValue();
return RetryManager->CreateRequest(Params.RetryLimitCountOverride, Params.RetryTimeoutRelativeSecondsOverride, Params.RetryResponseCodes, Params.RetryVerbs, Params.RetryDomains);
}
}
void {{classname}}::HandleResponse(FHttpResponsePtr HttpResponse, bool bSucceeded, Response& InOutResponse) const
{
InOutResponse.SetHttpResponse(HttpResponse);
@ -96,7 +130,7 @@ bool {{classname}}::{{operationIdCamelCase}}(const {{operationIdCamelCase}}Reque
if (!IsValid())
return false;
FHttpRequestRef HttpRequest = FHttpModule::Get().CreateRequest();
FHttpRequestRef HttpRequest = CreateHttpRequest(Request);
HttpRequest->SetURL(*(Url + Request.ComputePath()));
for(const auto& It : AdditionalHeaderParams)
@ -106,26 +140,15 @@ bool {{classname}}::{{operationIdCamelCase}}(const {{operationIdCamelCase}}Reque
Request.SetupHttpRequest(HttpRequest);
HttpRequest->OnProcessRequestComplete().BindRaw(this, &{{classname}}::On{{operationIdCamelCase}}Response, Delegate, Request.GetAutoRetryCount());
HttpRequest->OnProcessRequestComplete().BindRaw(this, &{{classname}}::On{{operationIdCamelCase}}Response, Delegate);
return HttpRequest->ProcessRequest();
}
void {{classname}}::On{{operationIdCamelCase}}Response(FHttpRequestPtr HttpRequest, FHttpResponsePtr HttpResponse, bool bSucceeded, F{{operationIdCamelCase}}Delegate Delegate, int AutoRetryCount) const
void {{classname}}::On{{operationIdCamelCase}}Response(FHttpRequestPtr HttpRequest, FHttpResponsePtr HttpResponse, bool bSucceeded, F{{operationIdCamelCase}}Delegate Delegate) const
{
{{operationIdCamelCase}}Response Response;
Response.SetHttpRequest(HttpRequest);
HandleResponse(HttpResponse, bSucceeded, Response);
if(!Response.IsSuccessful() && AutoRetryCount > 0)
{
HttpRequest->OnProcessRequestComplete().BindRaw(this, &{{classname}}::On{{operationIdCamelCase}}Response, Delegate, AutoRetryCount - 1);
Response.AsyncRetry();
}
else
{
Delegate.ExecuteIfBound(Response);
}
Delegate.ExecuteIfBound(Response);
}
{{/operation}}

View File

@ -5,6 +5,8 @@
#include "Interfaces/IHttpResponse.h"
#include "Serialization/JsonWriter.h"
#include "Dom/JsonObject.h"
#include "HttpRetrySystem.h"
#include "Containers/Ticker.h"
{{#cppNamespaceDeclarations}}
namespace {{this}}
@ -12,6 +14,31 @@ namespace {{this}}
{{/cppNamespaceDeclarations}}
typedef TSharedRef<TJsonWriter<>> JsonWriter;
using namespace FHttpRetrySystem;
struct {{dllapi}} HttpRetryManager : public FManager, public FTickerObjectBase
{
using FManager::FManager;
bool Tick(float DeltaTime) final;
};
struct {{dllapi}} HttpRetryParams
{
HttpRetryParams(
const FRetryLimitCountSetting& InRetryLimitCountOverride = FRetryLimitCountSetting(),
const FRetryTimeoutRelativeSecondsSetting& InRetryTimeoutRelativeSecondsOverride = FRetryTimeoutRelativeSecondsSetting(),
const FRetryResponseCodes& InRetryResponseCodes = FRetryResponseCodes(),
const FRetryVerbs& InRetryVerbs = FRetryVerbs(),
const FRetryDomainsPtr& InRetryDomains = FRetryDomainsPtr()
);
FRetryLimitCountSetting RetryLimitCountOverride;
FRetryTimeoutRelativeSecondsSetting RetryTimeoutRelativeSecondsOverride;
FRetryResponseCodes RetryResponseCodes;
FRetryVerbs RetryVerbs;
FRetryDomainsPtr RetryDomains;
};
class {{dllapi}} Model
{
@ -28,11 +55,12 @@ public:
virtual void SetupHttpRequest(const FHttpRequestRef& HttpRequest) const = 0;
virtual FString ComputePath() const = 0;
void SetAutoRetryCount(int InCount) { AutoRetryCount = InCount; }
int GetAutoRetryCount() const { return AutoRetryCount; }
/* Enables retry and optionally sets a retry policy for this request */
void SetShouldRetry(const HttpRetryParams& Params = HttpRetryParams()) { RetryParams = Params; }
const TOptional<HttpRetryParams>& GetRetryParams() const { return RetryParams; }
private:
int AutoRetryCount = 0;
TOptional<HttpRetryParams> RetryParams;
};
class {{dllapi}} Response
@ -44,8 +72,6 @@ public:
void SetSuccessful(bool InSuccessful) { Successful = InSuccessful; }
bool IsSuccessful() const { return Successful; }
void AsyncRetry() const;
virtual void SetHttpResponseCode(EHttpResponseCodes::Type InHttpResponseCode);
EHttpResponseCodes::Type GetHttpResponseCode() const { return ResponseCode; }
@ -55,15 +81,11 @@ public:
void SetHttpResponse(const FHttpResponsePtr& InHttpResponse) { HttpResponse = InHttpResponse; }
const FHttpResponsePtr& GetHttpResponse() const { return HttpResponse; }
void SetHttpRequest(const FHttpRequestPtr& InHttpRequest) { HttpRequest = InHttpRequest; }
const FHttpRequestPtr& GetHttpRequest() const { return HttpRequest; }
private:
bool Successful;
EHttpResponseCodes::Type ResponseCode;
FString ResponseString;
FHttpResponsePtr HttpResponse;
FHttpRequestPtr HttpRequest;
};
{{#cppNamespaceDeclarations}}

View File

@ -1,13 +1,30 @@
{{>licenseInfo}}
#include "{{modelNamePrefix}}BaseModel.h"
#include "Async/Async.h"
{{#cppNamespaceDeclarations}}
namespace {{this}}
{
{{/cppNamespaceDeclarations}}
bool HttpRetryManager::Tick(float DeltaTime)
{
FManager::Update();
return true;
}
HttpRetryParams::HttpRetryParams(const FRetryLimitCountSetting& InRetryLimitCountOverride /*= FRetryLimitCountSetting()*/,
const FRetryTimeoutRelativeSecondsSetting& InRetryTimeoutRelativeSecondsOverride /*= FRetryTimeoutRelativeSecondsSetting()*/,
const FRetryResponseCodes& InRetryResponseCodes /*= FRetryResponseCodes()*/,
const FRetryVerbs& InRetryVerbs /*= FRetryVerbs()*/,
const FRetryDomainsPtr& InRetryDomains /*= FRetryDomainsPtr() */)
: RetryLimitCountOverride(InRetryLimitCountOverride)
, RetryTimeoutRelativeSecondsOverride(InRetryTimeoutRelativeSecondsOverride)
, RetryResponseCodes(InRetryResponseCodes)
, RetryVerbs(InRetryVerbs)
, RetryDomains(InRetryDomains)
{
}
void Response::SetHttpResponseCode(EHttpResponseCodes::Type InHttpResponseCode)
{
ResponseCode = InHttpResponseCode;
@ -18,15 +35,6 @@ void Response::SetHttpResponseCode(EHttpResponseCodes::Type InHttpResponseCode)
}
}
void Response::AsyncRetry() const
{
// Unfortunately, it is currently usafe to call ProcessRequest() directly here.
// This is because the HttpManager will remove all references to this HttpRequest in FHttpManager::Tick including the new request we just added, instead of removing just one.
// This will lead to the request's destruction and eventually a crash.
// The only solution is therefore to ensure we are taking an extra reference to the request, and that the request is added after the queue is flushed.
Async(EAsyncExecution::TaskGraph, [AddRef = FHttpRequestPtr(GetHttpRequest())](){ AddRef->ProcessRequest(); });
}
{{#cppNamespaceDeclarations}}
}
{{/cppNamespaceDeclarations}}

View File

@ -197,44 +197,44 @@ module {{moduleName}}
# Returns Auth Settings hash for api client.
def auth_settings
Hash{ {{#authMethods}}{{#isApiKey}}"{{name}}" => {
type: "api_key",
in: {{#isKeyInHeader}}"header"{{/isKeyInHeader}}{{#isKeyInQuery}}"query"{{/isKeyInQuery}},
key: "{{keyParamName}}",
value: api_key_with_prefix("{{keyParamName}}")
},
Hash{
{{#authMethods}}
{{#isApiKey}}
"{{name}}" => {
type: "api_key",
in: {{#isKeyInHeader}}"header"{{/isKeyInHeader}}{{#isKeyInQuery}}"query"{{/isKeyInQuery}},
key: "{{keyParamName}}",
value: api_key_with_prefix("{{keyParamName}}")
},
{{/isApiKey}}
{{#isBasic}}
{{#isBasicBasic}}
"{{name}}" =>
{
type: "basic",
in: "header",
key: "Authorization",
value: basic_auth_token
},
"{{name}}" => {
type: "basic",
in: "header",
key: "Authorization",
value: basic_auth_token
},
{{/isBasicBasic}}
{{#isBasicBearer}}
"{{name}}" =>
{
type: "bearer",
in: "header",
{{#bearerFormat}}
format: "{{{.}}}",
{{/bearerFormat}}
key: "Authorization",
value: "Bearer #{access_token}"
},
"{{name}}" => {
type: "bearer",
in: "header",
{{#bearerFormat}}
format: "{{{.}}}",
{{/bearerFormat}}
key: "Authorization",
value: "Bearer #{access_token}"
},
{{/isBasicBearer}}
{{/isBasic}}
{{#isOAuth}}
"{{name}}" =>
{
type: "oauth2",
in: "header",
key: "Authorization",
value: "Bearer #{access_token}"
},
"{{name}}" => {
type: "oauth2",
in: "header",
key: "Authorization",
value: "Bearer #{access_token}"
},
{{/isOAuth}}
{{/authMethods}}
}

View File

@ -1,5 +1,6 @@
# {{#lambdaPrefixWithHash}}{{> api_info}}{{/lambdaPrefixWithHash}}
require "json"
require "time"
module {{moduleName}}

View File

@ -1,8 +1,10 @@
{{#description}}
# {{{description}}}
{{/description}}
class {{classname}}{{#parent}} < {{{.}}}{{/parent}} include JSON::Serializable
include JSON::Serializable {{#vars}}
class {{classname}}{{#parent}} < {{{.}}}{{/parent}}
include JSON::Serializable
{{#vars}}
{{#description}}
# {{{description}}}
{{/description}}

View File

@ -1,4 +1,4 @@
name: {{{moduleName}}}
name: {{{shardName}}}
version: {{{shardVersion}}}
authors:
- {{{shardAuthors}}}

View File

@ -137,6 +137,9 @@ services.AddHttpClient<YourApiClass>(httpClient =>
```csharp
using System.Collections.Generic;
using System.Diagnostics;
{{#useHttpClient}}
using System.Net.Http;
{{/useHttpClient}}
using {{packageName}}.{{apiPackage}};
using {{packageName}}.Client;
using {{packageName}}.{{modelPackage}};
@ -174,7 +177,15 @@ namespace Example
{{/authMethods}}
{{/hasAuthMethods}}
{{#useHttpClient}}
// create instances of HttpClient, HttpClientHandler to be reused later with different Api classes
HttpClient httpClient = new HttpClient();
HttpClientHandler httpClientHandler = new HttpClientHandler();
var apiInstance = new {{classname}}(httpClient, config, httpClientHandler);
{{/useHttpClient}}
{{^useHttpClient}}
var apiInstance = new {{classname}}(config);
{{/useHttpClient}}
{{#allParams}}
{{#isPrimitiveType}}
var {{paramName}} = {{{example}}}; // {{{dataType}}} | {{{description}}}{{^required}} (optional) {{/required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}}

View File

@ -22,6 +22,9 @@ Method | HTTP request | Description
```csharp
using System.Collections.Generic;
using System.Diagnostics;
{{#useHttpClient}}
using System.Net.Http;
{{/useHttpClient}}
using {{packageName}}.{{apiPackage}};
using {{packageName}}.Client;
using {{packageName}}.{{modelPackage}};
@ -58,7 +61,15 @@ namespace Example
{{/authMethods}}
{{/hasAuthMethods}}
{{#useHttpClient}}
// create instances of HttpClient, HttpClientHandler to be reused later with different Api classes
HttpClient httpClient = new HttpClient();
HttpClientHandler httpClientHandler = new HttpClientHandler();
var apiInstance = new {{classname}}(httpClient, config, httpClientHandler);
{{/useHttpClient}}
{{^useHttpClient}}
var apiInstance = new {{classname}}(config);
{{/useHttpClient}}
{{#allParams}}
{{#isPrimitiveType}}
var {{paramName}} = {{{example}}}; // {{{dataType}}} | {{{description}}}{{^required}} (optional) {{/required}}{{#defaultValue}} (default to {{{.}}}){{/defaultValue}}

View File

@ -98,13 +98,13 @@ namespace {{packageName}}.Client
if (type == typeof(byte[])) // return byte array
{
return response.Content.ReadAsByteArrayAsync().Result;
return response.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult();
}
// TODO: ? if (type.IsAssignableFrom(typeof(Stream)))
if (type == typeof(Stream))
{
var bytes = response.Content.ReadAsByteArrayAsync().Result;
var bytes = response.Content.ReadAsByteArrayAsync().GetAwaiter().GetResult();
if (headers != null)
{
var filePath = String.IsNullOrEmpty(_configuration.TempFolderPath)
@ -128,18 +128,18 @@ namespace {{packageName}}.Client
if (type.Name.StartsWith("System.Nullable`1[[System.DateTime")) // return a datetime object
{
return DateTime.Parse(response.Content.ReadAsStringAsync().Result, null, System.Globalization.DateTimeStyles.RoundtripKind);
return DateTime.Parse(response.Content.ReadAsStringAsync().GetAwaiter().GetResult(), null, System.Globalization.DateTimeStyles.RoundtripKind);
}
if (type == typeof(String) || type.Name.StartsWith("System.Nullable")) // return primitive type
{
return Convert.ChangeType(response.Content.ReadAsStringAsync().Result, type);
return Convert.ChangeType(response.Content.ReadAsStringAsync().GetAwaiter().GetResult(), type);
}
// at this point, it must be a model (json)
try
{
return JsonConvert.DeserializeObject(response.Content.ReadAsStringAsync().Result, type, _serializerSettings);
return JsonConvert.DeserializeObject(response.Content.ReadAsStringAsync().GetAwaiter().GetResult(), type, _serializerSettings);
}
catch (Exception e)
{
@ -191,8 +191,9 @@ namespace {{packageName}}.Client
/// <summary>
/// Initializes a new instance of the <see cref="ApiClient" />, defaulting to the global configurations' base url.
/// **IMPORTANT** This will also create an istance of HttpClient, which is less than ideal.
/// It's better to reuse the <see href="https://docs.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests#issues-with-the-original-httpclient-class-available-in-net">HttpClient and HttpClientHander</see>.
/// </summary>
[Obsolete("Constructors without HttpClient have non-trivial drawbacks and are thus considered deprecated. Check README.md for details.")]
public ApiClient() :
this({{packageName}}.Client.GlobalConfiguration.Instance.BasePath)
{
@ -200,10 +201,11 @@ namespace {{packageName}}.Client
/// <summary>
/// Initializes a new instance of the <see cref="ApiClient" />.
/// **IMPORTANT** This will also create an istance of HttpClient, which is less than ideal.
/// It's better to reuse the <see href="https://docs.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests#issues-with-the-original-httpclient-class-available-in-net">HttpClient and HttpClientHander</see>.
/// </summary>
/// <param name="basePath">The target service's base path in URL format.</param>
/// <exception cref="ArgumentException"></exception>
[Obsolete("Constructors without HttpClient have non-trivial drawbacks and are thus considered deprecated. Check README.md for details.")]
public ApiClient(String basePath)
{
if (string.IsNullOrEmpty(basePath)) throw new ArgumentException("basePath cannot be empty");
@ -399,10 +401,10 @@ namespace {{packageName}}.Client
partial void InterceptRequest(HttpRequestMessage req);
partial void InterceptResponse(HttpRequestMessage req, HttpResponseMessage response);
private ApiResponse<T> ToApiResponse<T>(HttpResponseMessage response, object responseData, Uri uri)
private async Task<ApiResponse<T>> ToApiResponse<T>(HttpResponseMessage response, object responseData, Uri uri)
{
T result = (T) responseData;
string rawContent = response.Content.ToString();
string rawContent = await response.Content.ReadAsStringAsync();
var transformed = new ApiResponse<T>(response.StatusCode, new Multimap<string, string>({{#caseInsensitiveResponseHeaders}}StringComparer.OrdinalIgnoreCase{{/caseInsensitiveResponseHeaders}}), result, rawContent)
{
@ -444,7 +446,7 @@ namespace {{packageName}}.Client
private ApiResponse<T> Exec<T>(HttpRequestMessage req, IReadableConfiguration configuration)
{
return ExecAsync<T>(req, configuration).Result;
return ExecAsync<T>(req, configuration).GetAwaiter().GetResult();
}
private async Task<ApiResponse<T>> ExecAsync<T>(HttpRequestMessage req,
@ -509,6 +511,11 @@ namespace {{packageName}}.Client
}
{{/supportsRetry}}
if (!response.IsSuccessStatusCode)
{
return await ToApiResponse<T>(response, default(T), req.RequestUri);
}
object responseData = deserializer.Deserialize<T>(response);
// if the response type is oneOf/anyOf, call FromJSON to deserialize the data
@ -523,9 +530,7 @@ namespace {{packageName}}.Client
InterceptResponse(req, response);
var result = ToApiResponse<T>(response, responseData, req.RequestUri);
return result;
return await ToApiResponse<T>(response, responseData, req.RequestUri);
}
{{#supportsAsync}}

View File

@ -106,20 +106,22 @@ namespace {{packageName}}.{{apiPackage}}
/// <summary>
/// Initializes a new instance of the <see cref="{{classname}}"/> class.
/// **IMPORTANT** This will also create an istance of HttpClient, which is less than ideal.
/// It's better to reuse the <see href="https://docs.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests#issues-with-the-original-httpclient-class-available-in-net">HttpClient and HttpClientHander</see>.
/// </summary>
/// <returns></returns>
[Obsolete("Constructors without HttpClient have non-trivial drawbacks and are thus considered deprecated. Check README.md for details.")]
public {{classname}}() : this((string)null)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="{{classname}}"/> class.
/// **IMPORTANT** This will also create an istance of HttpClient, which is less than ideal.
/// It's better to reuse the <see href="https://docs.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests#issues-with-the-original-httpclient-class-available-in-net">HttpClient and HttpClientHander</see>.
/// </summary>
/// <param name="basePath">The target service's base path in URL format.</param>
/// <exception cref="ArgumentException"></exception>
/// <returns></returns>
[Obsolete("Constructors without HttpClient have non-trivial drawbacks and are thus considered deprecated. Check README.md for details.")]
public {{classname}}(String basePath)
{
this.Configuration = {{packageName}}.Client.Configuration.MergeConfigurations(
@ -136,11 +138,12 @@ namespace {{packageName}}.{{apiPackage}}
/// <summary>
/// Initializes a new instance of the <see cref="{{classname}}"/> class using Configuration object.
/// **IMPORTANT** This will also create an istance of HttpClient, which is less than ideal.
/// It's better to reuse the <see href="https://docs.microsoft.com/en-us/dotnet/architecture/microservices/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests#issues-with-the-original-httpclient-class-available-in-net">HttpClient and HttpClientHander</see>.
/// </summary>
/// <param name="configuration">An instance of Configuration.</param>
/// <exception cref="ArgumentNullException"></exception>
/// <returns></returns>
[Obsolete("Constructors without HttpClient have non-trivial drawbacks and are thus considered deprecated. Check README.md for details.")]
public {{classname}}({{packageName}}.Client.Configuration configuration)
{
if (configuration == null) throw new ArgumentNullException("configuration");

View File

@ -84,7 +84,7 @@ class ApiClient {
? '?${urlEncodedQueryParams.join('&')}'
: '';
final url = '$basePath$path$queryString';
final Uri uri = Uri.parse('$basePath$path$queryString');
if (nullableContentType != null) {
headerParams['Content-Type'] = nullableContentType;
@ -96,7 +96,7 @@ class ApiClient {
body is MultipartFile && (nullableContentType == null ||
!nullableContentType.toLowerCase().startsWith('multipart/form-data'))
) {
final request = StreamedRequest(method, Uri.parse(url));
final request = StreamedRequest(method, uri);
request.headers.addAll(headerParams);
request.contentLength = body.length;
body.finalize().listen(
@ -110,7 +110,7 @@ class ApiClient {
}
if (body is MultipartRequest) {
final request = MultipartRequest(method, Uri.parse(url));
final request = MultipartRequest(method, uri);
request.fields.addAll(body.fields);
request.files.addAll(body.files);
request.headers.addAll(body.headers);
@ -125,12 +125,12 @@ class ApiClient {
final nullableHeaderParams = headerParams.isEmpty ? null : headerParams;
switch(method) {
case 'POST': return await _client.post(url, headers: nullableHeaderParams, body: msgBody,);
case 'PUT': return await _client.put(url, headers: nullableHeaderParams, body: msgBody,);
case 'DELETE': return await _client.delete(url, headers: nullableHeaderParams,);
case 'PATCH': return await _client.patch(url, headers: nullableHeaderParams, body: msgBody,);
case 'HEAD': return await _client.head(url, headers: nullableHeaderParams,);
case 'GET': return await _client.get(url, headers: nullableHeaderParams,);
case 'POST': return await _client.post(uri, headers: nullableHeaderParams, body: msgBody,);
case 'PUT': return await _client.put(uri, headers: nullableHeaderParams, body: msgBody,);
case 'DELETE': return await _client.delete(uri, headers: nullableHeaderParams,);
case 'PATCH': return await _client.patch(uri, headers: nullableHeaderParams, body: msgBody,);
case 'HEAD': return await _client.head(uri, headers: nullableHeaderParams,);
case 'GET': return await _client.get(uri, headers: nullableHeaderParams,);
}
} on SocketException catch (e, trace) {
throw ApiException.withInner(HttpStatus.badRequest, 'Socket operation failed: $method $path', e, trace,);

View File

@ -16,4 +16,3 @@ targets:
field_rename: none
ignore_unannotated: false
include_if_null: false
nullable: true

View File

@ -9,15 +9,15 @@ authors:
- '{{{pubAuthor}}} <{{{pubAuthorEmail}}}>'
homepage: '{{{pubHomepage}}}'
environment:
sdk: '>=2.0.0 <3.0.0'
sdk: '>=2.12.0 <3.0.0'
dependencies:
http: '>=0.12.0 <0.13.0'
intl: '^0.16.1'
http: '>=0.13.0 <0.14.0'
intl: '^0.17.0'
meta: '^1.1.8'
{{#json_serializable}}
json_annotation: '^3.1.1'{{/json_serializable}}
dev_dependencies:
test: '>=1.3.0 <1.16.0'
test: '>=1.16.0 <1.18.0'
{{#json_serializable}}
build_runner: '^1.0.0'
build_runner: '^1.10.9'
json_serializable: '^3.5.1'{{/json_serializable}}

View File

@ -1,6 +1,6 @@
{deps, [
{cowboy, {git, "https://github.com/ninenines/cowboy.git", {tag, "2.5.0"}}},
{cowboy, {git, "https://github.com/ninenines/cowboy.git", {tag, "2.8.0"}}},
{rfc3339, {git, "https://github.com/talentdeficit/rfc3339.git", {tag, "master"}}},
{jsx, {git, "https://github.com/talentdeficit/jsx.git", {tag, "2.9.0"}}},
{jesse, {git, "https://github.com/for-GET/jesse.git", {tag, "1.5.2"}}}
{jsx, {git, "https://github.com/talentdeficit/jsx.git", {tag, "v3.1.0"}}},
{jesse, {git, "https://github.com/for-GET/jesse.git", {tag, "1.5.6"}}}
]}.

View File

@ -25,7 +25,6 @@ import kotlinx.serialization.Contextual
{{#parcelizeModels}}
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
{{/parcelizeModels}}
{{/multiplatform}}
{{#multiplatform}}

View File

@ -97,7 +97,7 @@ import {{packageName}}.infrastructure.toMultiValue
{{/hasQueryParams}}
val localVariableHeaders: MutableMap<String, String> = mutableMapOf({{#hasFormParams}}"Content-Type" to {{^consumes}}"multipart/form-data"{{/consumes}}{{#consumes.0}}"{{{mediaType}}}"{{/consumes.0}}{{/hasFormParams}})
{{#headerParams}}
{{{paramName}}}?.apply { localVariableHeaders["{{baseName}}"] = {{#isContainer}}this.joinToString(separator = collectionDelimiter("{{collectionFormat}}")){{/isContainer}}{{^isContainer}}this.toString(){{/isContainer}} }
{{{paramName}}}{{^required}}?{{/required}}.apply { localVariableHeaders["{{baseName}}"] = {{#isContainer}}this.joinToString(separator = collectionDelimiter("{{collectionFormat}}")){{/isContainer}}{{^isContainer}}this.toString(){{/isContainer}} }
{{/headerParams}}
val localVariableConfig = RequestConfig(

View File

@ -0,0 +1,84 @@
# {{packageName}} - Kotlin Server library for {{appName}}
## Requires
* Kotlin 1.1.2
* Gradle 3.3
## Build
First, create the gradle wrapper script:
```
gradle wrapper
```
Then, run:
```
./gradlew check assemble
```
This runs all tests and packages the library.
## Features/Implementation Notes
* Supports JSON inputs/outputs, File inputs, and Form inputs.
* Supports collection formats for query parameters: csv, tsv, ssv, pipes.
* Some Kotlin and Java types are fully qualified to avoid conflicts with types defined in OpenAPI definitions.
{{#generateApiDocs}}
<a name="documentation-for-api-endpoints"></a>
## Documentation for API Endpoints
All URIs are relative to *{{{basePath}}}*
Class | Method | HTTP request | Description
------------ | ------------- | ------------- | -------------
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}*{{classname}}* | [**{{operationId}}**]({{apiDocPath}}{{classname}}.md#{{operationIdLowerCase}}) | **{{httpMethod}}** {{path}} | {{#summary}}{{{summary}}}{{/summary}}
{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
{{/generateApiDocs}}
{{#generateModelDocs}}
<a name="documentation-for-models"></a>
## Documentation for Models
{{#modelPackage}}
{{#models}}{{#model}} - [{{{modelPackage}}}.{{{classname}}}]({{modelDocPath}}{{{classname}}}.md)
{{/model}}{{/models}}
{{/modelPackage}}
{{^modelPackage}}
No model defined in this package
{{/modelPackage}}
{{/generateModelDocs}}
<a name="documentation-for-authorization"></a>{{! TODO: optional documentation for authorization? }}
## Documentation for Authorization
{{^authMethods}}
All endpoints do not require authorization.
{{/authMethods}}
{{#authMethods}}
{{#last}}
Authentication schemes defined for the API:
{{/last}}
{{/authMethods}}
{{#authMethods}}
<a name="{{name}}"></a>
### {{name}}
{{#isApiKey}}- **Type**: API key
- **API key parameter name**: {{keyParamName}}
- **Location**: {{#isKeyInQuery}}URL query string{{/isKeyInQuery}}{{#isKeyInHeader}}HTTP header{{/isKeyInHeader}}
{{/isApiKey}}
{{#isBasic}}- **Type**: HTTP basic authentication
{{/isBasic}}
{{#isOAuth}}- **Type**: OAuth
- **Flow**: {{flow}}
- **Authorization URL**: {{authorizationUrl}}
- **Scopes**: {{^scopes}}N/A{{/scopes}}
{{#scopes}} - {{scope}}: {{description}}
{{/scopes}}
{{/isOAuth}}
{{/authMethods}}

View File

@ -0,0 +1,65 @@
# {{classname}}{{#description}}
{{description}}{{/description}}
All URIs are relative to *{{basePath}}*
Method | HTTP request | Description
------------- | ------------- | -------------
{{#operations}}{{#operation}}[**{{operationId}}**]({{classname}}.md#{{operationId}}) | **{{httpMethod}}** {{path}} | {{#summary}}{{summary}}{{/summary}}
{{/operation}}{{/operations}}
{{#operations}}
{{#operation}}
<a name="{{operationId}}"></a>
# **{{operationId}}**
> {{#returnType}}{{returnType}} {{/returnType}}{{operationId}}({{#allParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/allParams}})
{{summary}}{{#notes}}
{{notes}}{{/notes}}
### Example
```kotlin
// Import classes:
//import {{{packageName}}}.infrastructure.*
//import {{{modelPackage}}}.*
{{! TODO: Auth method documentation examples}}
val apiInstance = {{{classname}}}()
{{#allParams}}
val {{{paramName}}} : {{{dataType}}} = {{{example}}} // {{{dataType}}} | {{{description}}}
{{/allParams}}
try {
{{#returnType}}val result : {{{returnType}}} = {{/returnType}}apiInstance.{{{operationId}}}({{#allParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/allParams}}){{#returnType}}
println(result){{/returnType}}
} catch (e: ClientException) {
println("4xx response calling {{{classname}}}#{{{operationId}}}")
e.printStackTrace()
} catch (e: ServerException) {
println("5xx response calling {{{classname}}}#{{{operationId}}}")
e.printStackTrace()
}
```
### Parameters
{{^allParams}}This endpoint does not need any parameter.{{/allParams}}{{#allParams}}{{#-last}}
Name | Type | Description | Notes
------------- | ------------- | ------------- | -------------{{/-last}}{{/allParams}}
{{#allParams}} **{{paramName}}** | {{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}{{#isFile}}**{{dataType}}**{{/isFile}}{{^isFile}}{{#generateModelDocs}}[**{{dataType}}**]({{baseType}}.md){{/generateModelDocs}}{{^generateModelDocs}}**{{dataType}}**{{/generateModelDocs}}{{/isFile}}{{/isPrimitiveType}}| {{description}} |{{^required}} [optional]{{/required}}{{#defaultValue}} [default to {{defaultValue}}]{{/defaultValue}}{{#allowableValues}} [enum: {{#values}}{{{.}}}{{^-last}}, {{/-last}}{{/values}}]{{/allowableValues}}
{{/allParams}}
### Return type
{{#returnType}}{{#returnTypeIsPrimitive}}**{{returnType}}**{{/returnTypeIsPrimitive}}{{^returnTypeIsPrimitive}}{{#generateModelDocs}}[**{{returnType}}**]({{returnBaseType}}.md){{/generateModelDocs}}{{^generateModelDocs}}**{{returnType}}**{{/generateModelDocs}}{{/returnTypeIsPrimitive}}{{/returnType}}{{^returnType}}null (empty response body){{/returnType}}
### Authorization
{{^authMethods}}No authorization required{{/authMethods}}{{#authMethods}}[{{name}}](../README.md#{{name}}){{^-last}}, {{/-last}}{{/authMethods}}
### HTTP request headers
- **Content-Type**: {{#consumes}}{{{mediaType}}}{{^-last}}, {{/-last}}{{/consumes}}{{^consumes}}Not defined{{/consumes}}
- **Accept**: {{#produces}}{{{mediaType}}}{{^-last}}, {{/-last}}{{/produces}}{{^produces}}Not defined{{/produces}}
{{/operation}}
{{/operations}}

View File

@ -0,0 +1,15 @@
# {{classname}}
## Properties
Name | Type | Description | Notes
------------ | ------------- | ------------- | -------------
{{#vars}}**{{name}}** | {{#isEnum}}[**inline**](#{{datatypeWithEnum}}){{/isEnum}}{{^isEnum}}{{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}[**{{dataType}}**]({{complexType}}.md){{/isPrimitiveType}}{{/isEnum}} | {{description}} | {{^required}} [optional]{{/required}}{{#isReadOnly}} [readonly]{{/isReadOnly}}
{{/vars}}
{{#vars}}{{#isEnum}}
<a name="{{{datatypeWithEnum}}}"></a>{{!NOTE: see java's resources "pojo_doc.mustache" once enums are fully implemented}}
## Enum: {{baseName}}
Name | Value
---- | -----{{#allowableValues}}
{{name}} | {{#values}}{{.}}{{^-last}}, {{/-last}}{{/values}}{{/allowableValues}}
{{/isEnum}}{{/vars}}

View File

@ -0,0 +1,52 @@
{{#parcelizeModels}}
import android.os.Parcelable
import kotlinx.parcelize.Parcelize
{{/parcelizeModels}}
{{#serializableModel}}
import java.io.Serializable
{{/serializableModel}}
/**
* {{{description}}}
{{#vars}}
* @param {{name}} {{{description}}}
{{/vars}}
*/
{{#parcelizeModels}}
@Parcelize
{{/parcelizeModels}}
data class {{classname}} (
{{#requiredVars}}
{{>data_class_req_var}}{{^-last}},
{{/-last}}{{/requiredVars}}{{#hasRequired}}{{#hasOptional}},
{{/hasOptional}}{{/hasRequired}}{{#optionalVars}}{{>data_class_opt_var}}{{^-last}},
{{/-last}}{{/optionalVars}}
) {{^serializableModel}}{{#parcelizeModels}} : Parcelable{{/parcelizeModels}}{{/serializableModel}}{{^parcelizeModels}}{{#serializableModel}}: Serializable {{/serializableModel}}{{/parcelizeModels}}{{#parcelizeModels}}{{#serializableModel}} : Parcelable, Serializable {{/serializableModel}}{{/parcelizeModels}}
{{#vendorExtensions.x-has-data-class-body}}
{
{{/vendorExtensions.x-has-data-class-body}}
{{#serializableModel}}
companion object {
private const val serialVersionUID: Long = 123
}
{{/serializableModel}}
{{#hasEnums}}
{{#vars}}
{{#isEnum}}
/**
* {{{description}}}
* Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^-last}},{{/-last}}{{/enumVars}}{{/allowableValues}}
*/
enum class {{nameInCamelCase}}(val value: {{dataType}}){
{{#allowableValues}}
{{#enumVars}}
{{&name}}({{{value}}}){{^-last}},{{/-last}}{{#-last}};{{/-last}}
{{/enumVars}}
{{/allowableValues}}
}
{{/isEnum}}
{{/vars}}
{{/hasEnums}}
{{#vendorExtensions.x-has-data-class-body}}
}
{{/vendorExtensions.x-has-data-class-body}}

View File

@ -0,0 +1,4 @@
{{#description}}
/* {{{description}}} */
{{/description}}
{{>modelMutable}} {{{name}}}: {{#isEnum}}{{classname}}.{{nameInCamelCase}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}? = {{#defaultvalue}}{{defaultvalue}}{{/defaultvalue}}{{^defaultvalue}}null{{/defaultvalue}}

View File

@ -0,0 +1,4 @@
{{#description}}
/* {{{description}}} */
{{/description}}
{{>modelMutable}} {{{name}}}: {{#isEnum}}{{classname}}.{{nameInCamelCase}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}

View File

@ -0,0 +1,9 @@
/**
* {{{description}}}
* Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^-last}},{{/-last}}{{/enumVars}}{{/allowableValues}}
*/
enum class {{classname}}(val value: {{dataType}}){
{{#allowableValues}}{{#enumVars}}
{{&name}}({{{value}}}){{^-last}},{{/-last}}{{#-last}};{{/-last}}
{{/enumVars}}{{/allowableValues}}
}

View File

@ -0,0 +1,7 @@
# {{classname}}
## Enum
{{#allowableValues}}{{#enumVars}}
* `{{name}}` (value: `{{{value}}}`)
{{/enumVars}}{{/allowableValues}}

View File

@ -0,0 +1,85 @@
package {{packageName}}.infrastructure
import io.ktor.application.ApplicationCall
import io.ktor.application.call
import io.ktor.auth.Authentication
import io.ktor.auth.AuthenticationFailedCause
import io.ktor.auth.AuthenticationPipeline
import io.ktor.auth.AuthenticationProvider
import io.ktor.auth.Credential
import io.ktor.auth.Principal
import io.ktor.auth.UnauthorizedResponse
import io.ktor.http.auth.HeaderValueEncoding
import io.ktor.http.auth.HttpAuthHeader
import io.ktor.request.ApplicationRequest
import io.ktor.response.respond
enum class ApiKeyLocation(val location: String) {
QUERY("query"),
HEADER("header")
}
data class ApiKeyCredential(val value: String): Credential
data class ApiPrincipal(val apiKeyCredential: ApiKeyCredential?) : Principal
/**
* Represents a Api Key authentication provider
* @param name is the name of the provider, or `null` for a default provider
*/
class ApiKeyAuthenticationProvider(name: String?) : AuthenticationProvider(name) {
internal var authenticationFunction: suspend ApplicationCall.(ApiKeyCredential) -> Principal? = { null }
var apiKeyName: String = "";
var apiKeyLocation: ApiKeyLocation = ApiKeyLocation.QUERY;
/**
* Sets a validation function that will check given [ApiKeyCredential] instance and return [Principal],
* or null if credential does not correspond to an authenticated principal
*/
fun validate(body: suspend ApplicationCall.(ApiKeyCredential) -> Principal?) {
authenticationFunction = body
}
}
fun Authentication.Configuration.apiKeyAuth(name: String? = null, configure: ApiKeyAuthenticationProvider.() -> Unit) {
val provider = ApiKeyAuthenticationProvider(name).apply(configure)
val apiKeyName = provider.apiKeyName
val apiKeyLocation = provider.apiKeyLocation
val authenticate = provider.authenticationFunction
provider.pipeline.intercept(AuthenticationPipeline.RequestAuthentication) { context ->
val credentials = call.request.apiKeyAuthenticationCredentials(apiKeyName, apiKeyLocation)
val principal = credentials?.let { authenticate(call, it) }
val cause = when {
credentials == null -> AuthenticationFailedCause.NoCredentials
principal == null -> AuthenticationFailedCause.InvalidCredentials
else -> null
}
if (cause != null) {
context.challenge(apiKeyName, cause) {
// TODO: Verify correct response structure here.
call.respond(UnauthorizedResponse(HttpAuthHeader.Parameterized("API_KEY", mapOf("key" to apiKeyName), HeaderValueEncoding.QUOTED_ALWAYS)))
it.complete()
}
}
if (principal != null) {
context.principal(principal)
}
}
}
fun ApplicationRequest.apiKeyAuthenticationCredentials(apiKeyName: String, apiKeyLocation: ApiKeyLocation): ApiKeyCredential? {
val value: String? = when(apiKeyLocation) {
ApiKeyLocation.QUERY -> this.queryParameters[apiKeyName]
ApiKeyLocation.HEADER -> this.headers[apiKeyName]
}
when (value) {
null -> return null
else -> return ApiKeyCredential(value)
}
}

View File

@ -0,0 +1,145 @@
package {{packageName}}
import com.codahale.metrics.Slf4jReporter
import com.typesafe.config.ConfigFactory
import io.ktor.application.Application
import io.ktor.application.ApplicationStopping
import io.ktor.application.install
import io.ktor.application.log
import io.ktor.client.HttpClient
import io.ktor.client.engine.apache.Apache
import io.ktor.config.HoconApplicationConfig
{{#featureAutoHead}}
import io.ktor.features.AutoHeadResponse
{{/featureAutoHead}}
{{#featureCompression}}
import io.ktor.features.Compression
{{/featureCompression}}
{{#featureCORS}}
import io.ktor.features.CORS
{{/featureCORS}}
{{#featureConditionalHeaders}}
import io.ktor.features.ConditionalHeaders
{{/featureConditionalHeaders}}
import io.ktor.features.ContentNegotiation
import io.ktor.features.DefaultHeaders
{{#featureHSTS}}
import io.ktor.features.HSTS
{{/featureHSTS}}
import io.ktor.gson.GsonConverter
import io.ktor.http.ContentType
import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.locations.Locations
import io.ktor.metrics.Metrics
import io.ktor.routing.Routing
import java.util.concurrent.TimeUnit
import io.ktor.util.KtorExperimentalAPI
{{#hasAuthMethods}}
import io.ktor.auth.Authentication
import io.ktor.auth.oauth
import org.openapitools.server.infrastructure.ApiKeyCredential
import org.openapitools.server.infrastructure.ApiPrincipal
import org.openapitools.server.infrastructure.apiKeyAuth
{{/hasAuthMethods}}
{{#generateApis}}{{#apiInfo}}{{#apis}}import {{apiPackage}}.{{classname}}
{{/apis}}{{/apiInfo}}{{/generateApis}}
@KtorExperimentalAPI
internal val settings = HoconApplicationConfig(ConfigFactory.defaultApplication(HTTP::class.java.classLoader))
object HTTP {
val client = HttpClient(Apache)
}
@KtorExperimentalAPI
@KtorExperimentalLocationsAPI
fun Application.main() {
install(DefaultHeaders)
install(Metrics) {
val reporter = Slf4jReporter.forRegistry(registry)
.outputTo(log)
.convertRatesTo(TimeUnit.SECONDS)
.convertDurationsTo(TimeUnit.MILLISECONDS)
.build()
reporter.start(10, TimeUnit.SECONDS)
}
{{#generateApis}}
install(ContentNegotiation) {
register(ContentType.Application.Json, GsonConverter())
}
{{#featureAutoHead}}
install(AutoHeadResponse) // see http://ktor.io/features/autoheadresponse.html
{{/featureAutoHead}}
{{#featureConditionalHeaders}}
install(ConditionalHeaders) // see http://ktor.io/features/conditional-headers.html
{{/featureConditionalHeaders}}
{{#featureHSTS}}
install(HSTS, ApplicationHstsConfiguration()) // see http://ktor.io/features/hsts.html
{{/featureHSTS}}
{{#featureCORS}}
install(CORS, ApplicationCORSConfiguration()) // see http://ktor.io/features/cors.html
{{/featureCORS}}
{{#featureCompression}}
install(Compression, ApplicationCompressionConfiguration()) // see http://ktor.io/features/compression.html
{{/featureCompression}}
install(Locations) // see http://ktor.io/features/locations.html
{{#hasAuthMethods}}
install(Authentication) {
{{#authMethods}}
{{#isBasic}}
basic("{{{name}}}") {
validate { credentials ->
// TODO: "Apply your basic authentication functionality."
// Accessible in-method via call.principal<UserIdPrincipal>()
if (credentials.name == "Swagger" && "Codegen" == credentials.password) {
UserIdPrincipal(credentials.name)
} else {
null
}
}
{{/isBasic}}
{{#isApiKey}}
// "Implement API key auth ({{{name}}}) for parameter name '{{{keyParamName}}}'."
apiKeyAuth("{{{name}}}") {
validate { apikeyCredential: ApiKeyCredential ->
when {
apikeyCredential.value == "keyboardcat" -> ApiPrincipal(apikeyCredential)
else -> null
}
}
}
{{/isApiKey}}
{{#isOAuth}}
{{#bodyAllowed}}
{{/bodyAllowed}}
{{^bodyAllowed}}
oauth("{{name}}") {
client = HttpClient(Apache)
providerLookup = { ApplicationAuthProviders["{{{name}}}"] }
urlProvider = { _ ->
// TODO: define a callback url here.
"/"
}
}
{{/bodyAllowed}}
{{/isOAuth}}
{{/authMethods}}
}
{{/hasAuthMethods}}
install(Routing) {
{{#apiInfo}}
{{#apis}}
{{#operations}}
{{classname}}()
{{/operations}}
{{/apis}}
{{/apiInfo}}
}
{{/generateApis}}
environment.monitor.subscribe(ApplicationStopping)
{
HTTP.client.close()
}
}

View File

@ -0,0 +1,114 @@
package {{packageName}}
// Use this file to hold package-level internal functions that return receiver object passed to the `install` method.
import io.ktor.auth.OAuthServerSettings
import io.ktor.features.Compression
import io.ktor.features.HSTS
import io.ktor.features.deflate
import io.ktor.features.gzip
import io.ktor.features.minimumSize
import io.ktor.http.HttpMethod
import io.ktor.util.KtorExperimentalAPI
import java.time.Duration
import java.util.concurrent.Executors
import {{packageName}}.settings
{{#featureCORS}}
/**
* Application block for [CORS] configuration.
*
* This file may be excluded in .openapi-generator-ignore,
* and application specific configuration can be applied in this function.
*
* See http://ktor.io/features/cors.html
*/
internal fun ApplicationCORSConfiguration(): CORS.Configuration.() -> Unit {
return {
// method(HttpMethod.Options)
// header(HttpHeaders.XForwardedProto)
// anyHost()
// host("my-host")
// host("my-host:80")
// host("my-host", subDomains = listOf("www"))
// host("my-host", schemes = listOf("http", "https"))
// allowCredentials = true
// maxAge = Duration.ofDays(1)
}
}
{{/featureCORS}}
{{#featureHSTS}}
/**
* Application block for [HSTS] configuration.
*
* This file may be excluded in .openapi-generator-ignore,
* and application specific configuration can be applied in this function.
*
* See http://ktor.io/features/hsts.html
*/
internal fun ApplicationHstsConfiguration(): HSTS.Configuration.() -> Unit {
return {
maxAge = Duration.ofDays(365)
includeSubDomains = true
preload = false
// You may also apply any custom directives supported by specific user-agent. For example:
// customDirectives.put("redirectHttpToHttps", "false")
}
}
{{/featureHSTS}}
{{#featureCompression}}
/**
* Application block for [Compression] configuration.
*
* This file may be excluded in .openapi-generator-ignore,
* and application specific configuration can be applied in this function.
*
* See http://ktor.io/features/compression.html
*/
internal fun ApplicationCompressionConfiguration(): Compression.Configuration.() -> Unit {
return {
gzip {
priority = 1.0
}
deflate {
priority = 10.0
minimumSize(1024) // condition
}
}
}
{{/featureCompression}}
// Defines authentication mechanisms used throughout the application.
@KtorExperimentalAPI
val ApplicationAuthProviders: Map<String, OAuthServerSettings> = listOf<OAuthServerSettings>(
{{#authMethods}}
{{#isOAuth}}
OAuthServerSettings.OAuth2ServerSettings(
name = "{{name}}",
authorizeUrl = "{{authorizationUrl}}",
accessTokenUrl = "{{tokenUrl}}",
requestMethod = HttpMethod.Get,
{{! TODO: flow, doesn't seem to be supported yet by ktor }}
clientId = settings.property("auth.oauth.{{name}}.clientId").getString(),
clientSecret = settings.property("auth.oauth.{{name}}.clientSecret").getString(),
defaultScopes = listOf({{#scopes}}"{{scope}}"{{^-last}}, {{/-last}}{{/scopes}})
){{^-last}},{{/-last}}
{{/isOAuth}}
{{/authMethods}}
// OAuthServerSettings.OAuth2ServerSettings(
// name = "facebook",
// authorizeUrl = "https://graph.facebook.com/oauth/authorize",
// accessTokenUrl = "https://graph.facebook.com/oauth/access_token",
// requestMethod = HttpMethod.Post,
//
// clientId = "settings.property("auth.oauth.facebook.clientId").getString()",
// clientSecret = "settings.property("auth.oauth.facebook.clientSecret").getString()",
// defaultScopes = listOf("public_profile")
// )
).associateBy { it.name }
// Provides an application-level fixed thread pool on which to execute coroutines (mainly)
internal val ApplicationExecutors = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 4)

View File

@ -0,0 +1,7 @@
FROM openjdk:8-jre-alpine
COPY ./build/libs/{{artifactId}}.jar /root/{{artifactId}}.jar
WORKDIR /root
CMD ["java", "-server", "-Xms4g", "-Xmx4g", "-XX:+UseG1GC", "-XX:MaxGCPauseMillis=100", "-XX:+UseStringDeduplication", "-jar", "{{artifactId}}.jar"]

View File

@ -0,0 +1,28 @@
{{>licenseInfo}}
package {{packageName}}
import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.locations.Location
{{#imports}}import {{import}}
{{/imports}}
{{#apiInfo}}
object Paths {
{{#apis}}
{{#operations}}
{{#operation}}
{{^bodyAllowed}}
/**
* {{summary}}
* {{#unescapedNotes}}{{.}}{{/unescapedNotes}}
{{#allParams}}* @param {{paramName}} {{description}} {{^required}}(optional{{#defaultValue}}, default to {{{.}}}{{/defaultValue}}){{/required}}
{{/allParams}}*/
@KtorExperimentalLocationsAPI
@Location("{{path}}") class {{operationId}}({{#allParams}}val {{paramName}}: {{{dataType}}}{{^required}}? = null{{/required}}{{#required}}{{#isNullable}}?{{/isNullable}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}})
{{/bodyAllowed}}
{{/operation}}
{{/operations}}
{{/apis}}
}
{{/apiInfo}}

View File

@ -0,0 +1,101 @@
# {{packageName}} - Kotlin Server library for {{appName}}
{{#unescapedAppDescription}}
{{.}}
{{/unescapedAppDescription}}
Generated by OpenAPI Generator {{generatorVersion}}{{^hideGenerationTimestamp}} ({{generatedDate}}){{/hideGenerationTimestamp}}.
## Requires
* Kotlin 1.3.21
* Gradle 4.9
## Build
First, create the gradle wrapper script:
```
gradle wrapper
```
Then, run:
```
./gradlew check assemble
```
This runs all tests and packages the library.
## Running
The server builds as a fat jar with a main entrypoint. To start the service, run `java -jar ./build/libs/{{artifactId}}.jar`.
You may also run in docker:
```
docker build -t {{artifactId}} .
docker run -p 8080:8080 {{artifactId}}
```
## Features/Implementation Notes
* Supports JSON inputs/outputs, File inputs, and Form inputs (see ktor documentation for more info).
* ~Supports collection formats for query parameters: csv, tsv, ssv, pipes.~
* Some Kotlin and Java types are fully qualified to avoid conflicts with types defined in OpenAPI definitions.
{{#generateApiDocs}}
<a name="documentation-for-api-endpoints"></a>
## Documentation for API Endpoints
All URIs are relative to *{{{basePath}}}*
Class | Method | HTTP request | Description
------------ | ------------- | ------------- | -------------
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}*{{classname}}* | [**{{operationId}}**]({{apiDocPath}}{{classname}}.md#{{operationIdLowerCase}}) | **{{httpMethod}}** {{path}} | {{#summary}}{{{summary}}}{{/summary}}
{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
{{/generateApiDocs}}
{{#generateModelDocs}}
<a name="documentation-for-models"></a>
## Documentation for Models
{{#modelPackage}}
{{#models}}{{#model}} - [{{{modelPackage}}}.{{{classname}}}]({{modelDocPath}}{{{classname}}}.md)
{{/model}}{{/models}}
{{/modelPackage}}
{{^modelPackage}}
No model defined in this package
{{/modelPackage}}
{{/generateModelDocs}}
<a name="documentation-for-authorization"></a>{{! TODO: optional documentation for authorization? }}
## Documentation for Authorization
{{^authMethods}}
All endpoints do not require authorization.
{{/authMethods}}
{{#authMethods}}
{{#last}}
Authentication schemes defined for the API:
{{/last}}
{{/authMethods}}
{{#authMethods}}
<a name="{{name}}"></a>
### {{name}}
{{#isApiKey}}- **Type**: API key
- **API key parameter name**: {{keyParamName}}
- **Location**: {{#isKeyInQuery}}URL query string{{/isKeyInQuery}}{{#isKeyInHeader}}HTTP header{{/isKeyInHeader}}
{{/isApiKey}}
{{#isBasic}}- **Type**: HTTP basic authentication
{{/isBasic}}
{{#isOAuth}}- **Type**: OAuth
- **Flow**: {{flow}}
- **Authorization URL**: {{authorizationUrl}}
- **Scopes**: {{^scopes}}N/A{{/scopes}}
{{#scopes}} - {{scope}}: {{description}}
{{/scopes}}
{{/isOAuth}}
{{/authMethods}}

View File

@ -0,0 +1,25 @@
{{#hasAuthMethods}}
{{>libraries/ktor/_principal}}
if (principal == null) {
call.respond(HttpStatusCode.Unauthorized)
} else {
{{#examples}}
{{#-first}}
{{#lambda.indented}}{{>_response}}{{/lambda.indented}}
{{/-first}}
{{/examples}}
{{^examples}}
call.respond(HttpStatusCode.NotImplemented)
{{/examples}}
}
{{/hasAuthMethods}}
{{^hasAuthMethods}}
{{#examples}}
{{#-first}}
{{>libraries/ktor/_response}}
{{/-first}}
{{/examples}}
{{^examples}}
call.respond(HttpStatusCode.NotImplemented)
{{/examples}}
{{/hasAuthMethods}}

View File

@ -0,0 +1,11 @@
{{#authMethods}}
{{#isBasic}}
val principal = call.authentication.principal<UserIdPrincipal>()
{{/isBasic}}
{{#isApiKey}}
val principal = call.authentication.principal<ApiPrincipal>()
{{/isApiKey}}
{{#isOAuth}}
val principal = call.authentication.principal<OAuthAccessTokenResponse>()
{{/isOAuth}}
{{/authMethods}}

View File

@ -0,0 +1,8 @@
val exampleContentType = "{{{contentType}}}"
val exampleContentString = """{{&example}}"""
when(exampleContentType) {
"application/json" -> call.respond(gson.fromJson(exampleContentString, empty::class.java))
"application/xml" -> call.respondText(exampleContentString, ContentType.Text.Xml)
else -> call.respondText(exampleContentString)
}

View File

@ -0,0 +1,62 @@
{{>licenseInfo}}
package {{apiPackage}}
import com.google.gson.Gson
import io.ktor.application.call
import io.ktor.auth.UserIdPrincipal
import io.ktor.auth.authentication
import io.ktor.auth.authenticate
import io.ktor.auth.OAuthAccessTokenResponse
import io.ktor.auth.OAuthServerSettings
import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode
import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.locations.delete
import io.ktor.locations.get
import io.ktor.response.respond
import io.ktor.response.respondText
import io.ktor.routing.Route
import io.ktor.routing.post
import io.ktor.routing.put
import io.ktor.routing.route
import {{packageName}}.Paths
import {{packageName}}.infrastructure.ApiPrincipal
{{#imports}}import {{import}}
{{/imports}}
{{#operations}}
@KtorExperimentalLocationsAPI
fun Route.{{classname}}() {
val gson = Gson()
val empty = mutableMapOf<String, Any?>()
{{#operation}}
{{#bodyAllowed}}
route("{{path}}") {
{{#hasAuthMethods}}
{{#authMethods}}
authenticate("{{{name}}}") {
{{/authMethods}}
{{/hasAuthMethods}}
{{#lambda.lowercase}}{{httpMethod}}{{/lambda.lowercase}} {
{{#lambda.indented_12}}{{>libraries/ktor/_api_body}}{{/lambda.indented_12}}
}
{{#hasAuthMethods}}
}
{{/hasAuthMethods}}
}
{{/bodyAllowed}}
{{^bodyAllowed}}
{{! NOTE: Locations can be used on routes without body parameters.}}
{{#lambda.lowercase}}{{httpMethod}}{{/lambda.lowercase}}<Paths.{{operationId}}> { _: Paths.{{operationId}} ->
{{#lambda.indented_8}}{{>libraries/ktor/_api_body}}{{/lambda.indented_8}}
}
{{/bodyAllowed}}
{{/operation}}
}
{{/operations}}

View File

@ -0,0 +1,27 @@
ktor {
deployment {
environment = development
port = 8080
autoreload = true
watch = [ {{packageName}} ]
}
application {
modules = [ {{packageName}}.AppMainKt.main ]
}
}
# Typesafe config allows multiple ways to provide configuration values without hard-coding them here.
# Please see https://github.com/lightbend/config for details.
auth {
oauth {
{{#authMethods}}
{{#isOAuth}}
{{name}} {
clientId = ""
clientSecret = ""
}
{{/isOAuth}}
{{/authMethods}}
}
}

View File

@ -0,0 +1,68 @@
group '{{groupId}}'
version '{{artifactVersion}}'
wrapper {
gradleVersion = '4.9'
distributionUrl = "https://services.gradle.org/distributions/gradle-$gradleVersion-all.zip"
}
buildscript {
ext.kotlin_version = '1.3.21'
ext.ktor_version = '1.1.3'
ext.shadow_version = '2.0.3'
repositories {
maven { url "https://repo1.maven.org/maven2" }
maven {
url = "https://plugins.gradle.org/m2/"
}
}
dependencies {
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "com.github.jengelman.gradle.plugins:shadow:$shadow_version"
}
}
apply plugin: 'java'
apply plugin: 'kotlin'
apply plugin: 'application'
mainClassName = "io.ktor.server.netty.DevelopmentEngine"
// Initialization order with shadow 2.0.1 and Gradle 4.3 is weird.
// See https://github.com/johnrengelman/shadow/issues/336#issuecomment-355402508
apply plugin: 'com.github.johnrengelman.shadow'
sourceCompatibility = 1.8
compileKotlin {
kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
kotlinOptions.jvmTarget = "1.8"
}
shadowJar {
baseName = '{{artifactId}}'
classifier = null
version = null
}
repositories {
maven { url "https://repo1.maven.org/maven2" }
maven { url "https://dl.bintray.com/kotlin/ktor" }
maven { url "https://dl.bintray.com/kotlin/kotlinx" }
}
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile "io.ktor:ktor-server-netty:$ktor_version"
compile "io.ktor:ktor-metrics:$ktor_version"
compile "io.ktor:ktor-locations:$ktor_version"
compile "io.ktor:ktor-gson:$ktor_version"
compile "io.ktor:ktor-client-core:$ktor_version"
compile "io.ktor:ktor-client-apache:$ktor_version"
compile "ch.qos.logback:logback-classic:1.2.1"
testCompile group: 'junit', name: 'junit', version: '4.13'
}

View File

@ -0,0 +1,11 @@
/**
* {{{appName}}}
* {{{appDescription}}}
*
* {{#version}}The version of the OpenAPI document: {{{version}}}{{/version}}
* {{#infoEmail}}Contact: {{{infoEmail}}}{{/infoEmail}}
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/

View File

@ -0,0 +1,15 @@
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{YYYY-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n</pattern>
</encoder>
</appender>
<root level="trace">
<appender-ref ref="STDOUT"/>
</root>
<logger name="org.eclipse.jetty" level="INFO"/>
<logger name="io.netty" level="INFO"/>
</configuration>

View File

@ -0,0 +1,11 @@
/**
* {{{appName}}}
* {{{appDescription}}}
*
* {{#version}}The version of the OpenAPI document: {{{version}}}{{/version}}
* {{#infoEmail}}Contact: {{{infoEmail}}}{{/infoEmail}}
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/

View File

@ -0,0 +1,11 @@
{{>licenseInfo}}
package {{modelPackage}}
{{#imports}}import {{import}}
{{/imports}}
{{#models}}
{{#model}}
{{#isEnum}}{{>enum_class}}{{/isEnum}}{{^isEnum}}{{>data_class}}{{/isEnum}}
{{/model}}
{{/models}}

View File

@ -0,0 +1 @@
{{#modelMutable}}var{{/modelMutable}}{{^modelMutable}}val{{/modelMutable}}

View File

@ -0,0 +1,3 @@
{{#models}}{{#model}}
{{#isEnum}}{{>enum_doc}}{{/isEnum}}{{^isEnum}}{{>class_doc}}{{/isEnum}}
{{/model}}{{/models}}

View File

@ -0,0 +1 @@
rootProject.name = '{{artifactId}}'

View File

@ -2,8 +2,8 @@
## Requires
* Kotlin 1.1.2
* Gradle 3.3
* Kotlin 1.4.31
* Gradle 6.8.2
## Build

View File

@ -15,7 +15,7 @@ import java.io.Serializable
{{#parcelizeModels}}
@Parcelize
{{/parcelizeModels}}
data class {{classname}} (
data class {{classname}}(
{{#requiredVars}}
{{>data_class_req_var}}{{^-last}},
{{/-last}}{{/requiredVars}}{{#hasRequired}}{{#hasOptional}},

View File

@ -2,7 +2,7 @@
* {{{description}}}
* Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^-last}},{{/-last}}{{/enumVars}}{{/allowableValues}}
*/
enum class {{classname}}(val value: {{dataType}}){
enum class {{classname}}(val value: {{dataType}}) {
{{#allowableValues}}{{#enumVars}}
{{&name}}({{{value}}}){{^-last}},{{/-last}}{{#-last}};{{/-last}}
{{/enumVars}}{{/allowableValues}}

View File

@ -1,85 +1,105 @@
package {{packageName}}.infrastructure
import io.ktor.application.ApplicationCall
import io.ktor.application.call
import io.ktor.auth.Authentication
import io.ktor.auth.AuthenticationFailedCause
import io.ktor.auth.AuthenticationPipeline
import io.ktor.auth.AuthenticationProvider
import io.ktor.auth.Credential
import io.ktor.auth.Principal
import io.ktor.auth.UnauthorizedResponse
import io.ktor.http.auth.HeaderValueEncoding
import io.ktor.http.auth.HttpAuthHeader
import io.ktor.request.ApplicationRequest
import io.ktor.response.respond
import io.ktor.application.*
import io.ktor.auth.*
import io.ktor.http.auth.*
import io.ktor.request.*
import io.ktor.response.*
enum class ApiKeyLocation(val location: String) {
QUERY("query"),
HEADER("header")
}
data class ApiKeyCredential(val value: String): Credential
data class ApiKeyCredential(val value: String) : Credential
data class ApiPrincipal(val apiKeyCredential: ApiKeyCredential?) : Principal
/**
* Represents a Api Key authentication provider
* @param name is the name of the provider, or `null` for a default provider
*/
class ApiKeyAuthenticationProvider(name: String?) : AuthenticationProvider(name) {
internal var authenticationFunction: suspend ApplicationCall.(ApiKeyCredential) -> Principal? = { null }
class ApiKeyAuthenticationProvider(configuration: Configuration) : AuthenticationProvider(configuration) {
var apiKeyName: String = "";
private val authenticationFunction = configuration.authenticationFunction
var apiKeyLocation: ApiKeyLocation = ApiKeyLocation.QUERY;
private val apiKeyName: String = configuration.apiKeyName
/**
* Sets a validation function that will check given [ApiKeyCredential] instance and return [Principal],
* or null if credential does not correspond to an authenticated principal
*/
fun validate(body: suspend ApplicationCall.(ApiKeyCredential) -> Principal?) {
authenticationFunction = body
}
}
private val apiKeyLocation: ApiKeyLocation = configuration.apiKeyLocation
fun Authentication.Configuration.apiKeyAuth(name: String? = null, configure: ApiKeyAuthenticationProvider.() -> Unit) {
val provider = ApiKeyAuthenticationProvider(name).apply(configure)
val apiKeyName = provider.apiKeyName
val apiKeyLocation = provider.apiKeyLocation
val authenticate = provider.authenticationFunction
internal fun install() {
pipeline.intercept(AuthenticationPipeline.RequestAuthentication) { context ->
val credentials = call.request.apiKeyAuthenticationCredentials(apiKeyName, apiKeyLocation)
val principal = credentials?.let { authenticationFunction(call, it) }
provider.pipeline.intercept(AuthenticationPipeline.RequestAuthentication) { context ->
val credentials = call.request.apiKeyAuthenticationCredentials(apiKeyName, apiKeyLocation)
val principal = credentials?.let { authenticate(call, it) }
val cause = when {
credentials == null -> AuthenticationFailedCause.NoCredentials
principal == null -> AuthenticationFailedCause.InvalidCredentials
else -> null
}
val cause = when {
credentials == null -> AuthenticationFailedCause.NoCredentials
principal == null -> AuthenticationFailedCause.InvalidCredentials
else -> null
}
if (cause != null) {
context.challenge(apiKeyName, cause) {
call.respond(
UnauthorizedResponse(
HttpAuthHeader.Parameterized(
"API_KEY",
mapOf("key" to apiKeyName),
HeaderValueEncoding.QUOTED_ALWAYS
)
)
)
it.complete()
}
}
if (cause != null) {
context.challenge(apiKeyName, cause) {
// TODO: Verify correct response structure here.
call.respond(UnauthorizedResponse(HttpAuthHeader.Parameterized("API_KEY", mapOf("key" to apiKeyName), HeaderValueEncoding.QUOTED_ALWAYS)))
it.complete()
if (principal != null) {
context.principal(principal)
}
}
}
if (principal != null) {
context.principal(principal)
class Configuration internal constructor(name: String?) : AuthenticationProvider.Configuration(name) {
internal var authenticationFunction: suspend ApplicationCall.(ApiKeyCredential) -> Principal? = {
throw NotImplementedError(
"Api Key auth validate function is not specified. Use apiKeyAuth { validate { ... } } to fix."
)
}
var apiKeyName: String = ""
var apiKeyLocation: ApiKeyLocation = ApiKeyLocation.QUERY
/**
* Sets a validation function that will check given [ApiKeyCredential] instance and return [Principal],
* or null if credential does not correspond to an authenticated principal
*/
fun validate(body: suspend ApplicationCall.(ApiKeyCredential) -> Principal?) {
authenticationFunction = body
}
}
}
fun ApplicationRequest.apiKeyAuthenticationCredentials(apiKeyName: String, apiKeyLocation: ApiKeyLocation): ApiKeyCredential? {
val value: String? = when(apiKeyLocation) {
fun Authentication.Configuration.apiKeyAuth(
name: String? = null,
configure: ApiKeyAuthenticationProvider.Configuration.() -> Unit
) {
val configuration = ApiKeyAuthenticationProvider.Configuration(name).apply(configure)
val provider = ApiKeyAuthenticationProvider(configuration)
provider.install()
register(provider)
}
fun ApplicationRequest.apiKeyAuthenticationCredentials(
apiKeyName: String,
apiKeyLocation: ApiKeyLocation
): ApiKeyCredential? {
val value: String? = when (apiKeyLocation) {
ApiKeyLocation.QUERY -> this.queryParameters[apiKeyName]
ApiKeyLocation.HEADER -> this.headers[apiKeyName]
}
when (value) {
null -> return null
else -> return ApiKeyCredential(value)
return when (value) {
null -> null
else -> ApiKeyCredential(value)
}
}

View File

@ -28,15 +28,16 @@ import io.ktor.features.HSTS
{{/featureHSTS}}
import io.ktor.gson.GsonConverter
import io.ktor.http.ContentType
{{#featureLocations}}
import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.locations.Locations
import io.ktor.metrics.Metrics
{{/featureLocations}}
import io.ktor.routing.Routing
import java.util.concurrent.TimeUnit
import io.ktor.util.KtorExperimentalAPI
{{#hasAuthMethods}}
import io.ktor.auth.Authentication
import io.ktor.auth.oauth
import io.ktor.metrics.dropwizard.DropwizardMetrics
import org.openapitools.server.infrastructure.ApiKeyCredential
import org.openapitools.server.infrastructure.ApiPrincipal
import org.openapitools.server.infrastructure.apiKeyAuth
@ -44,23 +45,23 @@ import org.openapitools.server.infrastructure.apiKeyAuth
{{#generateApis}}{{#apiInfo}}{{#apis}}import {{apiPackage}}.{{classname}}
{{/apis}}{{/apiInfo}}{{/generateApis}}
@KtorExperimentalAPI
internal val settings = HoconApplicationConfig(ConfigFactory.defaultApplication(HTTP::class.java.classLoader))
object HTTP {
val client = HttpClient(Apache)
}
@KtorExperimentalAPI
{{#featureLocations}}
@KtorExperimentalLocationsAPI
{{/featureLocations}}
fun Application.main() {
install(DefaultHeaders)
install(Metrics) {
install(DropwizardMetrics) {
val reporter = Slf4jReporter.forRegistry(registry)
.outputTo(log)
.convertRatesTo(TimeUnit.SECONDS)
.convertDurationsTo(TimeUnit.MILLISECONDS)
.build()
.outputTo(log)
.convertRatesTo(TimeUnit.SECONDS)
.convertDurationsTo(TimeUnit.MILLISECONDS)
.build()
reporter.start(10, TimeUnit.SECONDS)
}
{{#generateApis}}
@ -82,7 +83,9 @@ fun Application.main() {
{{#featureCompression}}
install(Compression, ApplicationCompressionConfiguration()) // see http://ktor.io/features/compression.html
{{/featureCompression}}
{{#featureLocations}}
install(Locations) // see http://ktor.io/features/locations.html
{{/featureLocations}}
{{#hasAuthMethods}}
install(Authentication) {
{{#authMethods}}
@ -117,8 +120,8 @@ fun Application.main() {
client = HttpClient(Apache)
providerLookup = { ApplicationAuthProviders["{{{name}}}"] }
urlProvider = { _ ->
// TODO: define a callback url here.
"/"
// TODO: define a callback url here.
"/"
}
}
{{/bodyAllowed}}
@ -138,8 +141,7 @@ fun Application.main() {
{{/generateApis}}
environment.monitor.subscribe(ApplicationStopping)
{
environment.monitor.subscribe(ApplicationStopping) {
HTTP.client.close()
}
}

View File

@ -8,11 +8,7 @@ import io.ktor.features.deflate
import io.ktor.features.gzip
import io.ktor.features.minimumSize
import io.ktor.http.HttpMethod
import io.ktor.util.KtorExperimentalAPI
import java.time.Duration
import java.util.concurrent.Executors
import {{packageName}}.settings
import java.util.concurrent.TimeUnit
{{#featureCORS}}
/**
@ -49,7 +45,7 @@ internal fun ApplicationCORSConfiguration(): CORS.Configuration.() -> Unit {
*/
internal fun ApplicationHstsConfiguration(): HSTS.Configuration.() -> Unit {
return {
maxAge = Duration.ofDays(365)
maxAgeInSeconds = TimeUnit.DAYS.toSeconds(365)
includeSubDomains = true
preload = false
@ -82,7 +78,6 @@ internal fun ApplicationCompressionConfiguration(): Compression.Configuration.()
{{/featureCompression}}
// Defines authentication mechanisms used throughout the application.
@KtorExperimentalAPI
val ApplicationAuthProviders: Map<String, OAuthServerSettings> = listOf<OAuthServerSettings>(
{{#authMethods}}
{{#isOAuth}}
@ -109,6 +104,3 @@ val ApplicationAuthProviders: Map<String, OAuthServerSettings> = listOf<OAuthSer
// defaultScopes = listOf("public_profile")
// )
).associateBy { it.name }
// Provides an application-level fixed thread pool on which to execute coroutines (mainly)
internal val ApplicationExecutors = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 4)

View File

@ -3,6 +3,7 @@ package {{packageName}}
import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.locations.Location
import {{packageName}}.models.*
{{#imports}}import {{import}}
{{/imports}}
@ -11,7 +12,6 @@ object Paths {
{{#apis}}
{{#operations}}
{{#operation}}
{{^bodyAllowed}}
/**
* {{summary}}
* {{#unescapedNotes}}{{.}}{{/unescapedNotes}}
@ -20,7 +20,6 @@ object Paths {
@KtorExperimentalLocationsAPI
@Location("{{path}}") class {{operationId}}({{#allParams}}val {{paramName}}: {{{dataType}}}{{^required}}? = null{{/required}}{{#required}}{{#isNullable}}?{{/isNullable}}{{/required}}{{^-last}}, {{/-last}}{{/allParams}})
{{/bodyAllowed}}
{{/operation}}
{{/operations}}
{{/apis}}

View File

@ -8,8 +8,8 @@ Generated by OpenAPI Generator {{generatorVersion}}{{^hideGenerationTimestamp}}
## Requires
* Kotlin 1.3.21
* Gradle 4.9
* Kotlin 1.4.31
* Gradle 6.8.2
## Build

View File

@ -1,17 +1,13 @@
{{#hasAuthMethods}}
{{>libraries/ktor/_principal}}
if (principal == null) {
call.respond(HttpStatusCode.Unauthorized)
} else {
{{#examples}}
{{#-first}}
{{#lambda.indented}}{{>_response}}{{/lambda.indented}}
{{/-first}}
{{/examples}}
{{^examples}}
call.respond(HttpStatusCode.NotImplemented)
{{/examples}}
}
{{#examples}}
{{#-first}}
{{#lambda.indented}}{{>_response}}{{/lambda.indented}}
{{/-first}}
{{/examples}}
{{^examples}}
call.respond(HttpStatusCode.NotImplemented)
{{/examples}}
{{/hasAuthMethods}}
{{^hasAuthMethods}}
{{#examples}}

View File

@ -1,11 +1,11 @@
{{#authMethods}}
{{#isBasic}}
val principal = call.authentication.principal<UserIdPrincipal>()
val principal = call.authentication.principal<UserIdPrincipal>()!!
{{/isBasic}}
{{#isApiKey}}
val principal = call.authentication.principal<ApiPrincipal>()
val principal = call.authentication.principal<ApiPrincipal>()!!
{{/isApiKey}}
{{#isOAuth}}
val principal = call.authentication.principal<OAuthAccessTokenResponse>()
val principal = call.authentication.principal<OAuthAccessTokenResponse>()!!
{{/isOAuth}}
{{/authMethods}}

View File

@ -1,7 +1,7 @@
val exampleContentType = "{{{contentType}}}"
val exampleContentString = """{{&example}}"""
when(exampleContentType) {
when (exampleContentType) {
"application/json" -> call.respond(gson.fromJson(exampleContentString, empty::class.java))
"application/xml" -> call.respondText(exampleContentString, ContentType.Text.Xml)
else -> call.respondText(exampleContentString)

View File

@ -10,17 +10,29 @@ import io.ktor.auth.OAuthAccessTokenResponse
import io.ktor.auth.OAuthServerSettings
import io.ktor.http.ContentType
import io.ktor.http.HttpStatusCode
import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.locations.delete
import io.ktor.locations.get
import io.ktor.response.respond
import io.ktor.response.respondText
import io.ktor.routing.Route
{{#featureLocations}}
import {{packageName}}.Paths
import io.ktor.locations.KtorExperimentalLocationsAPI
import io.ktor.locations.delete
import io.ktor.locations.get
import io.ktor.locations.post
import io.ktor.locations.put
import io.ktor.locations.options
import io.ktor.locations.head
{{/featureLocations}}
{{^featureLocations}}
import io.ktor.routing.delete
import io.ktor.routing.get
import io.ktor.routing.post
import io.ktor.routing.put
import io.ktor.routing.options
import io.ktor.routing.head
import io.ktor.routing.route
{{/featureLocations}}
import {{packageName}}.Paths
import {{packageName}}.infrastructure.ApiPrincipal
@ -28,34 +40,33 @@ import {{packageName}}.infrastructure.ApiPrincipal
{{/imports}}
{{#operations}}
{{#featureLocations}}
@KtorExperimentalLocationsAPI
{{/featureLocations}}
fun Route.{{classname}}() {
val gson = Gson()
val empty = mutableMapOf<String, Any?>()
{{#operation}}
{{#bodyAllowed}}
{{#hasAuthMethods}}
{{#authMethods}}
authenticate("{{{name}}}") {
{{/authMethods}}
{{/hasAuthMethods}}
{{^featureLocations}}
route("{{path}}") {
{{#hasAuthMethods}}
{{#authMethods}}
authenticate("{{{name}}}") {
{{/authMethods}}
{{/hasAuthMethods}}
{{#lambda.lowercase}}{{httpMethod}}{{/lambda.lowercase}} {
{{#lambda.indented_12}}{{>libraries/ktor/_api_body}}{{/lambda.indented_12}}
}
{{#hasAuthMethods}}
}
{{/hasAuthMethods}}
}
{{/bodyAllowed}}
{{^bodyAllowed}}
{{! NOTE: Locations can be used on routes without body parameters.}}
{{#lambda.lowercase}}{{httpMethod}}{{/lambda.lowercase}}<Paths.{{operationId}}> { _: Paths.{{operationId}} ->
{{/featureLocations}}
{{#featureLocations}}
{{#lambda.lowercase}}{{httpMethod}}{{/lambda.lowercase}}<Paths.{{operationId}}> {
{{#lambda.indented_8}}{{>libraries/ktor/_api_body}}{{/lambda.indented_8}}
}
{{/bodyAllowed}}
{{/featureLocations}}
{{#hasAuthMethods}}
}
{{/hasAuthMethods}}
{{/operation}}
}

View File

@ -2,13 +2,13 @@ group '{{groupId}}'
version '{{artifactVersion}}'
wrapper {
gradleVersion = '4.9'
gradleVersion = '6.8.2'
distributionUrl = "https://services.gradle.org/distributions/gradle-$gradleVersion-all.zip"
}
buildscript {
ext.kotlin_version = '1.3.21'
ext.ktor_version = '1.1.3'
ext.kotlin_version = '1.4.31'
ext.ktor_version = '1.5.2'
ext.shadow_version = '2.0.3'
repositories {
@ -29,7 +29,7 @@ apply plugin: 'application'
mainClassName = "io.ktor.server.netty.DevelopmentEngine"
// Initialization order with shadow 2.0.1 and Gradle 4.3 is weird.
// Initialization order with shadow 2.0.1 and Gradle 6.8.2 is weird.
// See https://github.com/johnrengelman/shadow/issues/336#issuecomment-355402508
apply plugin: 'com.github.johnrengelman.shadow'
@ -59,7 +59,10 @@ dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile "io.ktor:ktor-server-netty:$ktor_version"
compile "io.ktor:ktor-metrics:$ktor_version"
compile "io.ktor:ktor-auth:$ktor_version"
{{#featureLocations}}
compile "io.ktor:ktor-locations:$ktor_version"
{{/featureLocations}}
compile "io.ktor:ktor-gson:$ktor_version"
compile "io.ktor:ktor-client-core:$ktor_version"
compile "io.ktor:ktor-client-apache:$ktor_version"

View File

@ -1,11 +1,11 @@
/**
* {{{appName}}}
* {{{appDescription}}}
*
* {{#version}}The version of the OpenAPI document: {{{version}}}{{/version}}
* {{#infoEmail}}Contact: {{{infoEmail}}}{{/infoEmail}}
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
* {{{appName}}}
* {{{appDescription}}}
*
* {{#version}}The version of the OpenAPI document: {{{version}}}{{/version}}
* {{#infoEmail}}Contact: {{{infoEmail}}}{{/infoEmail}}
*
* NOTE: This class is auto generated by OpenAPI Generator (https://openapi-generator.tech).
* https://openapi-generator.tech
* Do not edit the class manually.
*/

Some files were not shown because too many files have changed in this diff Show More