diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index cf1c0bfefa1..634ba51b9bd 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -7,5 +7,5 @@
- [ ] Pull Request title clearly describes the work in the pull request and Pull Request description provides details about how to validate the work. Missing information here may result in delayed response from the community.
- [ ] If contributing template-only or documentation-only changes which will change sample output, [build the project](https://github.com/OpenAPITools/openapi-generator#14---build-projects) beforehand.
- [ ] Run the shell script `./bin/generate-samples.sh`to update all Petstore samples related to your fix. This is important, as CI jobs will verify _all_ generator outputs of your HEAD commit as it would merge with master. These must match the expectations made by your contribution. You may regenerate an individual generator by passing the relevant config(s) as an argument to the script, for example `./bin/generate-samples.sh bin/configs/java*`. For Windows users, please run the script in [Git BASH](https://gitforwindows.org/).
-- [ ] File the PR against the [correct branch](https://github.com/OpenAPITools/openapi-generator/wiki/Git-Branches): `master`
+- [ ] File the PR against the [correct branch](https://github.com/OpenAPITools/openapi-generator/wiki/Git-Branches): `master`, `5.1.x`, `6.0.x`
- [ ] Copy the [technical committee](https://github.com/openapitools/openapi-generator/#62---openapi-generator-technical-committee) to review the pull request if your PR is targeting a particular programming language.
diff --git a/.gitignore b/.gitignore
index 26c01a81696..ebe0dc1ff9c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -258,3 +258,6 @@ samples/client/petstore/c/*.so
# Ruby
samples/openapi3/client/petstore/ruby/Gemfile.lock
samples/openapi3/client/petstore/ruby-faraday/Gemfile.lock
+
+# Crystal
+samples/client/petstore/crystal/lib
diff --git a/.mvn/wrapper/MavenWrapperDownloader.java b/.mvn/wrapper/MavenWrapperDownloader.java
new file mode 100644
index 00000000000..b901097f2db
--- /dev/null
+++ b/.mvn/wrapper/MavenWrapperDownloader.java
@@ -0,0 +1,117 @@
+/*
+ * Copyright 2007-present the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import java.net.*;
+import java.io.*;
+import java.nio.channels.*;
+import java.util.Properties;
+
+public class MavenWrapperDownloader {
+
+ private static final String WRAPPER_VERSION = "0.5.6";
+ /**
+ * Default URL to download the maven-wrapper.jar from, if no 'downloadUrl' is provided.
+ */
+ private static final String DEFAULT_DOWNLOAD_URL = "https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/"
+ + WRAPPER_VERSION + "/maven-wrapper-" + WRAPPER_VERSION + ".jar";
+
+ /**
+ * Path to the maven-wrapper.properties file, which might contain a downloadUrl property to
+ * use instead of the default one.
+ */
+ private static final String MAVEN_WRAPPER_PROPERTIES_PATH =
+ ".mvn/wrapper/maven-wrapper.properties";
+
+ /**
+ * Path where the maven-wrapper.jar will be saved to.
+ */
+ private static final String MAVEN_WRAPPER_JAR_PATH =
+ ".mvn/wrapper/maven-wrapper.jar";
+
+ /**
+ * Name of the property which should be used to override the default download url for the wrapper.
+ */
+ private static final String PROPERTY_NAME_WRAPPER_URL = "wrapperUrl";
+
+ public static void main(String args[]) {
+ System.out.println("- Downloader started");
+ File baseDirectory = new File(args[0]);
+ System.out.println("- Using base directory: " + baseDirectory.getAbsolutePath());
+
+ // If the maven-wrapper.properties exists, read it and check if it contains a custom
+ // wrapperUrl parameter.
+ File mavenWrapperPropertyFile = new File(baseDirectory, MAVEN_WRAPPER_PROPERTIES_PATH);
+ String url = DEFAULT_DOWNLOAD_URL;
+ if(mavenWrapperPropertyFile.exists()) {
+ FileInputStream mavenWrapperPropertyFileInputStream = null;
+ try {
+ mavenWrapperPropertyFileInputStream = new FileInputStream(mavenWrapperPropertyFile);
+ Properties mavenWrapperProperties = new Properties();
+ mavenWrapperProperties.load(mavenWrapperPropertyFileInputStream);
+ url = mavenWrapperProperties.getProperty(PROPERTY_NAME_WRAPPER_URL, url);
+ } catch (IOException e) {
+ System.out.println("- ERROR loading '" + MAVEN_WRAPPER_PROPERTIES_PATH + "'");
+ } finally {
+ try {
+ if(mavenWrapperPropertyFileInputStream != null) {
+ mavenWrapperPropertyFileInputStream.close();
+ }
+ } catch (IOException e) {
+ // Ignore ...
+ }
+ }
+ }
+ System.out.println("- Downloading from: " + url);
+
+ File outputFile = new File(baseDirectory.getAbsolutePath(), MAVEN_WRAPPER_JAR_PATH);
+ if(!outputFile.getParentFile().exists()) {
+ if(!outputFile.getParentFile().mkdirs()) {
+ System.out.println(
+ "- ERROR creating output directory '" + outputFile.getParentFile().getAbsolutePath() + "'");
+ }
+ }
+ System.out.println("- Downloading to: " + outputFile.getAbsolutePath());
+ try {
+ downloadFileFromURL(url, outputFile);
+ System.out.println("Done");
+ System.exit(0);
+ } catch (Throwable e) {
+ System.out.println("- Error downloading");
+ e.printStackTrace();
+ System.exit(1);
+ }
+ }
+
+ private static void downloadFileFromURL(String urlString, File destination) throws Exception {
+ if (System.getenv("MVNW_USERNAME") != null && System.getenv("MVNW_PASSWORD") != null) {
+ String username = System.getenv("MVNW_USERNAME");
+ char[] password = System.getenv("MVNW_PASSWORD").toCharArray();
+ Authenticator.setDefault(new Authenticator() {
+ @Override
+ protected PasswordAuthentication getPasswordAuthentication() {
+ return new PasswordAuthentication(username, password);
+ }
+ });
+ }
+ URL website = new URL(urlString);
+ ReadableByteChannel rbc;
+ rbc = Channels.newChannel(website.openStream());
+ FileOutputStream fos = new FileOutputStream(destination);
+ fos.getChannel().transferFrom(rbc, 0, Long.MAX_VALUE);
+ fos.close();
+ rbc.close();
+ }
+
+}
diff --git a/.mvn/wrapper/maven-wrapper.jar b/.mvn/wrapper/maven-wrapper.jar
index f775b1c04cf..2cc7d4a55c0 100755
Binary files a/.mvn/wrapper/maven-wrapper.jar and b/.mvn/wrapper/maven-wrapper.jar differ
diff --git a/.mvn/wrapper/maven-wrapper.properties b/.mvn/wrapper/maven-wrapper.properties
index eb91947648c..642d572ce90 100755
--- a/.mvn/wrapper/maven-wrapper.properties
+++ b/.mvn/wrapper/maven-wrapper.properties
@@ -1 +1,2 @@
-distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.3.3/apache-maven-3.3.3-bin.zip
\ No newline at end of file
+distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.6.3/apache-maven-3.6.3-bin.zip
+wrapperUrl=https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar
diff --git a/.travis.yml b/.travis.yml
index 52ad4a03ce7..edbd8c27dd5 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -62,6 +62,17 @@ addons:
- petstore.swagger.io
before_install:
+ # to run petstore server locally via docker
+ - echo "$DOCKER_HUB_PASSWORD" | docker login --username=$DOCKER_HUB_USERNAME --password-stdin || true
+ - docker pull swaggerapi/petstore
+ - docker run -d -e SWAGGER_HOST=http://petstore.swagger.io -e SWAGGER_BASE_PATH=/v2 -p 80:8080 swaggerapi/petstore
+ - docker ps -a
+ # install crystal
+ - curl -sSL https://dist.crystal-lang.org/apt/setup.sh | sudo bash
+ - curl -sL "https://keybase.io/crystal/pgp_keys.asc" | sudo apt-key add -
+ - echo "deb https://dist.crystal-lang.org/apt crystal main" | sudo tee /etc/apt/sources.list.d/crystal.list
+ - sudo apt-get update
+ - sudo apt install crystal
- curl -o- -L https://yarnpkg.com/install.sh | bash -s -- --version 1.22.0
- export PATH="$HOME/.yarn/bin:$PATH"
# install rust
@@ -75,10 +86,6 @@ before_install:
- npm config set registry http://registry.npmjs.org/
# set python 3.6.3 as default
- source ~/virtualenv/python3.6/bin/activate
- # to run petstore server locally via docker
- - docker pull swaggerapi/petstore
- - docker run -d -e SWAGGER_HOST=http://petstore.swagger.io -e SWAGGER_BASE_PATH=/v2 -p 80:8080 swaggerapi/petstore
- - docker ps -a
# -- skip bash test to shorten build time
# Add bats test framework and cURL for Bash script integration tests
#- sudo add-apt-repository ppa:duggan/bats --yes
@@ -124,7 +131,7 @@ before_install:
fi;
- pushd .; cd website; yarn install; popd
# install Deno
- - sh -s v1.1.2 < ./CI/deno_install.sh
+ - sh -s v1.6.2 < ./CI/deno_install.sh
- export PATH="$HOME/.deno/bin:$PATH"
install:
diff --git a/CI/circle_parallel.sh b/CI/circle_parallel.sh
index 70b541743d8..25705a08528 100755
--- a/CI/circle_parallel.sh
+++ b/CI/circle_parallel.sh
@@ -41,13 +41,14 @@ elif [ "$NODE_INDEX" = "2" ]; then
curl -sSL https://get.haskellstack.org/ | sh
stack upgrade
stack --version
- # install r
+ # prepare r
sudo sh -c 'echo "deb http://cran.rstudio.com/bin/linux/ubuntu trusty/" >> /etc/apt/sources.list'
gpg --keyserver keyserver.ubuntu.com --recv-key E084DAB9
gpg -a --export E084DAB9 | sudo apt-key add -
sudo apt-get update
sudo apt-get -y install r-base
R --version
+
# install curl
sudo apt-get -y build-dep libcurl4-gnutls-dev
sudo apt-get -y install libcurl4-gnutls-dev
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 9644a5cb366..30258ec9fd6 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -49,6 +49,7 @@ Code change should conform to the programming style guide of the respective lang
- 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
- 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
- Elixir: https://github.com/christopheradams/elixir_style_guide
- Eiffel: https://www.eiffel.org/doc/eiffel/Coding%20Standards
diff --git a/README.md b/README.md
index 48e5a79fdb2..7ea36a530a8 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@
-[Master](https://github.com/OpenAPITools/openapi-generator/tree/master) (`5.0.0`):
+[Master](https://github.com/OpenAPITools/openapi-generator/tree/master) (`5.0.1`):
[](https://travis-ci.org/OpenAPITools/openapi-generator)
[](https://circleci.com/gh/OpenAPITools/openapi-generator)
[](https://app.shippable.com/github/OpenAPITools/openapi-generator)
@@ -18,6 +18,22 @@
[](https://app.bitrise.io/app/4a2b10a819d12b67)
[](https://github.com/OpenAPITools/openapi-generator/actions?query=workflow%3A%22Check+Supported+Java+Versions%22)
+[5.1.x](https://github.com/OpenAPITools/openapi-generator/tree/5.1.x) (`5.1.x`):
+[](https://travis-ci.org/OpenAPITools/openapi-generator)
+[](https://circleci.com/gh/OpenAPITools/openapi-generator)
+[](https://app.shippable.com/github/OpenAPITools/openapi-generator)
+[](https://ci.appveyor.com/project/WilliamCheng/openapi-generator-wh2wu)
+[](https://cloud.drone.io/OpenAPITools/openapi-generator)
+[](https://app.bitrise.io/app/4a2b10a819d12b67)
+
+[6.0.x](https://github.com/OpenAPITools/openapi-generator/tree/6.0.x) (`6.0.x`):
+[](https://travis-ci.org/OpenAPITools/openapi-generator)
+[](https://circleci.com/gh/OpenAPITools/openapi-generator)
+[](https://app.shippable.com/github/OpenAPITools/openapi-generator)
+[](https://ci.appveyor.com/project/WilliamCheng/openapi-generator-wh2wu)
+[](https://cloud.drone.io/OpenAPITools/openapi-generator)
+[](https://app.bitrise.io/app/4a2b10a819d12b67)
+
@@ -60,11 +76,11 @@ OpenAPI Generator allows generation of API client libraries (SDK generation), se
| | Languages/Frameworks |
| -------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
-| **API clients** | **ActionScript**, **Ada**, **Apex**, **Bash**, **C**, **C#** (.net 2.0, 3.5 or later, .NET Standard 1.3 - 2.0, .NET Core 2.0), **C++** (cpp-restsdk, Qt5, Tizen), **Clojure**, **Dart**, **Elixir**, **Elm**, **Eiffel**, **Erlang**, **Go**, **Groovy**, **Haskell** (http-client, Servant), **Java** (Jersey1.x, Jersey2.x, OkHttp, Retrofit1.x, Retrofit2.x, Feign, RestTemplate, RESTEasy, Vertx, Google API Client Library for Java, Rest-assured, Spring 5 Web Client, MicroProfile Rest Client), **k6**, **Kotlin**, **Lua**, **Nim**, **Node.js/JavaScript** (ES5, ES6, AngularJS with Google Closure Compiler annotations, Flow types, Apollo GraphQL DataStore), **Objective-C**, **OCaml**, **Perl**, **PHP**, **PowerShell**, **Python**, **R**, **Ruby**, **Rust** (rust, rust-server), **Scala** (akka, http4s, scalaz, sttp, swagger-async-httpclient), **Swift** (2.x, 3.x, 4.x, 5.x), **Typescript** (AngularJS, Angular (2.x - 8.x), Aurelia, Axios, Fetch, Inversify, jQuery, Node, Rxjs) |
+| **API clients** | **ActionScript**, **Ada**, **Apex**, **Bash**, **C**, **C#** (.net 2.0, 3.5 or later, .NET Standard 1.3 - 2.0, .NET Core 2.0), **C++** (cpp-restsdk, Qt5, Tizen), **Clojure**, **Crystal**, **Dart**, **Elixir**, **Elm**, **Eiffel**, **Erlang**, **Go**, **Groovy**, **Haskell** (http-client, Servant), **Java** (Jersey1.x, Jersey2.x, OkHttp, Retrofit1.x, Retrofit2.x, Feign, RestTemplate, RESTEasy, Vertx, Google API Client Library for Java, Rest-assured, Spring 5 Web Client, MicroProfile Rest Client), **k6**, **Kotlin**, **Lua**, **Nim**, **Node.js/JavaScript** (ES5, ES6, AngularJS with Google Closure Compiler annotations, Flow types, Apollo GraphQL DataStore), **Objective-C**, **OCaml**, **Perl**, **PHP**, **PowerShell**, **Python**, **R**, **Ruby**, **Rust** (rust, rust-server), **Scala** (akka, http4s, scalaz, sttp, swagger-async-httpclient), **Swift** (2.x, 3.x, 4.x, 5.x), **Typescript** (AngularJS, Angular (2.x - 8.x), Aurelia, Axios, Fetch, Inversify, jQuery, Node, Rxjs) |
| **Server stubs** | **Ada**, **C#** (ASP.NET Core, NancyFx), **C++** (Pistache, Restbed, Qt5 QHTTPEngine), **Erlang**, **F#** (Giraffe), **Go** (net/http, Gin), **Haskell** (Servant), **Java** (MSF4J, Spring, Undertow, JAX-RS: CDI, CXF, Inflector, Jersey, RestEasy, Play Framework, [PKMST](https://github.com/ProKarma-Inc/pkmst-getting-started-examples), [Vert.x](https://vertx.io/)), **Kotlin** (Spring Boot, Ktor, Vertx), **PHP** (Laravel, Lumen, Slim, Silex, [Symfony](https://symfony.com/), [Zend Expressive](https://github.com/zendframework/zend-expressive)), **Python** (Flask), **NodeJS**, **Ruby** (Sinatra, Rails5), **Rust** (rust-server), **Scala** (Akka, [Finch](https://github.com/finagle/finch), [Lagom](https://github.com/lagom/lagom), [Play](https://www.playframework.com/), Scalatra) |
| **API documentation generators** | **HTML**, **Confluence Wiki**, **Asciidoc** |
| **Configuration files** | [**Apache2**](https://httpd.apache.org/) |
-| **Others** | **GraphQL**, **JMeter**, **MySQL Schema**, **Protocol Buffer** |
+| **Others** | **GraphQL**, **JMeter**, **Ktorm**, **MySQL Schema**, **Protocol Buffer** |
## Table of contents
@@ -102,7 +118,9 @@ The OpenAPI Specification has undergone 3 revisions since initial creation in 20
| OpenAPI Generator Version | Release Date | Notes |
| --------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------ | ------------------------------------------------- |
-| 5.0.1 (upcoming patch release) [SNAPSHOT](https://oss.sonatype.org/content/repositories/snapshots/org/openapitools/openapi-generator-cli/5.0.1-SNAPSHOT/) | TBD | Patch release with enhancements, bug fixes, etc |
+| 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.1.0 (upcoming minor release) [SNAPSHOT](https://oss.sonatype.org/content/repositories/snapshots/org/openapitools/openapi-generator-cli/5.1.0-SNAPSHOT/) | Mar/Apr 2021 | Minor release with breaking changes (with fallback) |
+| 5.0.1 (upcoming patch release) [SNAPSHOT](https://oss.sonatype.org/content/repositories/snapshots/org/openapitools/openapi-generator-cli/5.0.1-SNAPSHOT/) | Jan/Feb 2021 | Patch release with enhancements, bug fixes, etc |
| [5.0.0](https://github.com/OpenAPITools/openapi-generator/releases/tag/v5.0.0) (latest stable release) | 21.12.2020 | Major release with breaking changes (no fallback) |
| [4.3.1](https://github.com/OpenAPITools/openapi-generator/releases/tag/v4.3.1) | 06.05.2020 | Patch release (enhancements, bug fixes, etc) |
@@ -824,6 +842,7 @@ Here is a list of template creators:
* C# (.NET Standard 1.3 ): @Gronsak
* C# (.NET 4.5 refactored): @jimschubert [:heart:](https://www.patreon.com/jimschubert)
* Clojure: @xhh
+ * Crystal: @wing328
* Dart: @yissachar
* Dart (refactor): @joernahrens
* Dart 2: @swipesight
@@ -939,6 +958,7 @@ Here is a list of template creators:
* Schema
* Avro: @sgadouar
* GraphQL: @wing328 [:heart:](https://www.patreon.com/wing328)
+ * Ktorm: @Luiz-Monad
* MySQL: @ybelenko
* Protocol Buffer: @wing328
@@ -990,11 +1010,11 @@ If you want to join the committee, please kindly apply by sending an email to te
| Elm | @eriktim (2018/09) |
| Erlang | @tsloughter (2017/11) @jfacorro (2018/10) @robertoaloi (2018/10) |
| F# | @nmfisher (2019/05) |
-| Go | @antihax (2017/11) @grokify (2018/07) @kemokemo (2018/09) @bkabrda (2019/07) |
+| Go | @antihax (2017/11) @grokify (2018/07) @kemokemo (2018/09) @jirikuncar (2021/01) |
| GraphQL | @renepardon (2018/12) |
| Groovy | |
| Haskell | |
-| Java | @bbdouglas (2017/07) @sreeshas (2017/08) @jfiala (2017/08) @lukoyanov (2017/09) @cbornet (2017/09) @jeff9finger (2018/01) @karismann (2019/03) @Zomzog (2019/04) @lwlee2608 (2019/10) @bkabrda (2020/01) |
+| Java | @bbdouglas (2017/07) @sreeshas (2017/08) @jfiala (2017/08) @lukoyanov (2017/09) @cbornet (2017/09) @jeff9finger (2018/01) @karismann (2019/03) @Zomzog (2019/04) @lwlee2608 (2019/10) @nmuesch (2021/01) |
| Kotlin | @jimschubert (2017/09) [:heart:](https://www.patreon.com/jimschubert), @dr4ke616 (2018/08) @karismann (2019/03) @Zomzog (2019/04) @andrewemery (2019/10) @4brunu (2019/11) @yutaka0m (2020/03) |
| Lua | @daurnimator (2017/08) |
| Nim | |
diff --git a/bin/configs/crystal.yaml b/bin/configs/crystal.yaml
new file mode 100644
index 00000000000..74badcce662
--- /dev/null
+++ b/bin/configs/crystal.yaml
@@ -0,0 +1,9 @@
+generatorName: crystal
+outputDir: samples/client/petstore/crystal
+inputSpec: modules/openapi-generator/src/test/resources/3_0/petstore.yaml
+templateDir: modules/openapi-generator/src/main/resources/crystal
+additionalProperties:
+ shardVersion: 1.0.0
+ moduleName: Petstore
+ shardName: petstore
+strictSpecBehavior: false
diff --git a/bin/configs/ktorm-schema.yaml b/bin/configs/ktorm-schema.yaml
new file mode 100644
index 00000000000..aff895cb279
--- /dev/null
+++ b/bin/configs/ktorm-schema.yaml
@@ -0,0 +1,7 @@
+generatorName: ktorm-schema
+outputDir: samples/schema/petstore/ktorm
+inputSpec: modules/openapi-generator/src/test/resources/2_0/petstore.yaml
+templateDir: modules/openapi-generator/src/main/resources/ktorm-schema
+additionalProperties:
+ hideGenerationTimestamp: true
+ importModelPackageName: org.openapitools.client.models
\ No newline at end of file
diff --git a/bin/configs/ruby-extensions-x-auth-id-alias.yaml b/bin/configs/ruby-extensions-x-auth-id-alias.yaml
new file mode 100644
index 00000000000..7f51216567c
--- /dev/null
+++ b/bin/configs/ruby-extensions-x-auth-id-alias.yaml
@@ -0,0 +1,8 @@
+generatorName: ruby
+outputDir: samples/openapi3/client/extensions/x-auth-id-alias/ruby-client
+inputSpec: modules/openapi-generator/src/test/resources/3_0/extensions/x-auth-id-alias.yaml
+templateDir: modules/openapi-generator/src/main/resources/ruby-client
+additionalProperties:
+ gemName: x_auth_id_alias
+ gemVersion: 1.0.0
+ moduleName: XAuthIDAlias
\ No newline at end of file
diff --git a/bin/configs/typescript-angular-v9-provided-in-any.yaml b/bin/configs/typescript-angular-v9-provided-in-any.yaml
new file mode 100644
index 00000000000..6678f7bb2ec
--- /dev/null
+++ b/bin/configs/typescript-angular-v9-provided-in-any.yaml
@@ -0,0 +1,6 @@
+generatorName: typescript-angular
+outputDir: samples/client/petstore/typescript-angular-v9-provided-in-any/builds/default
+inputSpec: modules/openapi-generator/src/test/resources/2_0/petstore.yaml
+additionalProperties:
+ ngVersion: 9.0.0
+ providedIn: any
diff --git a/docs/contributing.md b/docs/contributing.md
index 482ffecf4dd..a791b0d6f1b 100644
--- a/docs/contributing.md
+++ b/docs/contributing.md
@@ -53,6 +53,7 @@ Code change should conform to the programming style guide of the respective lang
- 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
- 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
- Elixir: https://github.com/christopheradams/elixir_style_guide
- Eiffel: https://www.eiffel.org/doc/eiffel/Coding%20Standards
diff --git a/docs/generators.md b/docs/generators.md
index 8a68e529532..739d0cfd19a 100644
--- a/docs/generators.md
+++ b/docs/generators.md
@@ -16,6 +16,7 @@ The following generators are available:
* [cpp-restsdk](generators/cpp-restsdk.md)
* [cpp-tizen](generators/cpp-tizen.md)
* [cpp-ue4 (beta)](generators/cpp-ue4.md)
+* [crystal (beta)](generators/crystal.md)
* [csharp](generators/csharp.md)
* [csharp-dotnet2 (deprecated)](generators/csharp-dotnet2.md)
* [csharp-netcore](generators/csharp-netcore.md)
@@ -141,6 +142,7 @@ The following generators are available:
## SCHEMA generators
* [avro-schema (beta)](generators/avro-schema.md)
* [graphql-schema](generators/graphql-schema.md)
+* [ktorm-schema (beta)](generators/ktorm-schema.md)
* [mysql-schema](generators/mysql-schema.md)
* [protobuf-schema (beta)](generators/protobuf-schema.md)
diff --git a/docs/generators/c.md b/docs/generators/c.md
index 29df6b2096e..448cf98b4bf 100644
--- a/docs/generators/c.md
+++ b/docs/generators/c.md
@@ -56,41 +56,109 @@ These options may be applied as additional-properties (cli) or configOptions (pl
_noreturn
_static_assert
_thread_local
+
alignas
+
alignof
+
and
+
and_eq
+
asm
+
atomic_cancel
+
atomic_commit
+
atomic_noexcept
auto
+
bitand
+
bitor
+
bool
break
case
+
catch
char
+
char16_t
+
char32_t
+
char8_t
+
class
+
co_await
+
co_return
+
co_yield
+
compl
+
concept
const
+
const_cast
+
consteval
+
constexpr
+
constinit
continue
+
decltype
default
+
delete
do
double
+
dynamic_cast
else
enum
+
explicit
+
export
extern
+
false
+
final
float
for
+
friend
goto
if
inline
int
long
+
mutable
+
namespace
+
new
+
noexcept
+
not
+
not_eq
+
nullptr
+
operator
+
or
+
or_eq
+
override
+
private
+
protected
+
public
+
reflexpr
register
+
reinterpret_cast
remove
+
requires
restrict
return
short
signed
sizeof
static
+
static_assert
+
static_cast
struct
switch
+
synchronized
+
template
+
this
+
thread_local
+
throw
+
transaction_safe
+
transaction_safe_dynamic
+
true
+
try
typedef
+
typeid
+
typename
union
unsigned
+
using
+
virtual
void
volatile
+
wchar_t
while
+
xor
+
xor_eq
## FEATURE SET
diff --git a/docs/generators/crystal.md b/docs/generators/crystal.md
new file mode 100644
index 00000000000..8965a800fff
--- /dev/null
+++ b/docs/generators/crystal.md
@@ -0,0 +1,226 @@
+---
+title: Config Options for crystal
+sidebar_label: crystal
+---
+
+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 |
+| ------ | ----------- | ------ | ------- |
+|allowUnicodeIdentifiers|boolean, toggles whether unicode identifiers are allowed in names or not, default is false| |false|
+|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.|
**false** The 'additionalProperties' implementation is compliant with the OAS and JSON schema specifications. **true** Keep the old (incorrect) behaviour that 'additionalProperties' is set to false by default. |true|
+|ensureUniqueParams|Whether to ensure parameter names are unique in an operation (rename parameters that are not).| |true|
+|hideGenerationTimestamp|Hides the generation timestamp when files are generated.| |true|
+|legacyDiscriminatorBehavior|Set to true for generators with better support for discriminators. (Python, Java, Go, PowerShell, C#have this enabled by default).|
**true** The mapping in the discriminator includes descendent schemas that allOf inherit from self and the discriminator mapping schemas in the OAS document. **false** 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. |true|
+|prependFormOrBodyParameters|Add form or body parameters to the beginning of the parameter list.| |false|
+|shardAuthor|shard author (only one is supported).| |null|
+|shardAuthorEmail|shard author email (only one is supported).| |null|
+|shardDescription|shard description.| |This shard maps to a REST API|
+|shardHomepage|shard homepage.| |http://org.openapitools|
+|shardLicense|shard license.| |unlicense|
+|shardName|shard name (e.g. twitter_client| |openapi_client|
+|shardVersion|shard version.| |1.0.0|
+|sortModelPropertiesByRequiredFlag|Sort model properties to place required parameters before optional parameters.| |true|
+|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true|
+
+## IMPORT MAPPING
+
+| Type/Alias | Imports |
+| ---------- | ------- |
+
+
+## INSTANTIATION TYPES
+
+| Type/Alias | Instantiated By |
+| ---------- | --------------- |
+|array|Array|
+|map|Hash|
+|set|Set|
+
+
+## LANGUAGE PRIMITIVES
+
+
+Array
+Boolean
+Date
+File
+Float
+Hash
+Integer
+Object
+String
+Time
+
+
+## RESERVED WORDS
+
+
+abstract
+alias
+as
+as?
+asm
+begin
+break
+case
+class
+def
+do
+else
+elsif
+end
+ensure
+enum
+extend
+false
+for
+fun
+if
+in
+include
+instance
+is_a?
+lib
+macro
+module
+next
+nil
+nil?
+of
+out
+pointerof
+private
+protected
+require
+rescue
+responds_to?
+return
+select
+self
+sizeof
+struct
+super
+then
+true
+type
+typeof
+uninitialized
+union
+unless
+until
+verbatim
+when
+while
+with
+yield
+
+
+## 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
diff --git a/docs/generators/ktorm-schema.md b/docs/generators/ktorm-schema.md
new file mode 100644
index 00000000000..4f65a1ecc81
--- /dev/null
+++ b/docs/generators/ktorm-schema.md
@@ -0,0 +1,355 @@
+---
+title: Config Options for ktorm-schema
+sidebar_label: ktorm-schema
+---
+
+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 |
+| ------ | ----------- | ------ | ------- |
+|addSurrogateKey|Adds the surrogate key for all models that don't already have a primary key (named by the above convention)| |false|
+|artifactId|Generated artifact id (name of jar).| |ktorm|
+|artifactVersion|Generated artifact's package version.| |1.0.0|
+|defaultDatabaseName|Default database name for all queries| |sqlite.db|
+|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', and 'original'| |camelCase|
+|groupId|Generated artifact package's organization (i.e. maven groupId).| |org.openapitools|
+|identifierNamingConvention|Naming convention of Ktorm identifiers(table names and column names). This is not related to database name which is defined by defaultDatabaseName option|
**original** Do not transform original names **snake_case** Use snake_case names |original|
+|importModelPackageName|Package name of the imported models| |org.openapitools.database.models|
+|modelMutable|Create mutable models| |false|
+|packageName|Generated artifact package name.| |org.openapitools.database|
+|primaryKeyConvention|Primary key naming convention| |id|
+|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.util.Date|
+|DateTime|java.time.LocalDateTime|
+|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
+
+
+kotlin.Array
+kotlin.Boolean
+kotlin.Byte
+kotlin.ByteArray
+kotlin.Char
+kotlin.Double
+kotlin.Float
+kotlin.Int
+kotlin.Long
+kotlin.Short
+kotlin.String
+kotlin.collections.List
+kotlin.collections.Map
+kotlin.collections.Set
+
+
+## RESERVED WORDS
+
+
+abort
+action
+add
+after
+all
+alter
+always
+analyze
+and
+any
+as
+asc
+attach
+autoincr
+autoincrement
+before
+begin
+between
+bitand
+bitnot
+bitor
+blob
+by
+cascade
+case
+cast
+check
+collate
+column
+comma
+commit
+concat
+conflict
+constraint
+create
+cross
+current
+current_date
+current_time
+current_timestamp
+database
+default
+deferrable
+deferred
+delete
+desc
+detach
+distinct
+do
+dot
+drop
+each
+else
+end
+eq
+escape
+except
+exclude
+exclusive
+exists
+explain
+fail
+filter
+first
+float
+following
+for
+foreign
+from
+full
+ge
+generated
+glob
+group
+groups
+gt
+having
+id
+if
+ignore
+immediate
+in
+index
+indexed
+initially
+inner
+insert
+instead
+integer
+intersect
+into
+is
+isnull
+join
+key
+last
+le
+left
+like
+limit
+lp
+lshift
+lt
+match
+minus
+natural
+ne
+no
+not
+nothing
+notnull
+null
+nulls
+of
+offset
+on
+or
+order
+others
+outer
+over
+partition
+plan
+plus
+pragma
+preceding
+primary
+query
+raise
+range
+recursive
+references
+regexp
+reindex
+release
+rem
+rename
+replace
+restrict
+right
+rollback
+row
+rows
+rp
+rshift
+savepoint
+select
+set
+slash
+star
+string
+table
+temp
+temporary
+then
+ties
+to
+transaction
+trigger
+unbounded
+union
+unique
+update
+using
+vacuum
+values
+variable
+view
+virtual
+when
+where
+window
+with
+without
+
+
+## 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
diff --git a/docs/generators/powershell.md b/docs/generators/powershell.md
index 18cb16912c3..19ee0fe1218 100644
--- a/docs/generators/powershell.md
+++ b/docs/generators/powershell.md
@@ -19,6 +19,7 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|powershellGalleryUrl|URL to the module in PowerShell Gallery (e.g. https://www.powershellgallery.com/packages/PSTwitter/).| |null|
|projectUri|A URL to the main website for this project| |null|
|releaseNotes|Release notes of the generated PowerShell module| |null|
+|skipVerbParsing|Set skipVerbParsing to not try get powershell verbs of operation names| |null|
|tags|Tags applied to the generated PowerShell module. These help with module discovery in online galleries| |null|
|useOneOfDiscriminatorLookup|Use the discriminator's mapping in oneOf to speed up the model lookup. IMPORTANT: Validation (e.g. one and onlye one match in oneOf's schemas) will be skipped.| |null|
diff --git a/docs/generators/typescript-angular.md b/docs/generators/typescript-angular.md
index adb04b56dfd..bd90759d0fa 100644
--- a/docs/generators/typescript-angular.md
+++ b/docs/generators/typescript-angular.md
@@ -25,7 +25,8 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|npmVersion|The version of your npm package. If not provided, using the version from the OpenAPI specification file.| |1.0.0|
|nullSafeAdditionalProps|Set to make additional properties types declare that their indexer may return undefined| |false|
|prependFormOrBodyParameters|Add form or body parameters to the beginning of the parameter list.| |false|
-|providedInRoot|Use this property to provide Injectables in root (it is only valid in angular version greater or equal to 6.0.0).| |false|
+|providedIn|Use this property to provide Injectables in wanted level (it is only valid in angular version greater or equal to 9.0.0).|
**root** The application-level injector in most apps. **none** No providedIn (same as providedInRoot=false) **any** Provides a unique instance in each lazy loaded module while all eagerly loaded modules share one instance. **platform** A special singleton platform injector shared by all applications on the page. |root|
+|providedInRoot|Use this property to provide Injectables in root (it is only valid in angular version greater or equal to 6.0.0). IMPORTANT: Deprecated for angular version greater or equal to 9.0.0, use **providedIn** instead.| |false|
|queryParamObjectFormat|The format for query param objects: 'dot', 'json', 'key'.| |dot|
|serviceFileSuffix|The suffix of the file of the generated service (service<suffix>.ts).| |.service|
|serviceSuffix|The suffix of the generated service.| |Service|
diff --git a/modules/openapi-generator-gradle-plugin/gradle.properties b/modules/openapi-generator-gradle-plugin/gradle.properties
index c8b12097262..84128661880 100644
--- a/modules/openapi-generator-gradle-plugin/gradle.properties
+++ b/modules/openapi-generator-gradle-plugin/gradle.properties
@@ -1,5 +1,6 @@
# RELEASE_VERSION
openApiGeneratorVersion=5.1.0-SNAPSHOT
+>>>>>>> origin/master
# /RELEASE_VERSION
# BEGIN placeholders
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CLibcurlClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CLibcurlClientCodegen.java
index f64d2d99d3f..dbc59ae281c 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CLibcurlClientCodegen.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CLibcurlClientCodegen.java
@@ -143,7 +143,111 @@ public class CLibcurlClientCodegen extends DefaultCodegen implements CodegenConf
"_Imaginary",
"_Noreturn",
"_Static_assert",
- "_Thread_local")
+ "_Thread_local",
+
+ // cpp reserved keywords
+ // ref: https://en.cppreference.com/w/cpp/keyword
+ "alignas",
+ "alignof",
+ "and",
+ "and_eq",
+ "asm",
+ "atomic_cancel",
+ "atomic_commit",
+ "atomic_noexcept",
+ //"auto",
+ "bitand",
+ "bitor",
+ "bool",
+ //"break",
+ //"case",
+ "catch",
+ //"char",
+ "char8_t",
+ "char16_t",
+ "char32_t",
+ "class",
+ "compl",
+ "concept",
+ //"const",
+ "consteval",
+ "constexpr",
+ "constinit",
+ "const_cast",
+ //"continue",
+ "co_await",
+ "co_return",
+ "co_yield",
+ "decltype",
+ //"default",
+ "delete",
+ //"do",
+ //"double",
+ "dynamic_cast",
+ //"else",
+ //"enum",
+ "explicit",
+ "export",
+ //"extern",
+ "false",
+ //"float",
+ //"for",
+ "friend",
+ //"goto",
+ //"if",
+ //"inline",
+ //"int",
+ //"long",
+ "mutable",
+ "namespace",
+ "new",
+ "noexcept",
+ "not",
+ "not_eq",
+ "nullptr",
+ "operator",
+ "or",
+ "or_eq",
+ "private",
+ "protected",
+ "public",
+ "reflexpr",
+ //"register",
+ "reinterpret_cast",
+ "requires",
+ //"return",
+ //"short",
+ //"signed",
+ //"sizeof",
+ //"static",
+ "static_assert",
+ "static_cast",
+ //"struct",
+ //"switch",
+ "synchronized",
+ "template",
+ "this",
+ "thread_local",
+ "throw",
+ "true",
+ "try",
+ //"typedef",
+ "typeid",
+ "typename",
+ //"union",
+ //"unsigned",
+ "using",
+ "virtual",
+ //"void",
+ //"volatile",
+ "wchar_t",
+ //"while",
+ "xor",
+ "xor_eq",
+ "final",
+ "override",
+ "transaction_safe",
+ "transaction_safe_dynamic")
);
instantiationTypes.clear();
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CSharpNetCoreClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CSharpNetCoreClientCodegen.java
index 9d569156159..12e3e15bb85 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CSharpNetCoreClientCodegen.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CSharpNetCoreClientCodegen.java
@@ -787,8 +787,7 @@ public class CSharpNetCoreClientCodegen extends AbstractCSharpCodegen {
}
// string
- String var = value.replaceAll("_", " ");
- //var = WordUtils.capitalizeFully(var);
+ String var = value.replaceAll(" ", "_");
var = camelize(var);
var = var.replaceAll("\\W+", "");
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ConfluenceWikiCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ConfluenceWikiCodegen.java
index c2cadfa1702..fcbc97b46e8 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ConfluenceWikiCodegen.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/ConfluenceWikiCodegen.java
@@ -22,6 +22,7 @@ import io.swagger.v3.oas.models.media.Schema;
import org.openapitools.codegen.*;
import org.openapitools.codegen.meta.features.*;
import org.openapitools.codegen.utils.ModelUtils;
+import org.apache.commons.lang3.StringUtils;
import java.util.*;
@@ -123,6 +124,11 @@ public class ConfluenceWikiCodegen extends DefaultCodegen implements CodegenConf
return objs;
}
+ @Override
+ public Map
postProcessModels(Map objs) {
+ return postProcessModelsEnum(objs);
+ }
+
@Override
public String escapeQuotationMark(String input) {
// just return the original string
@@ -134,4 +140,14 @@ public class ConfluenceWikiCodegen extends DefaultCodegen implements CodegenConf
// just return the original string
return input;
}
+
+ @Override
+ public String escapeText(String input) {
+ if (input == null) {
+ return input;
+ }
+
+ // chomp tailing newline because it breaks the tables and keep all other sign to show documentation properly
+ return StringUtils.chomp(input);
+ }
}
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java
new file mode 100644
index 00000000000..7a5b347180d
--- /dev/null
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/CrystalClientCodegen.java
@@ -0,0 +1,891 @@
+/*
+ * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech)
+ *
+ * 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 io.swagger.v3.oas.models.media.ArraySchema;
+import io.swagger.v3.oas.models.media.Schema;
+import org.apache.commons.io.FilenameUtils;
+import org.apache.commons.lang3.StringUtils;
+import org.openapitools.codegen.*;
+import org.openapitools.codegen.meta.GeneratorMetadata;
+import org.openapitools.codegen.meta.Stability;
+import org.openapitools.codegen.meta.features.*;
+import org.openapitools.codegen.templating.mustache.PrefixWithHashLambda;
+import org.openapitools.codegen.templating.mustache.TrimWhitespaceLambda;
+import org.openapitools.codegen.utils.ModelUtils;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.BufferedReader;
+import java.io.File;
+import java.io.InputStreamReader;
+import java.nio.charset.StandardCharsets;
+import java.time.LocalDate;
+import java.time.ZoneId;
+import java.util.*;
+
+import static org.openapitools.codegen.utils.StringUtils.camelize;
+import static org.openapitools.codegen.utils.StringUtils.underscore;
+
+public class CrystalClientCodegen extends DefaultCodegen {
+ private static final Logger LOGGER = LoggerFactory.getLogger(CrystalClientCodegen.class);
+ private static final String NUMERIC_ENUM_PREFIX = "N";
+ protected static int emptyMethodNameCounter = 0;
+
+ protected String shardName;
+ protected String moduleName;
+ protected String shardVersion = "1.0.0";
+ protected String specFolder = "spec";
+ protected String srcFolder = "src";
+ protected String shardLicense = "unlicense";
+ protected String shardHomepage = "https://openapitools.org";
+ protected String shardSummary = "A Crystal SDK for the REST API";
+ protected String shardDescription = "This shard maps to a REST API";
+ protected String shardAuthor = "";
+ protected String shardAuthorEmail = "";
+ protected String apiDocPath = "docs/";
+ protected String modelDocPath = "docs/";
+
+ public static final String SHARD_NAME = "shardName";
+ public static final String SHARD_VERSION = "shardVersion";
+ public static final String SHARD_LICENSE = "shardLicense";
+ public static final String SHARD_HOMEPAGE = "shardHomepage";
+ public static final String SHARD_SUMMARY = "shardSummary";
+ public static final String SHARD_DESCRIPTION = "shardDescription";
+ public static final String SHARD_AUTHOR = "shardAuthor";
+ public static final String SHARD_AUTHOR_EMAIL = "shardAuthorEmail";
+
+ public CrystalClientCodegen() {
+ super();
+
+ modifyFeatureSet(features -> features
+ .includeDocumentationFeatures(DocumentationFeature.Readme)
+ .wireFormatFeatures(EnumSet.of(WireFormatFeature.JSON, WireFormatFeature.XML, WireFormatFeature.Custom))
+ .securityFeatures(EnumSet.of(
+ SecurityFeature.BasicAuth,
+ SecurityFeature.BearerToken,
+ SecurityFeature.ApiKey,
+ SecurityFeature.OAuth2_Implicit
+ ))
+ .excludeGlobalFeatures(
+ GlobalFeature.XMLStructureDefinitions,
+ GlobalFeature.Callbacks,
+ GlobalFeature.LinkObjects,
+ GlobalFeature.ParameterStyling,
+ GlobalFeature.ParameterizedServer,
+ GlobalFeature.MultiServer
+ )
+ .includeSchemaSupportFeatures(
+ SchemaSupportFeature.Polymorphism
+ )
+ .excludeParameterFeatures(
+ ParameterFeature.Cookie
+ )
+ .includeClientModificationFeatures(
+ ClientModificationFeature.BasePath,
+ ClientModificationFeature.UserAgent
+ )
+ );
+
+ generatorMetadata = GeneratorMetadata.newBuilder(generatorMetadata)
+ .stability(Stability.BETA)
+ .build();
+
+ supportsInheritance = true;
+
+ // clear import mapping (from default generator) as crystal does not use it
+ // at the moment
+ importMapping.clear();
+
+ embeddedTemplateDir = templateDir = "crystal";
+ outputFolder = "generated-code" + File.separator + "crystal";
+
+ modelPackage = "models";
+ apiPackage = "api";
+ modelTemplateFiles.put("model.mustache", ".cr");
+ apiTemplateFiles.put("api.mustache", ".cr");
+
+ modelTestTemplateFiles.put("model_test.mustache", ".cr");
+ apiTestTemplateFiles.put("api_test.mustache", ".cr");
+
+ // TODO support auto-generated doc
+ //modelDocTemplateFiles.put("model_doc.mustache", ".md");
+ //apiDocTemplateFiles.put("api_doc.mustache", ".md");
+
+ // default HIDE_GENERATION_TIMESTAMP to true
+ hideGenerationTimestamp = Boolean.TRUE;
+
+ // reserved word. Ref: https://github.com/crystal-lang/crystal/wiki/Crystal-for-Rubyists#available-keywords
+ reservedWords = new HashSet(
+ Arrays.asList(
+ "abstract", "do", "if", "nil?", "select", "union",
+ "alias", "else", "in", "of", "self", "unless",
+ "as", "elsif", "include", "out", "sizeof", "until",
+ "as?", "end", "instance", "sizeof", "pointerof", "struct", "verbatim",
+ "asm", "ensure", "is_a?", "private", "super", "when",
+ "begin", "enum", "lib", "protected", "then", "while",
+ "break", "extend", "macro", "require", "true", "with",
+ "case", "false", "module", "rescue", "type", "yield",
+ "class", "for", "next", "responds_to?", "typeof",
+ "def", "fun", "nil", "return", "uninitialized")
+ );
+
+ languageSpecificPrimitives.clear();
+ languageSpecificPrimitives.add("String");
+ languageSpecificPrimitives.add("Boolean");
+ languageSpecificPrimitives.add("Integer");
+ languageSpecificPrimitives.add("Float");
+ languageSpecificPrimitives.add("Date");
+ languageSpecificPrimitives.add("Time");
+ languageSpecificPrimitives.add("Array");
+ languageSpecificPrimitives.add("Hash");
+ languageSpecificPrimitives.add("File");
+ languageSpecificPrimitives.add("Object");
+
+ typeMapping.clear();
+ typeMapping.put("string", "String");
+ typeMapping.put("boolean", "Bool");
+ typeMapping.put("char", "Char");
+ typeMapping.put("int", "Int32");
+ typeMapping.put("integer", "Int32");
+ typeMapping.put("long", "Int64");
+ typeMapping.put("short", "Int32");
+ typeMapping.put("float", "Float32");
+ typeMapping.put("double", "Float64");
+ typeMapping.put("number", "Float64");
+ typeMapping.put("date", "Time");
+ typeMapping.put("DateTime", "Time");
+ typeMapping.put("array", "Array");
+ typeMapping.put("List", "Array");
+ typeMapping.put("set", "Set");
+ typeMapping.put("map", "Hash");
+ typeMapping.put("object", "Object");
+ typeMapping.put("file", "File");
+ typeMapping.put("binary", "String");
+ typeMapping.put("ByteArray", "String");
+ typeMapping.put("UUID", "String");
+ typeMapping.put("URI", "String");
+
+ instantiationTypes.put("map", "Hash");
+ instantiationTypes.put("array", "Array");
+ instantiationTypes.put("set", "Set");
+
+ // remove modelPackage and apiPackage added by default
+ cliOptions.removeIf(opt -> CodegenConstants.MODEL_PACKAGE.equals(opt.getOpt()) ||
+ CodegenConstants.API_PACKAGE.equals(opt.getOpt()));
+
+ cliOptions.add(new CliOption(SHARD_NAME, "shard name (e.g. twitter_client").
+ defaultValue("openapi_client"));
+
+ cliOptions.add(new CliOption(SHARD_VERSION, "shard version.").defaultValue("1.0.0"));
+
+ cliOptions.add(new CliOption(SHARD_LICENSE, "shard license.").
+ defaultValue("unlicense"));
+
+ cliOptions.add(new CliOption(SHARD_HOMEPAGE, "shard homepage.").
+ defaultValue("http://org.openapitools"));
+
+ cliOptions.add(new CliOption(SHARD_DESCRIPTION, "shard description.").
+ defaultValue("This shard maps to a REST API"));
+
+ cliOptions.add(new CliOption(SHARD_AUTHOR, "shard author (only one is supported)."));
+
+ cliOptions.add(new CliOption(SHARD_AUTHOR_EMAIL, "shard author email (only one is supported)."));
+
+ cliOptions.add(new CliOption(CodegenConstants.HIDE_GENERATION_TIMESTAMP, CodegenConstants.HIDE_GENERATION_TIMESTAMP_DESC).
+ defaultValue(Boolean.TRUE.toString()));
+ }
+
+ @Override
+ public void processOpts() {
+ super.processOpts();
+
+ if (StringUtils.isEmpty(System.getenv("CRYSTAL_POST_PROCESS_FILE"))) {
+ LOGGER.info("Hint: Environment variable 'CRYSTAL_POST_PROCESS_FILE' (optional) not defined. E.g. to format the source code, please try 'export CRYSTAL_POST_PROCESS_FILE=\"/usr/local/bin/crystal tool format\"' (Linux/Mac)");
+ }
+
+ if (additionalProperties.containsKey(SHARD_NAME)) {
+ setShardName((String) additionalProperties.get(SHARD_NAME));
+ }
+ additionalProperties.put(SHARD_NAME, shardName);
+
+ if (additionalProperties.containsKey(SHARD_VERSION)) {
+ setShardVersion((String) additionalProperties.get(SHARD_VERSION));
+ } else {
+ // not set, pass the default value to template
+ additionalProperties.put(SHARD_VERSION, shardVersion);
+ }
+
+ if (additionalProperties.containsKey(SHARD_LICENSE)) {
+ setShardLicense((String) additionalProperties.get(SHARD_LICENSE));
+ }
+
+ if (additionalProperties.containsKey(SHARD_HOMEPAGE)) {
+ setShardHomepage((String) additionalProperties.get(SHARD_HOMEPAGE));
+ }
+
+ if (additionalProperties.containsKey(SHARD_SUMMARY)) {
+ setShardSummary((String) additionalProperties.get(SHARD_SUMMARY));
+ }
+
+ if (additionalProperties.containsKey(SHARD_DESCRIPTION)) {
+ setShardDescription((String) additionalProperties.get(SHARD_DESCRIPTION));
+ }
+
+ if (additionalProperties.containsKey(SHARD_AUTHOR)) {
+ setShardAuthor((String) additionalProperties.get(SHARD_AUTHOR));
+ }
+
+ if (additionalProperties.containsKey(SHARD_AUTHOR_EMAIL)) {
+ setShardAuthorEmail((String) additionalProperties.get(SHARD_AUTHOR_EMAIL));
+ }
+
+ // make api and model doc path available in mustache template
+ additionalProperties.put("apiDocPath", apiDocPath);
+ additionalProperties.put("modelDocPath", modelDocPath);
+
+ // use constant model/api package (folder path)
+ setModelPackage("models");
+ setApiPackage("api");
+
+ supportingFiles.add(new SupportingFile("shard_name.mustache", srcFolder, shardName + ".cr"));
+ String shardFolder = srcFolder + File.separator + shardName;
+ supportingFiles.add(new SupportingFile("api_error.mustache", shardFolder, "api_error.cr"));
+ supportingFiles.add(new SupportingFile("configuration.mustache", shardFolder, "configuration.cr"));
+ supportingFiles.add(new SupportingFile("api_client.mustache", shardFolder, "api_client.cr"));
+ supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
+ supportingFiles.add(new SupportingFile("git_push.sh.mustache", "", "git_push.sh"));
+ supportingFiles.add(new SupportingFile("gitignore.mustache", "", ".gitignore"));
+ supportingFiles.add(new SupportingFile("travis.mustache", "", ".travis.yml"));
+ supportingFiles.add(new SupportingFile("shard.mustache", "", "shard.yml"));
+
+ // crystal spec files
+ supportingFiles.add(new SupportingFile("spec_helper.mustache", specFolder, "spec_helper.cr")
+ .doNotOverwrite());
+
+ // add lambda for mustache templates
+ additionalProperties.put("lambdaPrefixWithHash", new PrefixWithHashLambda());
+
+ }
+
+ @Override
+ public String getHelp() {
+ return "Generates a Crystal client library (beta).";
+ }
+
+ @Override
+ public CodegenType getTag() {
+ return CodegenType.CLIENT;
+ }
+
+ @Override
+ public String getName() {
+ return "crystal";
+ }
+
+ @Override
+ public String apiFileFolder() {
+ return outputFolder + File.separator + srcFolder + File.separator + shardName + File.separator + apiPackage.replace("/", File.separator);
+ }
+
+ @Override
+ public String modelFileFolder() {
+ return outputFolder + File.separator + srcFolder + File.separator + shardName + File.separator + modelPackage.replace("/", File.separator);
+ }
+
+ @Override
+ public String apiTestFileFolder() {
+ return outputFolder + File.separator + specFolder + File.separator + apiPackage.replace("/", File.separator);
+ }
+
+ @Override
+ public String modelTestFileFolder() {
+ return outputFolder + File.separator + specFolder + File.separator + modelPackage.replace("/", File.separator);
+ }
+
+ @Override
+ public String apiDocFileFolder() {
+ return (outputFolder + "/" + apiDocPath).replace('/', File.separatorChar);
+ }
+
+ @Override
+ public String modelDocFileFolder() {
+ return (outputFolder + "/" + modelDocPath).replace('/', File.separatorChar);
+ }
+
+ @Override
+ public String getSchemaType(Schema schema) {
+ String openAPIType = super.getSchemaType(schema);
+ String type = null;
+ if (typeMapping.containsKey(openAPIType)) {
+ type = typeMapping.get(openAPIType);
+ if (languageSpecificPrimitives.contains(type)) {
+ return type;
+ }
+ } else {
+ type = openAPIType;
+ }
+
+ if (type == null) {
+ return null;
+ }
+
+ return toModelName(type);
+ }
+
+ @Override
+ public String toModelName(final String name) {
+ String modelName;
+ modelName = sanitizeName(name);
+
+ if (!StringUtils.isEmpty(modelNamePrefix)) {
+ modelName = modelNamePrefix + "_" + modelName;
+ }
+
+ if (!StringUtils.isEmpty(modelNameSuffix)) {
+ modelName = modelName + "_" + modelNameSuffix;
+ }
+
+ // model name cannot use reserved keyword, e.g. return
+ if (isReservedWord(modelName)) {
+ modelName = camelize("Model" + modelName);
+ LOGGER.warn(name + " (reserved word) cannot be used as model name. Renamed to " + modelName);
+ return modelName;
+ }
+
+ // model name starts with number
+ if (modelName.matches("^\\d.*")) {
+ LOGGER.warn(modelName + " (model name starts with number) cannot be used as model name. Renamed to " + camelize("model_" + modelName));
+ modelName = "model_" + modelName; // e.g. 200Response => Model200Response (after camelize)
+ }
+
+ // camelize the model name
+ // phone_number => PhoneNumber
+ return camelize(modelName);
+ }
+
+ @Override
+ public String toModelFilename(String name) {
+ return underscore(toModelName(name));
+ }
+
+ @Override
+ public String toModelDocFilename(String name) {
+ return toModelName(name);
+ }
+
+ @Override
+ public String toApiFilename(final String name) {
+ // replace - with _ e.g. created-at => created_at
+ String filename = name;
+ if (apiNameSuffix != null && apiNameSuffix.length() > 0) {
+ filename = filename + "_" + apiNameSuffix;
+ }
+
+ filename = filename.replaceAll("-", "_");
+
+ // e.g. PhoneNumberApi.cr => phone_number_api.cr
+ return underscore(filename);
+ }
+
+ @Override
+ public String toApiDocFilename(String name) {
+ return toApiName(name);
+ }
+
+ @Override
+ public String toApiTestFilename(String name) {
+ return toApiFilename(name) + "_spec";
+ }
+
+ @Override
+ public String toModelTestFilename(String name) {
+ return toModelFilename(name) + "_spec";
+ }
+
+ @Override
+ public String toApiName(String name) {
+ return super.toApiName(name);
+ }
+
+ @Override
+ public String toEnumValue(String value, String datatype) {
+ if ("Integer".equals(datatype) || "Float".equals(datatype)) {
+ return value;
+ } else {
+ return "\"" + escapeText(value) + "\"";
+ }
+ }
+
+ @Override
+ public String toEnumVarName(String name, String datatype) {
+ if (name.length() == 0) {
+ return "EMPTY";
+ }
+
+ // number
+ if ("Integer".equals(datatype) || "Float".equals(datatype)) {
+ String varName = name;
+ varName = varName.replaceAll("-", "MINUS_");
+ varName = varName.replaceAll("\\+", "PLUS_");
+ varName = varName.replaceAll("\\.", "_DOT_");
+ return NUMERIC_ENUM_PREFIX + varName;
+ }
+
+ // string
+ String enumName = sanitizeName(underscore(name).toUpperCase(Locale.ROOT));
+ enumName = enumName.replaceFirst("^_", "");
+ enumName = enumName.replaceFirst("_$", "");
+
+ if (enumName.matches("\\d.*")) { // starts with number
+ return NUMERIC_ENUM_PREFIX + enumName;
+ } else {
+ return enumName;
+ }
+ }
+
+ @Override
+ public String toEnumName(CodegenProperty property) {
+ String enumName = underscore(toModelName(property.name)).toUpperCase(Locale.ROOT);
+ enumName = enumName.replaceFirst("^_", "");
+ enumName = enumName.replaceFirst("_$", "");
+
+ if (enumName.matches("\\d.*")) { // starts with number
+ return NUMERIC_ENUM_PREFIX + enumName;
+ } else {
+ return enumName;
+ }
+ }
+
+ @Override
+ public Map postProcessModels(Map objs) {
+ // process enum in models
+ return postProcessModelsEnum(objs);
+ }
+
+ @Override
+ public String toOperationId(String operationId) {
+ // rename to empty_method_name_1 (e.g.) if method name is empty
+ if (StringUtils.isEmpty(operationId)) {
+ operationId = underscore("empty_method_name_" + emptyMethodNameCounter++);
+ LOGGER.warn("Empty method name (operationId) found. Renamed to " + operationId);
+ return operationId;
+ }
+
+ // method name cannot use reserved keyword, e.g. return
+ if (isReservedWord(operationId)) {
+ String newOperationId = underscore("call_" + operationId);
+ LOGGER.warn(operationId + " (reserved word) cannot be used as method name. Renamed to " + newOperationId);
+ return newOperationId;
+ }
+
+ // operationId starts with a number
+ if (operationId.matches("^\\d.*")) {
+ LOGGER.warn(operationId + " (starting with a number) cannot be used as method name. Renamed to " + underscore(sanitizeName("call_" + operationId)));
+ operationId = "call_" + operationId;
+ }
+
+ return underscore(sanitizeName(operationId));
+ }
+
+ @Override
+ public String toApiImport(String name) {
+ return shardName + "/" + apiPackage() + "/" + toApiFilename(name);
+ }
+
+ public void setShardName(String shardName) {
+ this.shardName = shardName;
+ }
+
+ public void setModuleName(String moduleName) {
+ this.moduleName = moduleName;
+ }
+
+ public void setShardVersion(String shardVersion) {
+ this.shardVersion = shardVersion;
+ }
+
+ public void setShardDescription(String shardDescription) {
+ this.shardDescription = shardDescription;
+ }
+
+ public void setShardSummary(String shardSummary) {
+ this.shardSummary = shardSummary;
+ }
+
+ public void setShardLicense(String shardLicense) {
+ this.shardLicense = shardLicense;
+ }
+
+ public void setShardHomepage(String shardHomepage) {
+ this.shardHomepage = shardHomepage;
+ }
+
+ public void setShardAuthor(String shardAuthor) {
+ this.shardAuthor = shardAuthor;
+ }
+
+ public void setShardAuthorEmail(String shardAuthorEmail) {
+ this.shardAuthorEmail = shardAuthorEmail;
+ }
+
+ @Override
+ protected void addAdditionPropertiesToCodeGenModel(CodegenModel codegenModel, Schema schema) {
+ final Schema additionalProperties = getAdditionalProperties(schema);
+
+ if (additionalProperties != null) {
+ codegenModel.additionalPropertiesType = getSchemaType(additionalProperties);
+ }
+ }
+
+ @Override
+ public Map postProcessOperationsWithModels(Map objs, List allModels) {
+ objs = super.postProcessOperationsWithModels(objs, allModels);
+ Map operations = (Map) objs.get("operations");
+ HashMap modelMaps = new HashMap();
+ HashMap processedModelMaps = new HashMap();
+
+ for (Object o : allModels) {
+ HashMap h = (HashMap) o;
+ CodegenModel m = (CodegenModel) h.get("model");
+ modelMaps.put(m.classname, m);
+ }
+
+ List operationList = (List) operations.get("operation");
+ for (CodegenOperation op : operationList) {
+ for (CodegenParameter p : op.allParams) {
+ p.vendorExtensions.put("x-crystal-example", constructExampleCode(p, modelMaps, processedModelMaps));
+ }
+ processedModelMaps.clear();
+ for (CodegenParameter p : op.requiredParams) {
+ p.vendorExtensions.put("x-crystal-example", constructExampleCode(p, modelMaps, processedModelMaps));
+ }
+ processedModelMaps.clear();
+ for (CodegenParameter p : op.optionalParams) {
+ p.vendorExtensions.put("x-crystal-example", constructExampleCode(p, modelMaps, processedModelMaps));
+ }
+ processedModelMaps.clear();
+ for (CodegenParameter p : op.bodyParams) {
+ p.vendorExtensions.put("x-crystal-example", constructExampleCode(p, modelMaps, processedModelMaps));
+ }
+ processedModelMaps.clear();
+ for (CodegenParameter p : op.pathParams) {
+ p.vendorExtensions.put("x-crystal-example", constructExampleCode(p, modelMaps, processedModelMaps));
+ }
+ processedModelMaps.clear();
+ }
+
+ return objs;
+ }
+
+ private String constructExampleCode(CodegenParameter codegenParameter, HashMap modelMaps, HashMap processedModelMap) {
+ if (codegenParameter.isArray) { // array
+ return "[" + constructExampleCode(codegenParameter.items, modelMaps, processedModelMap) + "]";
+ } else if (codegenParameter.isMap) {
+ return "{ key: " + constructExampleCode(codegenParameter.items, modelMaps, processedModelMap) + "}";
+ } else if (codegenParameter.isPrimitiveType) { // primitive type
+ if (codegenParameter.isEnum) {
+ // When inline enum, set example to first allowable value
+ List values = (List) codegenParameter.allowableValues.get("values");
+ codegenParameter.example = String.valueOf(values.get(0));
+ }
+ if (codegenParameter.isString || "String".equalsIgnoreCase(codegenParameter.baseType)) {
+ if (!StringUtils.isEmpty(codegenParameter.example) && !"null".equals(codegenParameter.example)) {
+ return "'" + codegenParameter.example + "'";
+ }
+ return "'" + codegenParameter.paramName + "_example'";
+ } else if (codegenParameter.isBoolean) { // boolean
+ if (Boolean.parseBoolean(codegenParameter.example)) {
+ return "true";
+ }
+ return "false";
+ } else if (codegenParameter.isUri) {
+ if (!StringUtils.isEmpty(codegenParameter.example) && !"null".equals(codegenParameter.example)) {
+ return "'" + codegenParameter.example + "'";
+ }
+ return "'https://example.com'";
+ } else if (codegenParameter.isDateTime) {
+ if (!StringUtils.isEmpty(codegenParameter.example) && !"null".equals(codegenParameter.example)) {
+ return "Time.parse('" + codegenParameter.example + "')";
+ }
+ return "Time.now";
+ } else if (codegenParameter.isDate) {
+ if (!StringUtils.isEmpty(codegenParameter.example) && !"null".equals(codegenParameter.example)) {
+ return "Date.parse('" + codegenParameter.example + "')";
+ }
+ return "Date.today";
+ } else if (codegenParameter.isFile) {
+ return "File.new('/path/to/some/file')";
+ } else if (codegenParameter.isInteger) {
+ if (!StringUtils.isEmpty(codegenParameter.example) && !"null".equals(codegenParameter.example)) {
+ return codegenParameter.example;
+ }
+ return "37";
+ } else { // number
+ if (!StringUtils.isEmpty(codegenParameter.example) && !"null".equals(codegenParameter.example)) {
+ return codegenParameter.example;
+ }
+ return "3.56";
+ }
+ } else { // model
+ // look up the model
+ if (modelMaps.containsKey(codegenParameter.dataType)) {
+ return constructExampleCode(modelMaps.get(codegenParameter.dataType), modelMaps, processedModelMap);
+ } else {
+ //LOGGER.error("Error in constructing examples. Failed to look up the model " + codegenParameter.dataType);
+ return "TODO";
+ }
+ }
+ }
+
+ private String constructExampleCode(CodegenProperty codegenProperty, HashMap modelMaps, HashMap processedModelMap) {
+ if (codegenProperty.isArray) { // array
+ return "[" + constructExampleCode(codegenProperty.items, modelMaps, processedModelMap) + "]";
+ } else if (codegenProperty.isMap) {
+ return "{ key: " + constructExampleCode(codegenProperty.items, modelMaps, processedModelMap) + "}";
+ } else if (codegenProperty.isPrimitiveType) { // primitive type
+ if (codegenProperty.isEnum) {
+ // When inline enum, set example to first allowable value
+ List values = (List) codegenProperty.allowableValues.get("values");
+ codegenProperty.example = String.valueOf(values.get(0));
+ }
+ if (codegenProperty.isString || "String".equalsIgnoreCase(codegenProperty.baseType)) {
+ if (!StringUtils.isEmpty(codegenProperty.example) && !"null".equals(codegenProperty.example)) {
+ return "'" + codegenProperty.example + "'";
+ } else {
+ return "'" + codegenProperty.name + "_example'";
+ }
+ } else if (codegenProperty.isBoolean) { // boolean
+ if (Boolean.parseBoolean(codegenProperty.example)) {
+ return "true";
+ } else {
+ return "false";
+ }
+ } else if (codegenProperty.isUri) {
+ if (!StringUtils.isEmpty(codegenProperty.example) && !"null".equals(codegenProperty.example)) {
+ return "'" + codegenProperty.example + "'";
+ }
+ return "'https://example.com'";
+ } else if (codegenProperty.isDateTime) {
+ if (!StringUtils.isEmpty(codegenProperty.example) && !"null".equals(codegenProperty.example)) {
+ return "Time.parse('" + codegenProperty.example + "')";
+ }
+ return "Time.now";
+ } else if (codegenProperty.isDate) {
+ if (!StringUtils.isEmpty(codegenProperty.example) && !"null".equals(codegenProperty.example)) {
+ return "Date.parse('" + codegenProperty.example + "')";
+ }
+ return "Date.today";
+ } else if (codegenProperty.isFile) {
+ return "File.new('/path/to/some/file')";
+ } else if (codegenProperty.isInteger) {
+ if (!StringUtils.isEmpty(codegenProperty.example) && !"null".equals(codegenProperty.example)) {
+ return codegenProperty.example;
+ }
+ return "37";
+ } else { // number
+ if (!StringUtils.isEmpty(codegenProperty.example) && !"null".equals(codegenProperty.example)) {
+ return codegenProperty.example;
+ }
+ return "3.56";
+ }
+ } else { // model
+ // look up the model
+ if (modelMaps.containsKey(codegenProperty.dataType)) {
+ return constructExampleCode(modelMaps.get(codegenProperty.dataType), modelMaps, processedModelMap);
+ } else {
+ //LOGGER.error("Error in constructing examples. Failed to look up the model " + codegenParameter.dataType);
+ return "TODO";
+ }
+ }
+ }
+
+ private String constructExampleCode(CodegenModel codegenModel, HashMap modelMaps, HashMap processedModelMap) {
+ // break infinite recursion. Return, in case a model is already processed in the current context.
+ String model = codegenModel.name;
+ if (processedModelMap.containsKey(model)) {
+ int count = processedModelMap.get(model);
+ if (count == 1) {
+ processedModelMap.put(model, 2);
+ } else if (count == 2) {
+ return "";
+ } else {
+ throw new RuntimeException("Invalid count when constructing example: " + count);
+ }
+ } else if (codegenModel.isEnum) {
+ List> enumVars = (List>) codegenModel.allowableValues.get("enumVars");
+ return moduleName + "::" + codegenModel.classname + "::" + enumVars.get(0).get("name");
+ } else if (codegenModel.oneOf != null && !codegenModel.oneOf.isEmpty()) {
+ String subModel = (String) codegenModel.oneOf.toArray()[0];
+ String oneOf = constructExampleCode(modelMaps.get(subModel), modelMaps, processedModelMap);
+ return oneOf;
+ } else {
+ processedModelMap.put(model, 1);
+ }
+
+ List propertyExamples = new ArrayList<>();
+ for (CodegenProperty codegenProperty : codegenModel.requiredVars) {
+ propertyExamples.add(codegenProperty.name + ": " + constructExampleCode(codegenProperty, modelMaps, processedModelMap));
+ }
+ String example = moduleName + "::" + toModelName(model) + ".new";
+ if (!propertyExamples.isEmpty()) {
+ example += "({" + StringUtils.join(propertyExamples, ", ") + "})";
+ }
+ return example;
+ }
+
+ @Override
+ public String escapeReservedWord(String name) {
+ if (this.reservedWordsMappings().containsKey(name)) {
+ return this.reservedWordsMappings().get(name);
+ }
+ return "_" + name;
+ }
+
+ @Override
+ public String getTypeDeclaration(Schema schema) {
+ if (ModelUtils.isArraySchema(schema)) {
+ Schema inner = ((ArraySchema) schema).getItems();
+ return getSchemaType(schema) + "(" + getTypeDeclaration(inner) + ")";
+ } else if (ModelUtils.isMapSchema(schema)) {
+ Schema inner = getAdditionalProperties(schema);
+ return getSchemaType(schema) + "(String, " + getTypeDeclaration(inner) + ")";
+ }
+
+ return super.getTypeDeclaration(schema);
+ }
+
+ @Override
+ public String toInstantiationType(Schema schema) {
+ if (ModelUtils.isMapSchema(schema)) {
+ return instantiationTypes.get("map");
+ } else if (ModelUtils.isArraySchema(schema)) {
+ String parentType;
+ if (ModelUtils.isSet(schema)) {
+ parentType = "set";
+ } else {
+ parentType = "array";
+ }
+ return instantiationTypes.get(parentType);
+ }
+ return super.toInstantiationType(schema);
+ }
+
+ @Override
+ public String toDefaultValue(Schema p) {
+ p = ModelUtils.getReferencedSchema(this.openAPI, p);
+ if (ModelUtils.isIntegerSchema(p) || ModelUtils.isNumberSchema(p) || ModelUtils.isBooleanSchema(p)) {
+ if (p.getDefault() != null) {
+ return p.getDefault().toString();
+ }
+ } else if (ModelUtils.isStringSchema(p)) {
+ if (p.getDefault() != null) {
+ if (p.getDefault() instanceof Date) {
+ Date date = (Date) p.getDefault();
+ LocalDate localDate = date.toInstant().atZone(ZoneId.systemDefault()).toLocalDate();
+ return "Date.parse(\"" + String.format(Locale.ROOT, localDate.toString(), "") + "\")";
+ } else if (p.getDefault() instanceof java.time.OffsetDateTime) {
+ return "Time.parse(\"" + String.format(Locale.ROOT, ((java.time.OffsetDateTime) p.getDefault()).atZoneSameInstant(ZoneId.systemDefault()).toString(), "") + "\")";
+ } else {
+ return "'" + escapeText((String) p.getDefault()) + "'";
+ }
+ }
+ }
+
+ return null;
+ }
+
+ @Override
+ public String toEnumDefaultValue(String value, String datatype) {
+ return datatype + "::" + value;
+ }
+
+ @Override
+ public String toVarName(final String name) {
+ String varName;
+ // sanitize name
+ varName = sanitizeName(name);
+ // if it's all uppper case, convert to lower case
+ if (name.matches("^[A-Z_]*$")) {
+ varName = varName.toLowerCase(Locale.ROOT);
+ }
+
+ // camelize (lower first character) the variable name
+ // petId => pet_id
+ varName = underscore(varName);
+
+ // for reserved word or word starting with number, append _
+ if (isReservedWord(varName) || varName.matches("^\\d.*")) {
+ varName = escapeReservedWord(varName);
+ }
+
+ return varName;
+ }
+
+ public String toRegularExpression(String pattern) {
+ return addRegularExpressionDelimiter(pattern);
+ }
+
+ @Override
+ public String toParamName(String name) {
+ // should be the same as variable name
+ return toVarName(name);
+ }
+
+ @Override
+ public String escapeQuotationMark(String input) {
+ // remove ' to avoid code injection
+ return input.replace("'", "");
+ }
+
+ @Override
+ public String escapeUnsafeCharacters(String input) {
+ return input.replace("=end", "=_end").replace("=begin", "=_begin").replace("#{", "\\#{");
+ }
+
+ @Override
+ public void postProcessFile(File file, String fileType) {
+ if (file == null) {
+ return;
+ }
+ String crystalPostProcessFile = System.getenv("CRYSTAL_POST_PROCESS_FILE");
+ if (StringUtils.isEmpty(crystalPostProcessFile)) {
+ return; // skip if CRYSTAL_POST_PROCESS_FILE env variable is not defined
+ }
+ // only process files with cr extension
+ if ("cr".equals(FilenameUtils.getExtension(file.toString()))) {
+ String command = crystalPostProcessFile + " " + file.toString();
+ try {
+ Process p = Runtime.getRuntime().exec(command);
+ int exitValue = p.waitFor();
+ if (exitValue != 0) {
+ BufferedReader br = new BufferedReader(new InputStreamReader(p.getErrorStream(), StandardCharsets.UTF_8));
+ StringBuilder sb = new StringBuilder();
+ String line;
+ while ((line = br.readLine()) != null) {
+ sb.append(line);
+ }
+ LOGGER.error("Error running the command ({}). Exit value: {}, Error output: {}", command, exitValue, sb.toString());
+ } else {
+ LOGGER.info("Successfully executed: " + command);
+ }
+ } catch (Exception e) {
+ LOGGER.error("Error running the command ({}). Exception: {}", command, e.getMessage());
+ }
+ }
+ }
+}
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/DartClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/DartClientCodegen.java
index 5ebe5d5f5f0..ce6c6234c38 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/DartClientCodegen.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/DartClientCodegen.java
@@ -472,13 +472,16 @@ public class DartClientCodegen extends DefaultCodegen {
}
if (schema.getDefault() != null) {
+ if (ModelUtils.isDateSchema(schema) || ModelUtils.isDateTimeSchema(schema)) {
+ // this is currently not supported and would create compile errors
+ return null;
+ }
if (ModelUtils.isStringSchema(schema)) {
return "'" + schema.getDefault().toString().replace("'", "\\'") + "'";
}
return schema.getDefault().toString();
- } else {
- return null;
}
+ return null;
}
@Override
@@ -555,6 +558,18 @@ public class DartClientCodegen extends DefaultCodegen {
}
}
}
+ for (CodegenParameter p : op.allParams) {
+ if (p.isContainer) {
+ final String type = p.isArray ? "array" : "map";
+ if (typeMapping().containsKey(type)) {
+ final String value = typeMapping().get(type);
+ // Also add container imports for parameters.
+ if (needToImport(value)) {
+ op.imports.add(value);
+ }
+ }
+ }
+ }
return op;
}
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/DartDioClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/DartDioClientCodegen.java
index ff934aa6161..582402c2251 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/DartDioClientCodegen.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/DartDioClientCodegen.java
@@ -126,6 +126,16 @@ public class DartDioClientCodegen extends DartClientCodegen {
@Override
public String toDefaultValue(Schema schema) {
if (schema.getDefault() != null) {
+ if (ModelUtils.isArraySchema(schema)) {
+ return "ListBuilder()";
+ }
+ if (ModelUtils.isMapSchema(schema)) {
+ return "MapBuilder()";
+ }
+ if (ModelUtils.isDateSchema(schema) || ModelUtils.isDateTimeSchema(schema)) {
+ // this is currently not supported and would create compile errors
+ return null;
+ }
if (ModelUtils.isStringSchema(schema)) {
return "'" + schema.getDefault().toString().replaceAll("'", "\\'") + "'";
}
@@ -279,6 +289,7 @@ public class DartDioClientCodegen extends DartClientCodegen {
Map operations = (Map) objs.get("operations");
List operationList = (List) operations.get("operation");
+ Set> serializers = new HashSet<>();
Set modelImports = new HashSet<>();
Set fullImports = new HashSet<>();
@@ -304,6 +315,13 @@ public class DartDioClientCodegen extends DartClientCodegen {
param.baseType = "MultipartFile";
param.dataType = "MultipartFile";
}
+ if (param.isContainer) {
+ final Map serializer = new HashMap<>();
+ serializer.put("isArray", param.isArray);
+ serializer.put("isMap", param.isMap);
+ serializer.put("baseType", param.baseType);
+ serializers.add(serializer);
+ }
}
op.vendorExtensions.put("x-is-json", isJson);
@@ -317,7 +335,7 @@ public class DartDioClientCodegen extends DartClientCodegen {
Set imports = new HashSet<>();
for (String item : op.imports) {
if (needToImport(item)) {
- if (importMapping().containsKey(item) && needToImport(item)) {
+ if (importMapping().containsKey(item)) {
fullImports.add(importMapping().get(item));
} else {
imports.add(underscore(item));
@@ -326,10 +344,19 @@ public class DartDioClientCodegen extends DartClientCodegen {
}
modelImports.addAll(imports);
op.imports = imports;
+
+ if (op.returnContainer != null) {
+ final Map serializer = new HashMap<>();
+ serializer.put("isArray", Objects.equals("array", op.returnContainer));
+ serializer.put("isMap", Objects.equals("map", op.returnContainer));
+ serializer.put("baseType", op.returnBaseType);
+ serializers.add(serializer);
+ }
}
objs.put("modelImports", modelImports);
objs.put("fullImports", fullImports);
+ objs.put("serializers", serializers);
return objs;
}
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/HaskellHttpClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/HaskellHttpClientCodegen.java
index 6c58e89d6d2..059d85cf2c8 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/HaskellHttpClientCodegen.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/HaskellHttpClientCodegen.java
@@ -135,7 +135,7 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
protected Set typeNames = new HashSet();
protected Set modelTypeNames = new HashSet();
- final private static Pattern JSON_MIME_PATTERN = Pattern.compile("(?i)application/.*json(;.*)?");
+ final private static Pattern CONTAINS_JSON_MIME_PATTERN = Pattern.compile("(?i)application/.*json(;.*)?");
public CodegenType getTag() {
return CodegenType.CLIENT;
@@ -1054,7 +1054,7 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
String mimeType = getMimeDataType(mediaType);
typeNames.add(mimeType);
m.put(X_MEDIA_DATA_TYPE, mimeType);
- if (isJsonMimeType(mediaType)) {
+ if (isJsonMimeType(mediaType) || ContainsJsonMimeType(mediaType)) {
m.put(X_MEDIA_IS_JSON, "true");
}
if (isWildcardMimeType(mediaType)) {
@@ -1461,4 +1461,7 @@ public class HaskellHttpClientCodegen extends DefaultCodegen implements CodegenC
}
}
}
+ static boolean ContainsJsonMimeType(String mime) {
+ return mime != null && CONTAINS_JSON_MIME_PATTERN.matcher(mime).matches();
+ }
}
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KtormSchemaCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KtormSchemaCodegen.java
new file mode 100644
index 00000000000..a21fe433bbf
--- /dev/null
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/KtormSchemaCodegen.java
@@ -0,0 +1,1222 @@
+/*
+ * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech)
+ *
+ * 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 org.openapitools.codegen.*;
+import org.openapitools.codegen.meta.features.*;
+import org.openapitools.codegen.meta.GeneratorMetadata;
+import org.openapitools.codegen.meta.Stability;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.util.*;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+import java.io.File;
+
+import io.swagger.v3.oas.models.media.IntegerSchema;
+import io.swagger.v3.parser.util.SchemaTypeUtil;
+
+import static org.openapitools.codegen.utils.StringUtils.*;
+
+// This code was almost entirely based on MySqlSchemaCodegen.
+
+@SuppressWarnings("unchecked")
+public class KtormSchemaCodegen extends AbstractKotlinCodegen {
+ static Logger LOGGER = LoggerFactory.getLogger(KtormSchemaCodegen.class);
+
+ public static final String VENDOR_EXTENSION_SCHEMA = "x-ktorm-schema";
+ public static final String DEFAULT_DATABASE_NAME = "defaultDatabaseName";
+ public static final String IMPORT_MODEL_PACKAGE_NAME = "importModelPackageName";
+ public static final String IDENTIFIER_NAMING_CONVENTION = "identifierNamingConvention";
+ public static final String PRIMARY_KEY_CONVENTION = "primaryKeyConvention";
+ public static final String ADD_SURROGATE_KEY = "addSurrogateKey";
+ public static final Integer IDENTIFIER_MAX_LENGTH = 255;
+
+ protected String importModelPackageName = "";
+ protected String defaultDatabaseName = "sqlite.db";
+ protected String databaseNamePrefix = "_", databaseNameSuffix = "";
+ protected String tableNamePrefix = "_", tableNameSuffix = "";
+ protected String columnNamePrefix = "_", columnNameSuffix = "";
+ protected String identifierNamingConvention = "original";
+ protected String primaryKeyConvention = "id";
+ protected boolean addSurrogateKey = false;
+
+ protected Map sqlTypeMapping = new HashMap();
+
+ // https://ktorm.liuwj.me/api-docs/me.liuwj.ktorm.schema/index.html
+ protected class SqlType {
+ protected static final String Blob = "blob";
+ protected static final String Boolean = "boolean";
+ protected static final String Bytes = "bytes";
+ protected static final String Date = "date";
+ protected static final String DateTime = "datetime";
+ protected static final String Decimal = "decimal";
+ protected static final String Double = "double";
+ protected static final String Float = "float";
+ protected static final String Enum = "enum";
+ protected static final String Int = "int";
+ protected static final String Long = "long";
+ protected static final String Text = "text";
+ protected static final String Varchar = "varchar";
+ protected static final String Json = "json";
+ }
+
+ public KtormSchemaCodegen() {
+ super();
+
+ generatorMetadata = GeneratorMetadata.newBuilder(generatorMetadata)
+ .stability(Stability.BETA)
+ .build();
+
+ modifyFeatureSet(features -> features
+ .includeDocumentationFeatures(DocumentationFeature.Readme)
+ .wireFormatFeatures(EnumSet.noneOf(WireFormatFeature.class))
+ .securityFeatures(EnumSet.noneOf(SecurityFeature.class))
+ .excludeGlobalFeatures(
+ GlobalFeature.XMLStructureDefinitions,
+ GlobalFeature.Callbacks,
+ GlobalFeature.LinkObjects,
+ GlobalFeature.ParameterStyling
+ )
+ .excludeSchemaSupportFeatures(
+ SchemaSupportFeature.Polymorphism
+ )
+ .clientModificationFeatures(EnumSet.noneOf(ClientModificationFeature.class))
+ );
+
+ // http://www.sqlite.org/draft/tokenreq.html
+ // https://sqlite.org/src/file/src/parse.y
+ setReservedWordsLowerCase(
+ Arrays.asList(
+ // SQL reserved words
+ "ABORT", "ACTION", "ADD", "AFTER", "ALL", "ALTER", "ALWAYS",
+ "ANALYZE", "AND", "ANY", "AS", "ASC", "ATTACH", "AUTOINCR",
+ "AUTOINCREMENT", "BEFORE", "BEGIN", "BETWEEN", "BITAND", "BITNOT",
+ "BITOR", "BLOB", "BY", "CASCADE", "CASE", "CAST", "CHECK",
+ "COLLATE", "COLUMN", "COMMA", "COMMIT", "CONCAT", "CONFLICT",
+ "CONSTRAINT", "CREATE", "CROSS", "CURRENT", "CURRENT_DATE",
+ "CURRENT_TIME", "CURRENT_TIMESTAMP", "DATABASE", "DEFAULT",
+ "DEFERRABLE", "DEFERRED", "DELETE", "DESC", "DETACH", "DISTINCT",
+ "DO", "DOT", "DROP", "EACH", "ELSE", "END", "EQ", "ESCAPE",
+ "EXCEPT", "EXCLUDE", "EXCLUSIVE", "EXISTS", "EXPLAIN", "FAIL",
+ "FILTER", "FIRST", "FLOAT", "FOLLOWING", "FOR", "FOREIGN", "FROM",
+ "FULL", "GE", "GENERATED", "GLOB", "GROUP", "GROUPS", "GT",
+ "HAVING", "ID", "IF", "IGNORE", "IMMEDIATE", "IN", "INDEX",
+ "INDEXED", "INITIALLY", "INNER", "INSERT", "INSTEAD", "INTEGER",
+ "INTERSECT", "INTO", "IS", "ISNULL", "JOIN", "KEY", "LAST", "LE",
+ "LEFT", "LIKE", "LIMIT", "LP", "LSHIFT", "LT", "MATCH", "MINUS",
+ "NATURAL", "NE", "NO", "NOT", "NOTHING", "NOTNULL", "NULL", "NULLS",
+ "OF", "OFFSET", "ON", "OR", "ORDER", "OTHERS", "OUTER", "OVER",
+ "PARTITION", "PLAN", "PLUS", "PRAGMA", "PRECEDING", "PRIMARY",
+ "QUERY", "RAISE", "RANGE", "RECURSIVE", "REFERENCES", "REGEXP",
+ "REINDEX", "RELEASE", "REM", "RENAME", "REPLACE", "RESTRICT",
+ "RIGHT", "ROLLBACK", "ROW", "ROWS", "RP", "RSHIFT", "SAVEPOINT",
+ "SELECT", "SET", "SLASH", "STAR", "STRING", "TABLE", "TEMP",
+ "TEMPORARY", "THEN", "TIES", "TO", "TRANSACTION", "TRIGGER",
+ "UNBOUNDED", "UNION", "UNIQUE", "UPDATE", "USING", "VACUUM",
+ "VALUES", "VARIABLE", "VIEW", "VIRTUAL", "WHEN", "WHERE",
+ "WINDOW", "WITH", "WITHOUT"
+ )
+ );
+
+ typeMapping = new HashMap();
+ typeMapping.put("string", "kotlin.String");
+ typeMapping.put("boolean", "kotlin.Boolean");
+ typeMapping.put("integer", "kotlin.Int");
+ typeMapping.put("float", "kotlin.Float");
+ typeMapping.put("long", "kotlin.Long");
+ typeMapping.put("double", "kotlin.Double");
+ typeMapping.put("ByteArray", "kotlin.ByteArray");
+ typeMapping.put("number", "java.math.BigDecimal");
+ typeMapping.put("date-time", "java.time.LocalDateTime");
+ typeMapping.put("date", "java.time.LocalDate");
+ typeMapping.put("file", "java.io.File");
+ typeMapping.put("array", "kotlin.Array");
+ typeMapping.put("list", "kotlin.collections.List");
+ typeMapping.put("set", "kotlin.collections.Set");
+ typeMapping.put("map", "kotlin.collections.Map");
+ typeMapping.put("object", "kotlin.Any");
+ typeMapping.put("binary", "kotlin.ByteArray");
+ typeMapping.put("Date", "java.time.LocalDate");
+ typeMapping.put("DateTime", "java.time.LocalDateTime");
+ //missing on baseclass
+ typeMapping.put("byte", "kotlin.Byte");
+ typeMapping.put("short", "kotlin.Short");
+ typeMapping.put("char", "kotlin.String");
+ typeMapping.put("real", "kotlin.Double");
+ typeMapping.put("UUID", "java.util.UUID"); //be explict
+ typeMapping.put("URI", "java.net.URI"); //be explict
+ typeMapping.put("decimal", "java.math.BigDecimal");
+ typeMapping.put("BigDecimal", "java.math.BigDecimal");
+ typeMapping.put("AnyType", "kotlin.Any");
+ typeMapping.put("password", "kotlin.String"); //nice to have
+
+ //mappings between kotlin and ktor
+ // ref: https://ktorm.liuwj.me/en/schema-definition.html
+ sqlTypeMapping.put("kotlin.String", SqlType.Text);
+ sqlTypeMapping.put("kotlin.Boolean", SqlType.Boolean);
+ sqlTypeMapping.put("kotlin.Byte", SqlType.Int);
+ sqlTypeMapping.put("kotlin.Short", SqlType.Int);
+ sqlTypeMapping.put("kotlin.Int", SqlType.Int);
+ sqlTypeMapping.put("kotlin.Long", SqlType.Long);
+ sqlTypeMapping.put("kotlin.Float", SqlType.Float);
+ sqlTypeMapping.put("kotlin.Double", SqlType.Double);
+ sqlTypeMapping.put("kotlin.ByteArray", SqlType.Blob);
+ sqlTypeMapping.put("kotlin.Array", SqlType.Blob);
+ sqlTypeMapping.put("kotlin.collections.List", SqlType.Blob);
+ sqlTypeMapping.put("kotlin.collections.Set", SqlType.Blob);
+ sqlTypeMapping.put("kotlin.collections.Map", SqlType.Blob);
+ sqlTypeMapping.put("kotlin.Any", SqlType.Blob);
+ sqlTypeMapping.put("java.io.File", SqlType.Blob);
+ sqlTypeMapping.put("java.math.BigDecimal", SqlType.Decimal);
+ sqlTypeMapping.put("java.time.LocalDateTime", SqlType.DateTime);
+ sqlTypeMapping.put("java.time.LocalDate", SqlType.Date);
+ sqlTypeMapping.put("java.util.UUID", SqlType.Text);
+ sqlTypeMapping.put("java.net.URI", SqlType.Text);
+
+ artifactId = "ktorm";
+ artifactVersion = "1.0.0";
+ packageName = "org.openapitools.database";
+
+ outputFolder = "generated-code" + File.separator + "kotlin-client";
+ embeddedTemplateDir = templateDir = "ktorm-schema";
+ modelTemplateFiles.put("model.mustache", ".kt");
+ modelDocTemplateFiles.put("model_doc.mustache", ".md");
+ modelPackage = packageName + ".models";
+ importModelPackageName = modelPackage;
+
+ // we don't clear clioptions from Kotlin
+ // cliOptions default redefinition need to be updated
+ updateOption(CodegenConstants.ARTIFACT_ID, artifactId);
+ updateOption(CodegenConstants.PACKAGE_NAME, packageName);
+ removeOption(CodegenConstants.API_SUFFIX);
+ removeOption(CodegenConstants.PARCELIZE_MODELS);
+ removeOption(CodegenConstants.SERIALIZABLE_MODEL);
+ removeOption(CodegenConstants.SERIALIZATION_LIBRARY);
+ addOption(DEFAULT_DATABASE_NAME, "Default database name for all queries", defaultDatabaseName);
+ addOption(IMPORT_MODEL_PACKAGE_NAME, "Package name of the imported models", importModelPackageName);
+ addOption(PRIMARY_KEY_CONVENTION, "Primary key naming convention", primaryKeyConvention);
+ addSwitch(ADD_SURROGATE_KEY, "Adds the surrogate key for all models that don't already have a primary key (named by the above convention)", addSurrogateKey);
+
+ // we used to snake_case table/column names, let's add this option
+ CliOption identifierNamingOpt = new CliOption(IDENTIFIER_NAMING_CONVENTION,
+ "Naming convention of Ktorm identifiers(table names and column names). This is not related to database name which is defined by " + DEFAULT_DATABASE_NAME + " option");
+
+ identifierNamingOpt.addEnum("original", "Do not transform original names")
+ .addEnum("snake_case", "Use snake_case names")
+ .setDefault("original");
+
+ cliOptions.add(identifierNamingOpt);
+
+ }
+
+ public CodegenType getTag() {
+ return CodegenType.SCHEMA;
+ }
+
+ public String getName() {
+ return "ktorm-schema";
+ }
+
+ public String getHelp() {
+ return "Generates a kotlin-ktorm schema (beta)";
+ }
+
+ @Override
+ public void processOpts() {
+ super.processOpts();
+
+ if (additionalProperties.containsKey(DEFAULT_DATABASE_NAME)) {
+ if (additionalProperties.get(DEFAULT_DATABASE_NAME).equals("")) {
+ additionalProperties.remove(DEFAULT_DATABASE_NAME);
+ } else {
+ setDefaultDatabaseName((String) additionalProperties.get(DEFAULT_DATABASE_NAME));
+ // default database name may be escaped, need to overwrite additional prop
+ additionalProperties.put(DEFAULT_DATABASE_NAME, getDefaultDatabaseName());
+ }
+ }
+
+ if (additionalProperties.containsKey(IDENTIFIER_NAMING_CONVENTION)) {
+ setIdentifierNamingConvention((String) additionalProperties.get(IDENTIFIER_NAMING_CONVENTION));
+ }
+
+ if (additionalProperties.containsKey(IMPORT_MODEL_PACKAGE_NAME)) {
+ setImportModelPackageName((String) additionalProperties.get(IMPORT_MODEL_PACKAGE_NAME));
+ }
+
+ if (additionalProperties.containsKey(PRIMARY_KEY_CONVENTION)) {
+ setPrimaryKeyConvention((String) additionalProperties.get(PRIMARY_KEY_CONVENTION));
+ }
+
+ if (additionalProperties.containsKey(ADD_SURROGATE_KEY)) {
+ setAddSurrogateKey(convertPropertyToBooleanAndWriteBack(ADD_SURROGATE_KEY));
+ }
+
+ // make model src path available in mustache template
+ additionalProperties.put("modelSrcPath", "./" + toSrcPath(modelPackage));
+
+ supportingFiles.add(new SupportingFile("README.mustache", "", "README.md"));
+ supportingFiles.add(new SupportingFile("build.gradle.mustache", "", "build.gradle"));
+ supportingFiles.add(new SupportingFile("settings.gradle.mustache", "", "settings.gradle"));
+ supportingFiles.add(new SupportingFile("ktorm_schema.mustache", "", "ktorm_schema.sql"));
+ }
+
+ @Override
+ public Map postProcessModels(Map objs) {
+ objs = super.postProcessModels(objs);
+
+ List models = (List) objs.get("models");
+ for (Object _mo : models) {
+ Map mo = (Map) _mo;
+ CodegenModel model = (CodegenModel) mo.get("model");
+ String modelName = model.getName();
+ String tableName = toTableName(modelName);
+ String modelDescription = model.getDescription();
+ Map modelVendorExtensions = model.getVendorExtensions();
+ Map ktormSchema = new HashMap();
+ Map tableDefinition = new HashMap();
+
+ if (getIdentifierNamingConvention().equals("snake_case") && !modelName.equals(tableName)) {
+ // add original name in table comment
+ String commentExtra = "Original model name - " + modelName + ".";
+ modelDescription = (modelDescription == null || modelDescription.isEmpty()) ? commentExtra : modelDescription + ". " + commentExtra;
+ }
+
+ if (modelVendorExtensions.containsKey(VENDOR_EXTENSION_SCHEMA)) {
+ // user already specified schema values
+ LOGGER.info("Found vendor extension in '" + modelName + "' model, autogeneration skipped");
+ } else {
+ modelVendorExtensions.put(VENDOR_EXTENSION_SCHEMA, ktormSchema);
+ ktormSchema.put("tableDefinition", tableDefinition);
+ tableDefinition.put("tblName", tableName);
+ tableDefinition.put("tblComment", modelDescription);
+ }
+
+ // check if we need to add the surrogate key
+ if (addSurrogateKey) {
+ boolean hasPrimaryKey = false;
+ for (CodegenProperty var : model.vars) {
+ if (var.getBaseName().equals(primaryKeyConvention)) {
+ hasPrimaryKey = true;
+ break;
+ }
+ }
+ if (!hasPrimaryKey) {
+ final IntegerSchema schema = new IntegerSchema().format(SchemaTypeUtil.INTEGER64_FORMAT);
+ CodegenProperty cp = super.fromProperty(primaryKeyConvention, schema);
+ cp.setRequired(true);
+ model.vars.add(0, cp);
+ model.allVars.add(0, cp);
+ model.requiredVars.add(0, cp);
+ model.readWriteVars.add(0, cp);
+ postProcessModelProperty(model, cp);
+ objs = super.postProcessModels(objs);
+ }
+ }
+ }
+
+ return objs;
+ }
+
+ private class KtormSchema extends HashMap {
+ private static final long serialVersionUID = -9159755928980443880L;
+ }
+
+ /**
+ * Processes each model's property mapped
+ *
+ * @param model codegen model
+ * @param property model's property
+ */
+ @Override
+ public void postProcessModelProperty(CodegenModel model, CodegenProperty property) {
+ Map relationDefinition = new HashMap();
+ Map vendorExtensions = property.getVendorExtensions();
+ KtormSchema ktormSchema = new KtormSchema();
+ String baseName = property.getBaseName();
+ String colName = toColumnName(baseName);
+ String description = property.getDescription();
+ String dataType = property.getDataType();
+ String dataFormat = property.getDataFormat();
+ String actualType = toColumnType(dataType, dataFormat);
+
+ if (vendorExtensions.containsKey(VENDOR_EXTENSION_SCHEMA)) {
+ // user already specified schema values
+ LOGGER.info("Found vendor extension in '" + baseName + "' property, autogeneration skipped");
+ return;
+ }
+
+ vendorExtensions.put(VENDOR_EXTENSION_SCHEMA, ktormSchema);
+
+ if (getIdentifierNamingConvention().equals("snake_case") && !baseName.equals(colName)) {
+ // add original name in column comment
+ String commentExtra = "Original param name - " + baseName + ".";
+ description = (description == null || description.isEmpty()) ? commentExtra : description + ". " + commentExtra;
+ }
+
+ switch (actualType) {
+ case SqlType.Boolean:
+ processBooleanTypeProperty(model, property, description, ktormSchema);
+ break;
+ case SqlType.Int:
+ case SqlType.Long:
+ processIntegerTypeProperty(model, property, description, ktormSchema);
+ break;
+ case SqlType.Float:
+ case SqlType.Double:
+ case SqlType.Decimal:
+ processRealTypeProperty(model, property, description, ktormSchema);
+ break;
+ case SqlType.Blob:
+ case SqlType.Text:
+ case SqlType.Varchar:
+ case SqlType.Bytes:
+ processStringTypeProperty(model, property, description, ktormSchema);
+ break;
+ case SqlType.Date:
+ case SqlType.DateTime:
+ processDateTypeProperty(model, property, description, ktormSchema);
+ break;
+ case SqlType.Json:
+ processJsonTypeProperty(model, property, description, ktormSchema);
+ break;
+ default:
+ processUnknownTypeProperty(model, property, description, ktormSchema);
+ }
+
+ if (processForeignKey(model, property, relationDefinition)) {
+ ktormSchema.put("relationDefinition", relationDefinition);
+ ktormSchema.put("relation", true);
+ }
+ }
+
+ /**
+ * Processes each model's property mapped to integer type and adds related vendor extensions
+ *
+ * @param model codegen model
+ * @param property model's property
+ * @param description property custom description
+ * @param ktormSchema schema
+ */
+ public void processIntegerTypeProperty(CodegenModel model, CodegenProperty property, String description, KtormSchema ktormSchema) {
+ Map columnDefinition = new HashMap();
+ String baseName = property.getBaseName();
+ String colName = toColumnName(baseName);
+ String dataType = property.getDataType();
+ String dataFormat = property.getDataFormat();
+ String actualType = toColumnType(dataType, dataFormat);
+
+ String minimum = property.getMinimum();
+ String maximum = property.getMaximum();
+ boolean exclusiveMinimum = property.getExclusiveMinimum();
+ boolean exclusiveMaximum = property.getIExclusiveMaximum();
+ boolean unsigned = false;
+ Boolean isUuid = property.isUuid;
+
+ Long cmin = (minimum != null) ? Long.parseLong(minimum) : null;
+ Long cmax = (maximum != null) ? Long.parseLong(maximum) : null;
+ if (exclusiveMinimum && cmin != null) cmin += 1;
+ if (exclusiveMaximum && cmax != null) cmax -= 1;
+ if (cmin != null && cmin >= 0) {
+ unsigned = true;
+ }
+ long min = (cmin != null) ? cmin : Long.MIN_VALUE;
+ long max = (cmax != null) ? cmax : Long.MAX_VALUE;
+ long actualMin = Math.min(min, max); // sometimes min and max values can be mixed up
+ long actualMax = Math.max(min, max); // sometimes only minimum specified and it can be pretty high
+
+ ktormSchema.put("columnDefinition", columnDefinition);
+ columnDefinition.put("colName", colName);
+ columnDefinition.put("colType", actualType);
+ columnDefinition.put("colKotlinType", dataType);
+ columnDefinition.put("colUnsigned", unsigned);
+ columnDefinition.put("colMinimum", actualMin);
+ columnDefinition.put("colMaximum", actualMax);
+ columnDefinition.put("colIsUuid", isUuid);
+
+ processTypeArgs(dataType, dataFormat, actualMin, actualMax, columnDefinition);
+ processNullAndDefault(model, property, description, columnDefinition);
+ }
+
+ /**
+ * Processes each model's property mapped to some real type and adds related vendor extensions
+ *
+ * @param model codegen model
+ * @param property model's property
+ * @param description property custom description
+ * @param ktormSchema schema
+ */
+ public void processRealTypeProperty(CodegenModel model, CodegenProperty property, String description, KtormSchema ktormSchema) {
+ Map columnDefinition = new HashMap();
+ String baseName = property.getBaseName();
+ String colName = toColumnName(baseName);
+ String dataType = property.getDataType();
+ String dataFormat = property.getDataFormat();
+ String actualType = toColumnType(dataType, dataFormat);
+
+ String minimum = property.getMinimum();
+ String maximum = property.getMaximum();
+ boolean exclusiveMinimum = property.getExclusiveMinimum();
+ boolean exclusiveMaximum = property.getIExclusiveMaximum();
+
+ Float cmin = (minimum != null) ? Float.parseFloat(minimum) : null;
+ Float cmax = (maximum != null) ? Float.parseFloat(maximum) : null;
+ if (exclusiveMinimum && cmin != null) cmin += 1;
+ if (exclusiveMaximum && cmax != null) cmax -= 1;
+ Float min = (cmin != null) ? cmin : Float.MIN_VALUE;
+ Float max = (cmax != null) ? cmax : Float.MAX_VALUE;
+ Float actualMin = Math.min(min, max); // sometimes min and max values can be mixed up
+ Float actualMax = Math.max(min, max); // sometimes only minimum specified and it can be pretty high
+
+ ktormSchema.put("columnDefinition", columnDefinition);
+ columnDefinition.put("colName", colName);
+ columnDefinition.put("colType", actualType);
+ columnDefinition.put("colKotlinType", dataType);
+ columnDefinition.put("colMinimum", actualMin);
+ columnDefinition.put("colMaximum", actualMax);
+
+ processTypeArgs(dataType, dataFormat, actualMin, actualMax, columnDefinition);
+ processNullAndDefault(model, property, description, columnDefinition);
+ }
+
+ /**
+ * Processes each model's property mapped to boolean type and adds related vendor extensions
+ *
+ * @param model codegen model
+ * @param property model's property
+ * @param description property custom description
+ * @param ktormSchema schema
+ */
+ public void processBooleanTypeProperty(CodegenModel model, CodegenProperty property, String description, KtormSchema ktormSchema) {
+ Map columnDefinition = new HashMap();
+ String baseName = property.getBaseName();
+ String colName = toColumnName(baseName);
+ String dataType = property.getDataType();
+ String dataFormat = property.getDataFormat();
+ String actualType = toColumnType(dataType, dataFormat);
+
+ ktormSchema.put("columnDefinition", columnDefinition);
+ columnDefinition.put("colName", colName);
+ columnDefinition.put("colType", actualType);
+ columnDefinition.put("colKotlinType", dataType);
+
+ processTypeArgs(dataType, dataFormat, 0, 1, columnDefinition);
+ processNullAndDefault(model, property, description, columnDefinition);
+ }
+
+ /**
+ * Processes each model's property mapped to string type and adds related vendor extensions
+ *
+ * @param model codegen model
+ * @param property model's property
+ * @param description property custom description
+ * @param ktormSchema schema
+ */
+ public void processStringTypeProperty(CodegenModel model, CodegenProperty property, String description, KtormSchema ktormSchema) {
+ Map columnDefinition = new HashMap();
+ String baseName = property.getBaseName();
+ String colName = toColumnName(baseName);
+ String dataType = property.getDataType();
+ String dataFormat = property.getDataFormat();
+ String actualType = toColumnType(dataType, dataFormat);
+
+ Integer minLength = property.getMinLength();
+ Integer maxLength = property.getMaxLength();
+
+ Integer min = (minLength != null) ? minLength : 0;
+ Integer max = (maxLength != null) ? maxLength : Integer.MAX_VALUE;
+ int actualMin = Math.min(min, max); // sometimes min and max values can be mixed up
+ int actualMax = Math.max(min, max); // sometimes only minimum specified and it can be pretty high
+
+ ktormSchema.put("columnDefinition", columnDefinition);
+ columnDefinition.put("colName", colName);
+ columnDefinition.put("colType", actualType);
+ columnDefinition.put("colKotlinType", dataType);
+ if (actualMin != 0) {
+ columnDefinition.put("colMinimum", actualMin);
+ }
+ if (actualMax != Integer.MAX_VALUE) {
+ columnDefinition.put("colMaximum", actualMax);
+ }
+
+ processTypeArgs(dataType, dataFormat, actualMin, actualMax, columnDefinition);
+ processNullAndDefault(model, property, description, columnDefinition);
+ }
+
+ /**
+ * Processes each model's property mapped to date type and adds related vendor extensions
+ *
+ * @param model codegen model
+ * @param property model's property
+ * @param description property custom description
+ * @param ktormSchema schema
+ */
+ public void processDateTypeProperty(CodegenModel model, CodegenProperty property, String description, KtormSchema ktormSchema) {
+ Map columnDefinition = new HashMap();
+ String baseName = property.getBaseName();
+ String colName = toColumnName(baseName);
+ String dataType = property.getDataType();
+ String dataFormat = property.getDataFormat();
+ String actualType = toColumnType(dataType, dataFormat);
+
+ ktormSchema.put("columnDefinition", columnDefinition);
+ columnDefinition.put("colName", colName);
+ columnDefinition.put("colType", actualType);
+ columnDefinition.put("colKotlinType", dataType);
+
+ processTypeArgs(dataType, dataFormat, null, null, columnDefinition);
+ processNullAndDefault(model, property, description, columnDefinition);
+ }
+
+ /**
+ * Processes each model's property mapped to JSON type and adds related vendor extensions
+ *
+ * @param model codegen model
+ * @param property model's property
+ * @param description property custom description
+ * @param ktormSchema schema
+ */
+ public void processJsonTypeProperty(CodegenModel model, CodegenProperty property, String description, KtormSchema ktormSchema) {
+ Map columnDefinition = new HashMap();
+ String baseName = property.getBaseName();
+ String colName = toColumnName(baseName);
+ String dataType = property.getDataType();
+ String dataFormat = property.getDataFormat();
+ String actualType = toColumnType(dataType, dataFormat);
+
+ ktormSchema.put("columnDefinition", columnDefinition);
+ columnDefinition.put("colName", colName);
+ columnDefinition.put("colType", actualType);
+ columnDefinition.put("colKotlinType", dataType);
+
+ processTypeArgs(dataType, dataFormat, null, null, columnDefinition);
+ processNullAndDefault(model, property, description, columnDefinition);
+ }
+
+ /**
+ * Processes each model's property not mapped to any type and adds related
+ * vendor extensions Most of time it's related to referenced properties eg.
+ * \Model\User
+ *
+ * @param model codegen model
+ * @param property model's property
+ * @param description property custom description
+ * @param ktormSchema schema
+ */
+ public void processUnknownTypeProperty(CodegenModel model, CodegenProperty property, String description, KtormSchema ktormSchema) {
+ Map columnDefinition = new HashMap();
+ String baseName = property.getBaseName();
+ String colName = toColumnName(baseName);
+ String dataType = property.getDataType();
+ String dataFormat = property.getDataFormat();
+ String actualType = toColumnType(dataType, dataFormat);
+
+ ktormSchema.put("columnDefinition", columnDefinition);
+ columnDefinition.put("colName", colName);
+ columnDefinition.put("colType", actualType);
+ columnDefinition.put("colKotlinType", dataType);
+
+ processTypeArgs(dataType, dataFormat, null, null, columnDefinition);
+ processNullAndDefault(model, property, description, columnDefinition);
+ }
+
+ /**
+ * Processes each model's property type arguments definitions
+ *
+ * @param dataType the choosen sql type
+ * @param dataFormat the choosen sql format
+ * @param min the minimum value, if specified, in the target type
+ * @param max the maximum value, if specified, in the target type
+ * @param columnDefinition resulting column definition dictionary
+ */
+ public void processTypeArgs(String dataType, String dataFormat, Object min, Object max, Map columnDefinition) {
+ HashMap a = new HashMap();
+ SqlTypeArgs args = new SqlTypeArgs();
+ toColumnTypeArgs(dataType, dataFormat, min, max, args);
+ a.put("isPrimitive", args.isPrimitive);
+ a.put("isNumeric", args.isNumeric);
+ a.put("isBoolean", args.isBoolean);
+ a.put("isInteger", args.isInteger);
+ a.put("isFloat", args.isFloat);
+ a.put("isDecimal", args.isDecimal);
+ a.put("isString", args.isString);
+ a.put("isDate", args.isDate);
+ a.put("isDateTime", args.isDateTime);
+ a.put("isBlob", args.isBlob);
+ a.put("isJson", args.isJson);
+ a.put("isNull", args.isNull);
+ //as we are using sqlite, it is not implemented for now
+ //columnDefinition.put("colTypeArgs", a);
+ columnDefinition.put("colPrimaryKey", isPrimaryKey(columnDefinition));
+ }
+
+ /**
+ * Processes each model's property null/default definitions
+ *
+ * @param model model's name
+ * @param property model's property
+ * @param description property's customized description
+ * @param columnDefinition resulting column definition dictionary
+ */
+ public void processNullAndDefault(CodegenModel model, CodegenProperty property, String description, Map columnDefinition) {
+ String baseName = property.getBaseName();
+ Boolean required = property.getRequired();
+ String dataType = property.getDataType();
+ String dataFormat = property.getDataFormat();
+ String defaultValue = property.getDefaultValue();
+ if (Boolean.TRUE.equals(required)) {
+ columnDefinition.put("colNotNull", true);
+ } else {
+ columnDefinition.put("colNotNull", false);
+ try {
+ columnDefinition.put("colDefault", toColumnTypeDefault(defaultValue, dataType, dataFormat));
+ } catch (RuntimeException exception) {
+ LOGGER.warn("Property '" + baseName + "' of model '" + model.getName() + "' mapped to data type which doesn't support default value");
+ columnDefinition.put("colDefault", null);
+ }
+ }
+ if (description != null) {
+ columnDefinition.put("colComment", description);
+ }
+ }
+
+ /**
+ * Processes each model's property that relates to another model
+ *
+ * @param model model's name
+ * @param property model's property
+ * @param relationDefinition resulting relation definition dictionary
+ * @return did we create the foreign key section.
+ */
+ public boolean processForeignKey(CodegenModel model, CodegenProperty property, Map relationDefinition) {
+ String dataType = property.getDataType();
+ if (!property.isArray && !isRelation(dataType)) return false;
+
+ String modelName = model.getName();
+ String tryDataType = property.isArray ? property.items.dataType : property.dataType;
+ String tryDataFormat = property.isArray ? property.items.dataFormat : property.dataFormat;
+ Boolean isPrimitive = (tryDataType.startsWith("kotlin.") || tryDataType.startsWith("java."));
+ String propName = isPrimitive ? property.getName() : tryDataType;
+
+ String pkName = toTitleCase(toModelName(modelName));
+ String pkColName = toColumnName(pkName);
+ String fkName = toTitleCase(toModelName(propName));
+ String fkColName = toColumnName(fkName);
+ String relName = toModelName(camelize(modelName) + camelize(propName));
+ String relTblName = toTableName(relName);
+
+ final IntegerSchema pkSchema = new IntegerSchema().format(SchemaTypeUtil.INTEGER64_FORMAT);
+ String pkDataType = getSchemaType(pkSchema);
+ String pkDataFormat = pkSchema.getFormat();
+ String pkColType = toColumnType(pkDataType, pkDataFormat);
+ String fkDataType = isPrimitive ? tryDataType : pkDataType;
+ String fkDataFormat = isPrimitive ? tryDataFormat : pkDataFormat;
+ String fkColType = toColumnType(fkDataType, fkDataFormat);
+
+ SqlTypeArgs pkArgs = new SqlTypeArgs();
+ toColumnTypeArgs(pkDataType, pkDataFormat, null, null, pkArgs);
+ SqlTypeArgs fkArgs = new SqlTypeArgs();
+ toColumnTypeArgs(fkDataType, fkDataFormat, null, null, fkArgs);
+
+ relationDefinition.put("pkName", pkName);
+ relationDefinition.put("pkColName", pkColName);
+ relationDefinition.put("pkColType", pkColType);
+ relationDefinition.put("pkColKotlinType", pkDataType);
+ relationDefinition.put("pkIsNumeric", pkArgs.isNumeric);
+ relationDefinition.put("pkIsInteger", pkArgs.isInteger);
+ relationDefinition.put("pkIsString", pkArgs.isString);
+ relationDefinition.put("pkIsPrimitive", pkArgs.isPrimitive);
+
+ relationDefinition.put("fkName", fkName);
+ relationDefinition.put("fkColName", fkColName);
+ relationDefinition.put("fkColType", fkColType);
+ relationDefinition.put("fkColKotlinType", fkDataType);
+ relationDefinition.put("fkIsNumeric", fkArgs.isNumeric);
+ relationDefinition.put("fkIsInteger", fkArgs.isInteger);
+ relationDefinition.put("fkIsString", fkArgs.isString);
+ relationDefinition.put("fkIsPrimitive", fkArgs.isPrimitive);
+
+ relationDefinition.put("relName", relName);
+ relationDefinition.put("relTblName", relTblName);
+
+ return true;
+ }
+
+ private String toTitleCase(final String input) {
+ return input.substring(0, 1).toLowerCase(Locale.ROOT) + input.substring(1);
+ }
+
+ private class SqlTypeArgs {
+ // type classes
+ public boolean isPrimitive;
+ public boolean isNumeric;
+ // specific types
+ public boolean isBoolean;
+ public boolean isInteger;
+ public boolean isFloat;
+ public boolean isDecimal;
+ public boolean isString;
+ public boolean isDate;
+ public boolean isDateTime;
+ public boolean isBlob;
+ public boolean isJson;
+ // special args
+ public boolean isNull;
+ }
+
+ /**
+ * Checks if the model type should be a relationship instead.
+ *
+ * @param columnDefinition resulting column definition dictionary
+ * @return is a relation
+ */
+ private boolean isPrimaryKey(Map columnDefinition) {
+ String colName = (String) columnDefinition.get("colName");
+ return colName.equals(primaryKeyConvention);
+ }
+
+ /**
+ * Checks if the model type should be a relationship instead.
+ *
+ * @param dataType type name
+ * @return is a relation
+ */
+ private boolean isRelation(String dataType) {
+ String sqlType = sqlTypeMapping.getOrDefault(dataType, "").toLowerCase(Locale.ROOT);
+ switch (sqlType) {
+ case SqlType.Boolean:
+ case SqlType.Int:
+ case SqlType.Long:
+ case SqlType.Float:
+ case SqlType.Double:
+ case SqlType.Decimal:
+ case SqlType.Text:
+ case SqlType.Varchar:
+ case SqlType.Date:
+ case SqlType.DateTime:
+ case SqlType.Blob:
+ case SqlType.Json:
+ return false;
+ default:
+ // If its explicitly configured kotlin.* and java.* types.
+ if (dataType.startsWith("kotlin.") || dataType.startsWith("java.")) {
+ // We just have to serialize it.
+ return false;
+ }
+ // Otherwise we assume this is a legitimate model name and it becomes a foreign key.
+ return true;
+ }
+ }
+
+ /**
+ * Generates codegen type mapping between ktor and sqlite
+ * Ref: http://www.sqlite.org/draft/datatype3.html
+ *
+ * @param dataType type name
+ * @param dataFormat type format
+ * @return generated codegen type
+ */
+ private String toColumnType(String dataType, String dataFormat) {
+ String sqlType = sqlTypeMapping.getOrDefault(dataType, "").toLowerCase(Locale.ROOT);
+ switch (sqlType) {
+ case SqlType.Boolean:
+ case SqlType.Int:
+ case SqlType.Long:
+ case SqlType.Float:
+ case SqlType.Double:
+ case SqlType.Decimal:
+ case SqlType.Text:
+ case SqlType.Varchar:
+ case SqlType.Date:
+ case SqlType.DateTime:
+ case SqlType.Blob:
+ case SqlType.Json:
+ return sqlType;
+ default:
+ return isRelation(dataType) ? SqlType.Long : SqlType.Blob;
+ }
+ }
+
+ /**
+ * Generates codegen type argument mapping between ktor and sqlite
+ * Ref: http://www.sqlite.org/draft/datatype3.html
+ *
+ * @param dataType type name
+ * @param dataFormat type format
+ * @return generated codegen type
+ */
+ private void toColumnTypeArgs(String dataType, String dataFormat, Object min, Object max, SqlTypeArgs args) {
+ String sqlType = toColumnType(dataType, dataFormat);
+ switch (sqlType) {
+ case SqlType.Boolean:
+ case SqlType.Int:
+ case SqlType.Long:
+ case SqlType.Float:
+ case SqlType.Double:
+ case SqlType.Decimal:
+ case SqlType.Text:
+ case SqlType.Varchar:
+ case SqlType.Date:
+ case SqlType.DateTime:
+ args.isPrimitive = true;
+ break;
+ case SqlType.Blob:
+ case SqlType.Json:
+ default:
+ }
+ switch (sqlType) {
+ case SqlType.Boolean:
+ case SqlType.Int:
+ case SqlType.Long:
+ case SqlType.Float:
+ case SqlType.Double:
+ case SqlType.Decimal:
+ args.isNumeric = true;
+ break;
+ case SqlType.Text:
+ case SqlType.Varchar:
+ case SqlType.Date:
+ case SqlType.DateTime:
+ case SqlType.Blob:
+ case SqlType.Json:
+ default:
+ }
+ switch (sqlType) {
+ case SqlType.Boolean:
+ args.isBoolean = true;
+ break;
+ case SqlType.Int:
+ case SqlType.Long:
+ args.isInteger = true;
+ break;
+ case SqlType.Float:
+ case SqlType.Double:
+ args.isFloat = true;
+ break;
+ case SqlType.Decimal:
+ args.isDecimal = true;
+ break;
+ case SqlType.Text:
+ case SqlType.Varchar:
+ args.isString = true;
+ break;
+ case SqlType.Date:
+ args.isDate = true;
+ break;
+ case SqlType.DateTime:
+ args.isDateTime = true;
+ break;
+ case SqlType.Blob:
+ args.isBlob = true;
+ break;
+ case SqlType.Json:
+ args.isJson = true;
+ break;
+ default:
+ args.isNull = true;
+ break;
+ }
+ }
+
+ /**
+ * Generates codegen default value mapping between ktor and sqlite
+ * Ref: http://www.sqlite.org/draft/lang_createtable.html, sec3.2
+ *
+ * @param defaultValue value
+ * @param dataType type name
+ * @param dataFormat type format
+ * @return generated codegen default
+ */
+ private Map toColumnTypeDefault(String defaultValue, String dataType, String dataFormat) {
+ String sqlType = toColumnType(dataType, dataFormat);
+ String sqlDefault = "";
+ if (defaultValue == null || defaultValue.toUpperCase(Locale.ROOT).equals("NULL")) {
+ sqlType = "null";
+ }
+ //special case for keywords if needed
+ switch (sqlType) {
+ case SqlType.Boolean:
+ case SqlType.Int:
+ case SqlType.Long:
+ case SqlType.Float:
+ case SqlType.Double:
+ case SqlType.Decimal:
+ case SqlType.Text:
+ case SqlType.Varchar:
+ case SqlType.Date:
+ case SqlType.DateTime:
+ sqlDefault = defaultValue;
+ case SqlType.Blob:
+ case SqlType.Json:
+ throw new RuntimeException("The BLOB and JSON data types cannot be assigned a default value");
+ default:
+ sqlDefault = "NULL";
+ }
+ Map args = new HashMap();
+ processTypeArgs(sqlType, null, null, null, args);
+ args.put("defaultValue", sqlDefault);
+ return args;
+ }
+
+ /**
+ * Converts name to valid database name
+ * Produced name must be used with backticks only, eg. `database_name`
+ *
+ * @param name source name
+ * @return database name
+ */
+ public String toDatabaseName(String name) {
+ String identifier = toIdentifier(name, databaseNamePrefix, databaseNameSuffix);
+ if (identifier.length() > IDENTIFIER_MAX_LENGTH) {
+ LOGGER.warn("Database name too long. Name '" + name + "' will be truncated");
+ identifier = identifier.substring(0, IDENTIFIER_MAX_LENGTH);
+ }
+ return identifier;
+ }
+
+ /**
+ * Converts name to valid column name
+ * Produced name must be used with backticks only, eg. `table_name`
+ *
+ * @param name source name
+ * @return table name
+ */
+ public String toTableName(String name) {
+ String identifier = toIdentifier(name, tableNamePrefix, tableNameSuffix);
+ if (identifierNamingConvention.equals("snake_case")) {
+ identifier = underscore(identifier);
+ }
+ if (identifier.length() > IDENTIFIER_MAX_LENGTH) {
+ LOGGER.warn("Table name too long. Name '" + name + "' will be truncated");
+ identifier = identifier.substring(0, IDENTIFIER_MAX_LENGTH);
+ }
+ return identifier;
+ }
+
+ /**
+ * Converts name to valid column name
+ * Produced name must be used with backticks only, eg. `column_name`
+ *
+ * @param name source name
+ * @return column name
+ */
+ public String toColumnName(String name) {
+ String identifier = toIdentifier(name, columnNamePrefix, columnNameSuffix);
+ if (identifierNamingConvention.equals("snake_case")) {
+ identifier = underscore(identifier);
+ }
+ if (identifier.length() > IDENTIFIER_MAX_LENGTH) {
+ LOGGER.warn("Column name too long. Name '" + name + "' will be truncated");
+ identifier = identifier.substring(0, IDENTIFIER_MAX_LENGTH);
+ }
+ return identifier;
+ }
+
+ /**
+ * Converts name to valid identifier which can be used as database, table, column name
+ * Produced name must be used quoted only, eg. "column_name"
+ *
+ * @param name source name
+ * @param prefix when escaped name is digits only, prefix will be prepended
+ * @param suffix when escaped name is digits only, suffix will be appended
+ * @return identifier name
+ */
+ public String toIdentifier(String name, String prefix, String suffix) {
+ String escapedName = escapeQuotedIdentifier(name);
+ // Database, table, and column names cannot end with space characters.
+ if (escapedName.matches(".*\\s$")) {
+ LOGGER.warn("Database, table, and column names cannot end with space characters. Check '" + name + "' name");
+ escapedName = escapedName.replaceAll("\\s+$", "");
+ }
+
+ // Identifiers may begin with a digit but unless quoted may not consist solely of digits.
+ if (escapedName.matches("^\\d+$")) {
+ LOGGER.warn("Database, table, and column names cannot consist solely of digits. Check '" + name + "' name");
+ escapedName = prefix + escapedName + suffix;
+ }
+
+ // identifier name cannot be empty
+ if (escapedName.isEmpty()) {
+ throw new RuntimeException("Empty database/table/column name for property '" + name.toString() + "' not allowed");
+ }
+ return escapedName;
+ }
+
+ /**
+ * Escapes identifier to use it in SQL statements with backticks, eg. SELECT "identifier" FROM
+ * Ref: https://www.sqlite.org/draft/tokenreq.html H41130
+ * Spec is similar to MySQL
+ *
+ * @param identifier source identifier
+ * @return escaped identifier
+ */
+ public String escapeQuotedIdentifier(String identifier) {
+ // ASCII: [0-9,a-z,A-Z$_] (basic Latin letters, digits 0-9, dollar, underscore) Extended: U+0080 .. U+FFFF
+ // ASCII NUL (U+0000) and supplementary characters (U+10000 and higher) are not permitted in quoted or unquoted identifiers.
+ // This does in fact matches against >\xFFFF and against ^\x0000. works only on Java7+
+ Pattern regexp = Pattern.compile("[^0-9a-zA-z$_\\x0080-\\xFFFF]");
+ Matcher matcher = regexp.matcher(identifier);
+ if (matcher.find()) {
+ LOGGER.warn("Identifier '" + identifier + "' contains unsafe characters out of [0-9,a-z,A-Z$_] and U+0080..U+FFFF range");
+ identifier = identifier.replaceAll("[^0-9a-zA-z$_\\x0080-\\xFFFF]", "");
+ }
+ return identifier;
+ }
+
+ @Override
+ public String escapeReservedWord(String name) {
+ //LOGGER.warn("'" + name + "' is reserved word. Do not use that word or properly escape it with backticks in mustache template");
+ return name;
+ }
+
+ @Override
+ public String escapeQuotationMark(String input) {
+ // remove ' to avoid code injection
+ return input.replace("'", "");
+ }
+
+ @Override
+ public String escapeUnsafeCharacters(String input) {
+ return input.replace("*/", "*_/").replace("/*", "/_*");
+ }
+
+ /**
+ * Sets default database name for all queries
+ * Provided value will be escaped when necessary
+ *
+ * @param databaseName source name
+ */
+ public void setDefaultDatabaseName(String databaseName) {
+ String escapedName = toDatabaseName(databaseName);
+ if (!escapedName.equals(databaseName)) {
+ LOGGER.error("Invalid database name. '" + databaseName + "' cannot be used as identifier. Escaped value '" + escapedName + "' will be used instead.");
+ }
+ this.defaultDatabaseName = escapedName;
+ }
+
+ /**
+ * Returns default database name for all queries
+ * This value must be used with backticks only, eg. `database_name`
+ *
+ * @return default database name
+ */
+ public String getDefaultDatabaseName() {
+ return this.defaultDatabaseName;
+ }
+
+ /**
+ * Sets imported package name for the models
+ *
+ * @param name name
+ */
+ public void setImportModelPackageName(String name) {
+ this.importModelPackageName = name;
+ }
+
+ /**
+ * Returns imported package name for the models
+ *
+ * @return name
+ */
+ public String getImportModelPackageName() {
+ return this.importModelPackageName;
+ }
+
+ /**
+ * Sets identifier naming convention for table names and column names.
+ * This is not related to database name which is defined by defaultDatabaseName option.
+ *
+ * @param naming identifier naming convention (original|snake_case)
+ */
+ public void setIdentifierNamingConvention(String naming) {
+ switch (naming) {
+ case "original":
+ case "snake_case":
+ this.identifierNamingConvention = naming;
+ break;
+ default:
+ LOGGER.warn("\"" + (String) naming + "\" is invalid \"identifierNamingConvention\" argument. Current \"" + (String) this.identifierNamingConvention + "\" used instead.");
+ }
+ }
+
+ /**
+ * Returns identifier naming convention for table names and column names.
+ *
+ * @return identifier naming convention
+ */
+ public String getIdentifierNamingConvention() {
+ return this.identifierNamingConvention;
+ }
+
+ /**
+ * Sets primary key naming convenion
+ *
+ * @param name name
+ */
+ public void setPrimaryKeyConvention(String name) {
+ this.primaryKeyConvention = name;
+ }
+
+ /**
+ * Returns primary key naming convenion
+ *
+ * @return name
+ */
+ public String getPrimaryKeyConvention() {
+ return this.primaryKeyConvention;
+ }
+
+ /**
+ * Sets primary key naming convenion
+ *
+ * @param enable enable this option
+ */
+ public void setAddSurrogateKey(boolean enable) {
+ this.addSurrogateKey = enable;
+ }
+
+ /**
+ * Returns primary key naming convenion
+ *
+ * @return is enabled
+ */
+ public boolean getAddSurrogateKey() {
+ return this.addSurrogateKey;
+ }
+
+ /**
+ * Slightly modified version of AbstractPhpCodegen.toSrcPath method.
+ *
+ * @param packageName package name
+ * @return path
+ */
+ public String toSrcPath(String packageName) {
+ // Trim prefix file separators from package path
+ String packagePath = StringUtils.removeStart(
+ // Replace period, backslash, forward slash with file separator in package name
+ packageName.replaceAll("[\\.\\\\/]", Matcher.quoteReplacement("/")),
+ File.separator
+ );
+
+ // Trim trailing file separators from the overall path
+ return StringUtils.removeEnd(packagePath, File.separator);
+ }
+
+}
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PowerShellClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PowerShellClientCodegen.java
index ac119546ba1..d2b4a8d1ed2 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PowerShellClientCodegen.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PowerShellClientCodegen.java
@@ -55,6 +55,7 @@ public class PowerShellClientCodegen extends DefaultCodegen implements CodegenCo
protected HashSet methodNames; // store a list of method names to detect duplicates
protected boolean useOneOfDiscriminatorLookup = false; // use oneOf discriminator's mapping for model lookup
protected boolean discardReadOnly = false; // Discard the readonly property in initialize cmdlet
+ protected boolean skipVerbParsing = false; // Attempt to parse cmdlets from operation names
protected String projectUri;
protected String licenseUri;
protected String releaseNotes;
@@ -511,6 +512,8 @@ public class PowerShellClientCodegen extends DefaultCodegen implements CodegenCo
cliOptions.add(new CliOption("licenseUri","A URL to the license for the generated PowerShell module"));
cliOptions.add(new CliOption("iconUri","A URL to an icon representing the generated PowerShell module"));
cliOptions.add(new CliOption("releaseNotes","Release notes of the generated PowerShell module"));
+ cliOptions.add(new CliOption("skipVerbParsing", "Set skipVerbParsing to not try get powershell verbs of operation names"));
+
// option to change how we process + set the data in the 'additionalProperties' keyword.
CliOption disallowAdditionalPropertiesIfNotPresentOpt = CliOption.newBoolean(
CodegenConstants.DISALLOW_ADDITIONAL_PROPERTIES_IF_NOT_PRESENT,
@@ -601,6 +604,7 @@ public class PowerShellClientCodegen extends DefaultCodegen implements CodegenCo
this.iconUri = iconUri;
}
+ public void setSkipVerbParsing(boolean skipVerbParsing) { this.skipVerbParsing = skipVerbParsing; };
@Override
public void processOpts() {
@@ -628,7 +632,13 @@ public class PowerShellClientCodegen extends DefaultCodegen implements CodegenCo
setDiscardReadOnly(convertPropertyToBooleanAndWriteBack("discardReadOnly"));
} else {
additionalProperties.put("discardReadOnly", discardReadOnly);
- }
+ }
+
+ if (additionalProperties.containsKey("skipVerbParsing")) {
+ setSkipVerbParsing(convertPropertyToBoolean("skipVerbParsing"));
+ } else {
+ additionalProperties.put("skipVerbParsing", skipVerbParsing);
+ }
if (additionalProperties.containsKey("tags")) {
String[] entries = ((String) additionalProperties.get("tags")).split(",");
@@ -1212,20 +1222,22 @@ public class PowerShellClientCodegen extends DefaultCodegen implements CodegenCo
private String toMethodName(String operationId) {
String methodName = camelize(operationId);
- // check if method name starts with powershell verbs
- for (String verb : (HashSet) powershellVerbs) {
- if (methodName.startsWith(verb)) {
- methodName = verb + "-" + apiNamePrefix + methodName.substring(verb.length());
- LOGGER.info("Naming the method using the PowerShell verb: {} => {}", operationId, methodName);
- return methodName;
+ if (!skipVerbParsing) {
+ // check if method name starts with powershell verbs
+ for (String verb : (HashSet) powershellVerbs) {
+ if (methodName.startsWith(verb)) {
+ methodName = verb + "-" + apiNamePrefix + methodName.substring(verb.length());
+ LOGGER.info("Naming the method using the PowerShell verb: {} => {}", operationId, methodName);
+ return methodName;
+ }
}
- }
- for (Map.Entry entry : commonVerbs.entrySet()) {
- if (methodName.startsWith(entry.getKey())) {
- methodName = entry.getValue() + "-" + apiNamePrefix + methodName.substring(entry.getKey().length());
- LOGGER.info("Naming the method by mapping the common verbs (e.g. Create, Change) to PS verbs: {} => {}", operationId, methodName);
- return methodName;
+ for (Map.Entry entry : commonVerbs.entrySet()) {
+ if (methodName.startsWith(entry.getKey())) {
+ methodName = entry.getValue() + "-" + apiNamePrefix + methodName.substring(entry.getKey().length());
+ LOGGER.info("Naming the method by mapping the common verbs (e.g. Create, Change) to PS verbs: {} => {}", operationId, methodName);
+ return methodName;
+ }
}
}
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientCodegen.java
index 2a84a4fb6cd..534c5248293 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientCodegen.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/PythonClientCodegen.java
@@ -16,6 +16,7 @@
package org.openapitools.codegen.languages;
+import com.google.common.collect.Sets;
import io.swagger.v3.core.util.Json;
import io.swagger.v3.oas.models.media.*;
import io.swagger.v3.oas.models.media.ArraySchema;
@@ -879,7 +880,7 @@ public class PythonClientCodegen extends PythonLegacyClientCodegen {
public String toExampleValue(Schema schema, Object objExample) {
String modelName = getModelName(schema);
- return toExampleValueRecursive(modelName, schema, objExample, 1, "", 0);
+ return toExampleValueRecursive(modelName, schema, objExample, 1, "", 0, Sets.newHashSet());
}
private Boolean simpleStringSchema(Schema schema) {
@@ -925,9 +926,12 @@ public class PythonClientCodegen extends PythonLegacyClientCodegen {
* ModelName( line 0
* some_property='some_property_example' line 1
* ) line 2
+ * @param seenSchemas This set contains all the schemas passed into the recursive function. It is used to check
+ * if a schema was already passed into the function and breaks the infinite recursive loop. The
+ * only schemas that are not added are ones that contain $ref != null
* @return the string example
*/
- private String toExampleValueRecursive(String modelName, Schema schema, Object objExample, int indentationLevel, String prefix, Integer exampleLine) {
+ private String toExampleValueRecursive(String modelName, Schema schema, Object objExample, int indentationLevel, String prefix, Integer exampleLine, Set seenSchemas) {
final String indentionConst = " ";
String currentIndentation = "";
String closingIndentation = "";
@@ -951,6 +955,27 @@ public class PythonClientCodegen extends PythonLegacyClientCodegen {
if (objExample != null) {
example = objExample.toString();
}
+ // checks if the current schema has already been passed in. If so, breaks the current recursive pass
+ if (seenSchemas.contains(schema)){
+ if (modelName != null) {
+ return fullPrefix + modelName + closeChars;
+ } else {
+ // this is a recursive schema
+ // need to add a reasonable example to avoid
+ // infinite recursion
+ if(ModelUtils.isNullable(schema)) {
+ // if the schema is nullable, then 'None' is a valid value
+ return fullPrefix + "None" + closeChars;
+ } else if(ModelUtils.isArraySchema(schema)) {
+ // the schema is an array, add an empty array
+ return fullPrefix + "[]" + closeChars;
+ } else {
+ // the schema is an object, make an empty object
+ return fullPrefix + "{}" + closeChars;
+ }
+ }
+ }
+
if (null != schema.get$ref()) {
Map allDefinitions = ModelUtils.getSchemas(this.openAPI);
String ref = ModelUtils.getSimpleRef(schema.get$ref());
@@ -960,7 +985,7 @@ public class PythonClientCodegen extends PythonLegacyClientCodegen {
return fullPrefix + "None" + closeChars;
}
String refModelName = getModelName(schema);
- return toExampleValueRecursive(refModelName, refSchema, objExample, indentationLevel, prefix, exampleLine);
+ return toExampleValueRecursive(refModelName, refSchema, objExample, indentationLevel, prefix, exampleLine, seenSchemas);
} else if (ModelUtils.isNullType(schema) || isAnyTypeSchema(schema)) {
// The 'null' type is allowed in OAS 3.1 and above. It is not supported by OAS 3.0.x,
// though this tooling supports it.
@@ -1058,7 +1083,8 @@ public class PythonClientCodegen extends PythonLegacyClientCodegen {
ArraySchema arrayschema = (ArraySchema) schema;
Schema itemSchema = arrayschema.getItems();
String itemModelName = getModelName(itemSchema);
- example = fullPrefix + "[" + "\n" + toExampleValueRecursive(itemModelName, itemSchema, objExample, indentationLevel + 1, "", exampleLine + 1) + ",\n" + closingIndentation + "]" + closeChars;
+ seenSchemas.add(schema);
+ example = fullPrefix + "[" + "\n" + toExampleValueRecursive(itemModelName, itemSchema, objExample, indentationLevel + 1, "", exampleLine + 1, seenSchemas) + ",\n" + closingIndentation + "]" + closeChars;
return example;
} else if (ModelUtils.isMapSchema(schema)) {
if (modelName == null) {
@@ -1080,7 +1106,8 @@ public class PythonClientCodegen extends PythonLegacyClientCodegen {
addPropPrefix = ensureQuotes(key) + ": ";
}
String addPropsModelName = getModelName(addPropsSchema);
- example = fullPrefix + "\n" + toExampleValueRecursive(addPropsModelName, addPropsSchema, addPropsExample, indentationLevel + 1, addPropPrefix, exampleLine + 1) + ",\n" + closingIndentation + closeChars;
+ seenSchemas.add(schema);
+ example = fullPrefix + "\n" + toExampleValueRecursive(addPropsModelName, addPropsSchema, addPropsExample, indentationLevel + 1, addPropPrefix, exampleLine + 1, seenSchemas) + ",\n" + closingIndentation + closeChars;
} else {
example = fullPrefix + closeChars;
}
@@ -1103,7 +1130,12 @@ public class PythonClientCodegen extends PythonLegacyClientCodegen {
return fullPrefix + closeChars;
}
}
- return exampleForObjectModel(schema, fullPrefix, closeChars, null, indentationLevel, exampleLine, closingIndentation);
+ // Adds schema to seenSchemas before running example model function. romoves schema after running
+ // the function. It also doesnt keep track of any schemas within the ObjectModel.
+ seenSchemas.add(schema);
+ String exampleForObjectModel = exampleForObjectModel(schema, fullPrefix, closeChars, null, indentationLevel, exampleLine, closingIndentation, seenSchemas);
+ seenSchemas.remove(schema);
+ return exampleForObjectModel;
} else if (ModelUtils.isComposedSchema(schema)) {
// TODO add examples for composed schema models without discriminators
@@ -1117,7 +1149,12 @@ public class PythonClientCodegen extends PythonLegacyClientCodegen {
CodegenProperty cp = new CodegenProperty();
cp.setName(disc.getPropertyName());
cp.setExample(discPropNameValue);
- return exampleForObjectModel(modelSchema, fullPrefix, closeChars, cp, indentationLevel, exampleLine, closingIndentation);
+ // Adds schema to seenSchemas before running example model function. romoves schema after running
+ // the function. It also doesnt keep track of any schemas within the ObjectModel.
+ seenSchemas.add(modelSchema);
+ String exampleForObjectModel = exampleForObjectModel(modelSchema, fullPrefix, closeChars, cp, indentationLevel, exampleLine, closingIndentation, seenSchemas);
+ seenSchemas.remove(modelSchema);
+ return exampleForObjectModel;
} else {
return fullPrefix + closeChars;
}
@@ -1130,7 +1167,7 @@ public class PythonClientCodegen extends PythonLegacyClientCodegen {
return example;
}
- private String exampleForObjectModel(Schema schema, String fullPrefix, String closeChars, CodegenProperty discProp, int indentationLevel, int exampleLine, String closingIndentation) {
+ private String exampleForObjectModel(Schema schema, String fullPrefix, String closeChars, CodegenProperty discProp, int indentationLevel, int exampleLine, String closingIndentation, Set seenSchemas) {
Map requiredAndOptionalProps = schema.getProperties();
if (requiredAndOptionalProps == null || requiredAndOptionalProps.isEmpty()) {
return fullPrefix + closeChars;
@@ -1150,7 +1187,7 @@ public class PythonClientCodegen extends PythonLegacyClientCodegen {
propModelName = getModelName(propSchema);
propExample = exampleFromStringOrArraySchema(propSchema, null, propName);
}
- example += toExampleValueRecursive(propModelName, propSchema, propExample, indentationLevel + 1, propName + "=", exampleLine + 1) + ",\n";
+ example += toExampleValueRecursive(propModelName, propSchema, propExample, indentationLevel + 1, propName + "=", exampleLine + 1, seenSchemas) + ",\n";
}
// TODO handle additionalProperties also
example += closingIndentation + closeChars;
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift5ClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift5ClientCodegen.java
index 10f44a3f937..bb86edadfef 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift5ClientCodegen.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/Swift5ClientCodegen.java
@@ -522,7 +522,7 @@ public class Swift5ClientCodegen extends DefaultCodegen implements CodegenConfig
return ModelUtils.isSet(p) ? "Set<" + getTypeDeclaration(inner) + ">" : "[" + getTypeDeclaration(inner) + "]";
} else if (ModelUtils.isMapSchema(p)) {
Schema inner = getAdditionalProperties(p);
- return "[String:" + getTypeDeclaration(inner) + "]";
+ return "[String: " + getTypeDeclaration(inner) + "]";
}
return super.getTypeDeclaration(p);
}
@@ -807,7 +807,7 @@ public class Swift5ClientCodegen extends DefaultCodegen implements CodegenConfig
@Override
public String toEnumValue(String value, String datatype) {
// for string, array of string
- if ("String".equals(datatype) || "[String]".equals(datatype) || "[String:String]".equals(datatype)) {
+ if ("String".equals(datatype) || "[String]".equals(datatype) || "[String: String]".equals(datatype)) {
return "\"" + String.valueOf(value) + "\"";
} else {
return String.valueOf(value);
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAngularClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAngularClientCodegen.java
index 20f6ebe614b..e0d7c235a77 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAngularClientCodegen.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptAngularClientCodegen.java
@@ -41,6 +41,7 @@ public class TypeScriptAngularClientCodegen extends AbstractTypeScriptClientCode
private static String FILE_NAME_SUFFIX_PATTERN = "^[a-zA-Z0-9.-]*$";
public static enum QUERY_PARAM_OBJECT_FORMAT_TYPE {dot, json, key};
+ public static enum PROVIDED_IN_LEVEL {none, root, any, platform};
private static final String DEFAULT_IMPORT_PREFIX = "./";
@@ -50,6 +51,7 @@ public class TypeScriptAngularClientCodegen extends AbstractTypeScriptClientCode
public static final String TAGGED_UNIONS = "taggedUnions";
public static final String NG_VERSION = "ngVersion";
public static final String PROVIDED_IN_ROOT = "providedInRoot";
+ public static final String PROVIDED_IN = "providedIn";
public static final String ENFORCE_GENERIC_MODULE_WITH_PROVIDERS = "enforceGenericModuleWithProviders";
public static final String API_MODULE_PREFIX = "apiModulePrefix";
public static final String CONFIGURATION_PREFIX = "configurationPrefix";
@@ -72,6 +74,7 @@ public class TypeScriptAngularClientCodegen extends AbstractTypeScriptClientCode
protected String fileNaming = "camelCase";
protected Boolean stringEnums = false;
protected QUERY_PARAM_OBJECT_FORMAT_TYPE queryParamObjectFormat = QUERY_PARAM_OBJECT_FORMAT_TYPE.dot;
+ protected PROVIDED_IN_LEVEL providedIn = PROVIDED_IN_LEVEL.root;
private boolean taggedUnions = false;
@@ -104,8 +107,17 @@ public class TypeScriptAngularClientCodegen extends AbstractTypeScriptClientCode
"Use discriminators to create tagged unions instead of extending interfaces.",
this.taggedUnions));
this.cliOptions.add(CliOption.newBoolean(PROVIDED_IN_ROOT,
- "Use this property to provide Injectables in root (it is only valid in angular version greater or equal to 6.0.0).",
+ "Use this property to provide Injectables in root (it is only valid in angular version greater or equal to 6.0.0). IMPORTANT: Deprecated for angular version greater or equal to 9.0.0, use **providedIn** instead.",
false));
+ CliOption providedInCliOpt = new CliOption(PROVIDED_IN,
+ "Use this property to provide Injectables in wanted level (it is only valid in angular version greater or equal to 9.0.0).").defaultValue("root");
+ Map providedInOptions = new HashMap<>();
+ providedInOptions.put(PROVIDED_IN_LEVEL.none.toString(), "No providedIn (same as providedInRoot=false)");
+ providedInOptions.put(PROVIDED_IN_LEVEL.root.toString(), "The application-level injector in most apps.");
+ providedInOptions.put(PROVIDED_IN_LEVEL.platform.toString(), "A special singleton platform injector shared by all applications on the page.");
+ providedInOptions.put(PROVIDED_IN_LEVEL.any.toString(), "Provides a unique instance in each lazy loaded module while all eagerly loaded modules share one instance.");
+ providedInCliOpt.setEnum(providedInOptions);
+ this.cliOptions.add(providedInCliOpt);
this.cliOptions.add(new CliOption(NG_VERSION, "The version of Angular. (At least 6.0.0)").defaultValue(this.ngVersion));
this.cliOptions.add(new CliOption(API_MODULE_PREFIX, "The prefix of the generated ApiModule."));
this.cliOptions.add(new CliOption(CONFIGURATION_PREFIX, "The prefix of the generated Configuration."));
@@ -188,13 +200,28 @@ public class TypeScriptAngularClientCodegen extends AbstractTypeScriptClientCode
taggedUnions = Boolean.parseBoolean(additionalProperties.get(TAGGED_UNIONS).toString());
}
- if (!additionalProperties.containsKey(PROVIDED_IN_ROOT)) {
- additionalProperties.put(PROVIDED_IN_ROOT, true);
+ if (ngVersion.atLeast("9.0.0") && additionalProperties.containsKey(PROVIDED_IN)) {
+ setProvidedIn(additionalProperties.get(PROVIDED_IN).toString());
} else {
- additionalProperties.put(PROVIDED_IN_ROOT, Boolean.parseBoolean(
- additionalProperties.get(PROVIDED_IN_ROOT).toString()
- ));
+ // Keep for backward compatibility
+ if (!additionalProperties.containsKey(PROVIDED_IN_ROOT)) {
+ additionalProperties.put(PROVIDED_IN_ROOT, true);
+ } else {
+ if (ngVersion.atLeast("9.0.0")) {
+ LOGGER.warn("{} will be deprecated, use {} {} instead", PROVIDED_IN_ROOT, PROVIDED_IN, PROVIDED_IN_LEVEL.values());
+ }
+ additionalProperties.put(PROVIDED_IN_ROOT, Boolean.parseBoolean(
+ additionalProperties.get(PROVIDED_IN_ROOT).toString()
+ ));
+ }
+ if ((Boolean) additionalProperties.get(PROVIDED_IN_ROOT)) {
+ providedIn = PROVIDED_IN_LEVEL.root;
+ } else {
+ providedIn = PROVIDED_IN_LEVEL.none;
+ }
}
+ additionalProperties.put("providedIn", providedIn);
+ additionalProperties.put("isProvidedInNone", getIsProvidedInNone());
if (ngVersion.atLeast("9.0.0")) {
additionalProperties.put(ENFORCE_GENERIC_MODULE_WITH_PROVIDERS, true);
@@ -714,5 +741,29 @@ public class TypeScriptAngularClientCodegen extends AbstractTypeScriptClientCode
}
return name;
}
+
+ /**
+ * Set the Injectable level
+ *
+ * @param level the wanted level
+ */
+ public void setProvidedIn (String level) {
+ try {
+ providedIn = PROVIDED_IN_LEVEL.valueOf(level);
+ } catch (IllegalArgumentException e) {
+ String values = Stream.of(PROVIDED_IN_LEVEL.values())
+ .map(value -> "'" + value.name() + "'")
+ .collect(Collectors.joining(", "));
+ String msg = String.format(Locale.ROOT, "Invalid providedIn level '%s'. Must be one of %s.", level, values);
+ throw new IllegalArgumentException(msg);
+ }
+ }
+
+ /**
+ *
+ */
+ private boolean getIsProvidedInNone() {
+ return PROVIDED_IN_LEVEL.none.equals(providedIn);
+ }
}
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java
index cf591520be9..d845cb159cf 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/languages/TypeScriptFetchClientCodegen.java
@@ -219,6 +219,14 @@ public class TypeScriptFetchClientCodegen extends AbstractTypeScriptClientCodege
return objs;
}
+ @Override
+ public void postProcessParameter(CodegenParameter parameter) {
+ super.postProcessParameter(parameter);
+ if (parameter.isFormParam && parameter.isArray && "binary".equals(parameter.dataFormat)) {
+ parameter.isCollectionFormatMulti = true;
+ }
+ }
+
@Override
public Map postProcessAllModels(Map objs) {
Map result = super.postProcessAllModels(objs);
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/templating/mustache/PrefixWithHashLambda.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/templating/mustache/PrefixWithHashLambda.java
new file mode 100644
index 00000000000..c3418de09b7
--- /dev/null
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/templating/mustache/PrefixWithHashLambda.java
@@ -0,0 +1,49 @@
+/*
+ * 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.templating.mustache;
+
+import com.samskivert.mustache.Mustache;
+import com.samskivert.mustache.Template.Fragment;
+
+import java.io.IOException;
+import java.io.Writer;
+
+/**
+ * Replaces duplicate whitespace characters in a fragment with single space.
+ *
+ * Register:
+ *
+ * additionalProperties.put("lambdaPrefixWithHash", new PrefixWithHashLambda());
+ *
+ *
+ * Use:
+ *
+ * {{#lambdaPrefixWithHash}}{{name}}{{/lambdaPrefixWithHash}}
+ *
+ */
+public class PrefixWithHashLambda implements Mustache.Lambda {
+ private static final String WITH_HASH = "\n#";
+
+ private static final String NEWLINE_REGEX = "\\R";
+
+ @Override
+ public void execute(Fragment fragment, Writer writer) throws IOException {
+ writer.write(fragment.execute().replaceAll(NEWLINE_REGEX, WITH_HASH));
+ }
+
+}
diff --git a/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/StringUtils.java b/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/StringUtils.java
index 9bca83d638b..d1ee6762b54 100644
--- a/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/StringUtils.java
+++ b/modules/openapi-generator/src/main/java/org/openapitools/codegen/utils/StringUtils.java
@@ -61,7 +61,7 @@ public class StringUtils {
.build();
}
- private static Pattern capitalLetterPattern = Pattern.compile("([A-Z]+)([A-Z][a-z])");
+ private static Pattern capitalLetterPattern = Pattern.compile("([A-Z]+)([A-Z][a-z][a-z]+)");
private static Pattern lowercasePattern = Pattern.compile("([a-z\\d])([A-Z])");
private static Pattern pkgSeparatorPattern = Pattern.compile("\\.");
private static Pattern dollarPattern = Pattern.compile("\\$");
diff --git a/modules/openapi-generator/src/main/resources/C-libcurl/api-body.mustache b/modules/openapi-generator/src/main/resources/C-libcurl/api-body.mustache
index ff19d883490..0c85a67a2b3 100644
--- a/modules/openapi-generator/src/main/resources/C-libcurl/api-body.mustache
+++ b/modules/openapi-generator/src/main/resources/C-libcurl/api-body.mustache
@@ -116,13 +116,13 @@ end:
{{#pathParams}}
// Path Params
- long sizeOfPathParams_{{{paramName}}} = {{#pathParams}}{{#isLong}}sizeof({{paramName}})+3{{/isLong}}{{#isString}}strlen({{paramName}})+3{{/isString}}{{^-last}} + {{/-last}}{{/pathParams}} + strlen("{ {{paramName}} }");
+ long sizeOfPathParams_{{{paramName}}} = {{#pathParams}}{{#isLong}}sizeof({{paramName}})+3{{/isLong}}{{#isString}}strlen({{paramName}})+3{{/isString}}{{^-last}} + {{/-last}}{{/pathParams}} + strlen("{ {{baseName}} }");
{{#isNumeric}}
if({{paramName}} == 0){
goto end;
}
char* localVarToReplace_{{paramName}} = malloc(sizeOfPathParams_{{paramName}});
- snprintf(localVarToReplace_{{paramName}}, sizeOfPathParams_{{paramName}}, "{%s}", "{{paramName}}");
+ snprintf(localVarToReplace_{{paramName}}, sizeOfPathParams_{{paramName}}, "{%s}", "{{baseName}}");
char localVarBuff_{{paramName}}[256];
intToStr(localVarBuff_{{paramName}}, {{paramName}});
@@ -135,7 +135,7 @@ end:
goto end;
}
char* localVarToReplace_{{paramName}} = malloc(sizeOfPathParams_{{paramName}});
- snprintf(localVarToReplace_{{paramName}}, sizeOfPathParams_{{paramName}}, "{%s}", "{{paramName}}");
+ snprintf(localVarToReplace_{{paramName}}, sizeOfPathParams_{{paramName}}, "{%s}", "{{baseName}}");
char localVarBuff_{{paramName}}[256];
intToStr(localVarBuff_{{paramName}}, {{paramName}});
@@ -148,7 +148,7 @@ end:
goto end;
}
char* localVarToReplace_{{paramName}} = malloc(sizeOfPathParams_{{paramName}});
- snprintf(localVarToReplace_{{paramName}}, sizeOfPathParams_{{paramName}}, "{%s}", "{{paramName}}");
+ snprintf(localVarToReplace_{{paramName}}, sizeOfPathParams_{{paramName}}, "{%s}", "{{baseName}}");
char localVarBuff_{{paramName}}[256];
intToStr(localVarBuff_{{paramName}}, {{paramName}});
@@ -161,7 +161,7 @@ end:
goto end;
}
char* localVarToReplace_{{paramName}} = malloc(sizeOfPathParams_{{paramName}});
- snprintf(localVarToReplace_{{paramName}}, sizeOfPathParams_{{paramName}}, "{%s}", "{{paramName}}");
+ snprintf(localVarToReplace_{{paramName}}, sizeOfPathParams_{{paramName}}, "{%s}", "{{baseName}}");
char localVarBuff_{{paramName}}[256];
intToStr(localVarBuff_{{paramName}}, {{paramName}});
@@ -174,7 +174,7 @@ end:
goto end;
}
char* localVarToReplace_{{paramName}} = malloc(sizeOfPathParams_{{paramName}});
- sprintf(localVarToReplace_{{paramName}}, "{%s}", "{{paramName}}");
+ sprintf(localVarToReplace_{{paramName}}, "{%s}", "{{baseName}}");
localVarPath = strReplace(localVarPath, localVarToReplace_{{paramName}}, {{paramName}});
{{/isString}}
@@ -183,7 +183,7 @@ end:
goto end;
}
char* localVarToReplace_{{paramName}} = malloc(sizeOfPathParams_{{paramName}});
- sprintf(localVarToReplace_{{paramName}}, "{%s}", "{{paramName}}");
+ sprintf(localVarToReplace_{{paramName}}, "{%s}", "{{baseName}}");
localVarPath = strReplace(localVarPath, localVarToReplace_{{paramName}}, {{paramName}});
{{/isUuid}}
diff --git a/modules/openapi-generator/src/main/resources/Java/libraries/jersey2/ApiClient.mustache b/modules/openapi-generator/src/main/resources/Java/libraries/jersey2/ApiClient.mustache
index 713332e20b7..e8833c12611 100644
--- a/modules/openapi-generator/src/main/resources/Java/libraries/jersey2/ApiClient.mustache
+++ b/modules/openapi-generator/src/main/resources/Java/libraries/jersey2/ApiClient.mustache
@@ -396,7 +396,7 @@ public class ApiClient{{#java8}} extends JavaTimeFormatter{{/java8}} {
*
* @param secrets Hash map from authentication name to its secret.
*/
- public ApiClient configureApiKeys(HashMap secrets) {
+ public ApiClient configureApiKeys(Map secrets) {
for (Map.Entry authEntry : authentications.entrySet()) {
Authentication auth = authEntry.getValue();
if (auth instanceof ApiKeyAuth) {
diff --git a/modules/openapi-generator/src/main/resources/Java/libraries/resttemplate/ApiClient.mustache b/modules/openapi-generator/src/main/resources/Java/libraries/resttemplate/ApiClient.mustache
index a6687d778cd..315fcdd4415 100644
--- a/modules/openapi-generator/src/main/resources/Java/libraries/resttemplate/ApiClient.mustache
+++ b/modules/openapi-generator/src/main/resources/Java/libraries/resttemplate/ApiClient.mustache
@@ -522,6 +522,15 @@ public class ApiClient{{#java8}} extends JavaTimeFormatter{{/java8}} {
return mediaType != null && (MediaType.APPLICATION_JSON.isCompatibleWith(mediaType) || mediaType.getSubtype().matches("^.*\\+json[;]?\\s*$"));
}
+ /**
+ * Check if the given {@code String} is a Problem JSON MIME (RFC-7807).
+ * @param mediaType the input MediaType
+ * @return boolean true if the MediaType represents Problem JSON, false otherwise
+ */
+ public boolean isProblemJsonMime(String mediaType) {
+ return "application/problem+json".equalsIgnoreCase(mediaType);
+ }
+
/**
* Select the Accept header's value from the given accepts array:
* if JSON exists in the given array, use it;
@@ -536,7 +545,7 @@ public class ApiClient{{#java8}} extends JavaTimeFormatter{{/java8}} {
}
for (String accept : accepts) {
MediaType mediaType = MediaType.parseMediaType(accept);
- if (isJsonMime(mediaType)) {
+ if (isJsonMime(mediaType) && !isProblemJsonMime(accept)) {
return Collections.singletonList(mediaType);
}
}
diff --git a/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig b/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig
index 8fe400f1148..7a0d6556be6 100644
--- a/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig
+++ b/modules/openapi-generator/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig
@@ -7,6 +7,7 @@ org.openapitools.codegen.languages.AsciidocDocumentationCodegen
org.openapitools.codegen.languages.AspNetCoreServerCodegen
org.openapitools.codegen.languages.AvroSchemaCodegen
org.openapitools.codegen.languages.BashClientCodegen
+org.openapitools.codegen.languages.CrystalClientCodegen
org.openapitools.codegen.languages.CLibcurlClientCodegen
org.openapitools.codegen.languages.ClojureClientCodegen
org.openapitools.codegen.languages.ConfluenceWikiCodegen
@@ -44,6 +45,7 @@ org.openapitools.codegen.languages.KotlinClientCodegen
org.openapitools.codegen.languages.KotlinServerCodegen
org.openapitools.codegen.languages.KotlinSpringServerCodegen
org.openapitools.codegen.languages.KotlinVertxServerCodegen
+org.openapitools.codegen.languages.KtormSchemaCodegen
org.openapitools.codegen.languages.HaskellHttpClientCodegen
org.openapitools.codegen.languages.HaskellServantCodegen
org.openapitools.codegen.languages.JavaClientCodegen
diff --git a/modules/openapi-generator/src/main/resources/confluenceWikiDocs/index.mustache b/modules/openapi-generator/src/main/resources/confluenceWikiDocs/index.mustache
index be2e6ccc807..9ddece9da19 100644
--- a/modules/openapi-generator/src/main/resources/confluenceWikiDocs/index.mustache
+++ b/modules/openapi-generator/src/main/resources/confluenceWikiDocs/index.mustache
@@ -75,10 +75,14 @@ h2. Models
{anchor:{{classname}}ModelAnchor}
h3. {{classname}}
- {{description}}
-
- ||Field Name||Required||Type||Description||Enum||
+ {{{description}}}
+
+ {{#isEnum}} ||Name||Value||Description||
+ {{#allowableValues}} {{#enumVars}} |{{{name}}} |{{{value}}} |{{{enumDescription}}} |
+ {{/enumVars}}
+ {{/allowableValues}} {{/isEnum}}
+ {{^isEnum}}||Field Name||Required||Type||Description||Enum||
{{#vars}} |{{baseName}} |{{#required}}(/){{/required}}{{^required}}(x){{/required}} |{noformat:nopanel=true}{{{dataType}}}{noformat} |{{description}} | {{#isEnum}} {{_enum}} {{/isEnum}} |
- {{/vars}}
+ {{/vars}} {{/isEnum}}
{{/model}}
{{/models}}
diff --git a/modules/openapi-generator/src/main/resources/crystal/Gemfile.mustache b/modules/openapi-generator/src/main/resources/crystal/Gemfile.mustache
new file mode 100644
index 00000000000..c2e3127cdcf
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/crystal/Gemfile.mustache
@@ -0,0 +1,9 @@
+source 'https://rubygems.org'
+
+gemspec
+
+group :development, :test do
+ gem 'rake', '~> 13.0.1'
+ gem 'pry-byebug'
+ gem 'rubocop', '~> 0.66.0'
+end
diff --git a/modules/openapi-generator/src/main/resources/crystal/README.mustache b/modules/openapi-generator/src/main/resources/crystal/README.mustache
new file mode 100644
index 00000000000..20705236b9c
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/crystal/README.mustache
@@ -0,0 +1,46 @@
+# {{shardName}}
+
+The Crystsal module for the {{appName}}
+
+{{#appDescriptionWithNewLines}}
+{{{appDescriptionWithNewLines}}}
+{{/appDescriptionWithNewLines}}
+
+This SDK is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
+
+- API version: {{appVersion}}
+- Package version: {{shardVersion}}
+{{^hideGenerationTimestamp}}
+- Build date: {{generatedDate}}
+{{/hideGenerationTimestamp}}
+- Build package: {{generatorClass}}
+{{#infoUrl}}
+For more information, please visit [{{{infoUrl}}}]({{{infoUrl}}})
+{{/infoUrl}}
+
+## Installation
+
+### Install from Git
+
+Add the following to shard.yaml
+
+```yaml
+dependencies:
+ {{{shardName}}}:
+ github: {{#gitUserId}}{{.}}{{/gitUserId}}{{^gitUserId}}YOUR_GIT_USERNAME{{/gitUserId}}/{{#gitRepoId}}{{.}}{{/gitRepoId}}{{^gitRepoId}}YOUR_GIT_REPO{{/gitRepoId}}
+ version: ~> {{shardVersion}}
+```
+
+## Development
+
+Install dependencies
+
+```shell
+shards
+```
+
+Run the tests:
+
+```shell
+crystal spec
+```
diff --git a/modules/openapi-generator/src/main/resources/crystal/Rakefile.mustache b/modules/openapi-generator/src/main/resources/crystal/Rakefile.mustache
new file mode 100644
index 00000000000..c72ca30d454
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/crystal/Rakefile.mustache
@@ -0,0 +1,10 @@
+require "bundler/gem_tasks"
+
+begin
+ require 'rspec/core/rake_task'
+
+ RSpec::Core::RakeTask.new(:spec)
+ task default: :spec
+rescue LoadError
+ # no rspec available
+end
diff --git a/modules/openapi-generator/src/main/resources/crystal/api.mustache b/modules/openapi-generator/src/main/resources/crystal/api.mustache
new file mode 100644
index 00000000000..550f017c4c6
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/crystal/api.mustache
@@ -0,0 +1,185 @@
+# {{#lambdaPrefixWithHash}}{{> api_info}}{{/lambdaPrefixWithHash}}
+
+require "uri"
+
+module {{moduleName}}
+{{#operations}}
+ class {{classname}}
+ property api_client : ApiClient
+
+ def initialize(api_client = ApiClient.default)
+ @api_client = api_client
+ end
+{{#operation}}
+ {{#summary}}
+ # {{{summary}}}
+ {{/summary}}
+ {{#notes}}
+ # {{{notes}}}
+ {{/notes}}
+ {{#allParams}}
+ {{#required}}
+ # @param {{paramName}} [{{{dataType}}}{{^required}}?{{/required}}] {{description}}
+ {{/required}}
+ {{/allParams}}
+ # @return [{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}nil{{/returnType}}]
+ def {{operationId}}({{#allParams}}{{paramName}} : {{{dataType}}}{{^required}}?{{/required}}{{^-last}}, {{/-last}}{{/allParams}})
+ {{#returnType}}data, _status_code, _headers = {{/returnType}}{{operationId}}_with_http_info({{#allParams}}{{paramName}}{{^-last}}, {{/-last}}{{/allParams}})
+ {{#returnType}}data{{/returnType}}{{^returnType}}nil{{/returnType}}
+ end
+
+ {{#summary}}
+ # {{summary}}
+ {{/summary}}
+ {{#notes}}
+ # {{notes}}
+ {{/notes}}
+ {{#allParams}}
+ {{#required}}
+ # @param {{paramName}} [{{{dataType}}}{{^required}}?{{/required}}] {{description}}
+ {{/required}}
+ {{/allParams}}
+ # @return [Array<({{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}nil{{/returnType}}, Integer, Hash)>] {{#returnType}}{{{returnType}}} data{{/returnType}}{{^returnType}}nil{{/returnType}}, response status code and response headers
+ def {{operationId}}_with_http_info({{#allParams}}{{paramName}} : {{{dataType}}}{{^required}}?{{/required}}{{^-last}}, {{/-last}}{{/allParams}})
+ if @api_client.config.debugging
+ Log.debug {"Calling API: {{classname}}.{{operationId}} ..."}
+ end
+ {{#allParams}}
+ {{^isNullable}}
+ {{#required}}
+ # verify the required parameter "{{paramName}}" is set
+ if @api_client.config.client_side_validation && {{{paramName}}}.nil?
+ raise ArgumentError.new("Missing the required parameter '{{paramName}}' when calling {{classname}}.{{operationId}}")
+ end
+ {{#isEnum}}
+ {{^isContainer}}
+ # verify enum value
+ allowable_values = [{{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}}, {{/-last}}{{/enumVars}}{{/allowableValues}}]
+ if @api_client.config.client_side_validation && !allowable_values.include?({{{paramName}}})
+ raise ArgumentError.new("invalid value for \"{{{paramName}}}\", must be one of #{allowable_values}")
+ end
+ {{/isContainer}}
+ {{/isEnum}}
+ {{/required}}
+ {{/isNullable}}
+ {{^required}}
+ {{#isEnum}}
+ {{#collectionFormat}}
+ allowable_values = [{{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}}, {{/-last}}{{/enumVars}}{{/allowableValues}}]
+ if @api_client.config.client_side_validation && {{{paramName}}} && {{{paramName}}}.all? { |item| allowable_values.include?(item) }
+ raise ArgumentError.new("invalid value for \"{{{paramName}}}\", must include one of #{allowable_values}")
+ end
+ {{/collectionFormat}}
+ {{^collectionFormat}}
+ allowable_values = [{{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}}, {{/-last}}{{/enumVars}}{{/allowableValues}}]
+ if @api_client.config.client_side_validation && {{{paramName}}} && !allowable_values.include?({{{paramName}}}])
+ raise ArgumentError.new("invalid value for \"{{{paramName}}}\", must be one of #{allowable_values}")
+ end
+ {{/collectionFormat}}
+ {{/isEnum}}
+ {{/required}}
+ {{#hasValidation}}
+ {{#maxLength}}
+ if @api_client.config.client_side_validation && {{^required}}!{{{paramName}}}.nil? && {{/required}}{{{paramName}}}.to_s.length > {{{maxLength}}}
+ raise ArgumentError.new("invalid value for \"{{{paramName}}}\" when calling {{classname}}.{{operationId}}, the character length must be smaller than or equal to {{{maxLength}}}.")
+ end
+
+ {{/maxLength}}
+ {{#minLength}}
+ if @api_client.config.client_side_validation && {{^required}}!{{{paramName}}}.nil? && {{/required}}{{{paramName}}}.to_s.length < {{{minLength}}}
+ raise ArgumentError.new("invalid value for \"{{{paramName}}}\" when calling {{classname}}.{{operationId}}, the character length must be great than or equal to {{{minLength}}}.")
+ end
+
+ {{/minLength}}
+ {{#maximum}}
+ if @api_client.config.client_side_validation && {{^required}}!{{{paramName}}}.nil? && {{/required}}{{{paramName}}} >{{#exclusiveMaximum}}={{/exclusiveMaximum}} {{{maximum}}}
+ raise ArgumentError.new("invalid value for \"{{{paramName}}}\" when calling {{classname}}.{{operationId}}, must be smaller than {{^exclusiveMaximum}}or equal to {{/exclusiveMaximum}}{{{maximum}}}.")
+ end
+
+ {{/maximum}}
+ {{#minimum}}
+ if @api_client.config.client_side_validation && {{^required}}!{{{paramName}}}.nil? && {{/required}}{{{paramName}}} <{{#exclusiveMinimum}}={{/exclusiveMinimum}} {{{minimum}}}
+ raise ArgumentError.new("invalid value for \"{{{paramName}}}\" when calling {{classname}}.{{operationId}}, must be greater than {{^exclusiveMinimum}}or equal to {{/exclusiveMinimum}}{{{minimum}}}.")
+ end
+
+ {{/minimum}}
+ {{#pattern}}
+ pattern = Regexp.new({{{pattern}}})
+ if @api_client.config.client_side_validation && {{^required}}{{{paramName}}}.nil? && {{/required}}{{{paramName}}} !~ pattern
+ raise ArgumentError.new("invalid value for \"{{{paramName}}}\" when calling {{classname}}.{{operationId}}, must conform to the pattern #{pattern}.")
+ end
+
+ {{/pattern}}
+ {{#maxItems}}
+ if @api_client.config.client_side_validation && {{^required}}{{{paramName}}}.nil? && {{/required}}{{{paramName}}}.length > {{{maxItems}}}
+ raise ArgumentError.new("invalid value for \"{{{paramName}}}\" when calling {{classname}}.{{operationId}}, number of items must be less than or equal to {{{maxItems}}}.")
+ end
+
+ {{/maxItems}}
+ {{#minItems}}
+ if @api_client.config.client_side_validation && {{^required}}{{{paramName}}}.nil? && {{/required}}{{{paramName}}}.length < {{{minItems}}}
+ raise ArgumentError.new("invalid value for \"{{{paramName}}}\" when calling {{classname}}.{{operationId}}, number of items must be greater than or equal to {{{minItems}}}.")
+ end
+
+ {{/minItems}}
+ {{/hasValidation}}
+ {{/allParams}}
+ # resource path
+ local_var_path = "{{{path}}}"{{#pathParams}}.sub("{" + "{{baseName}}" + "}", URI.encode({{paramName}}.to_s){{^strictSpecBehavior}}.gsub("%2F", "/"){{/strictSpecBehavior}}){{/pathParams}}
+
+ # query parameters
+ query_params = Hash(Symbol, String).new
+ {{#queryParams}}
+ query_params[:"{{{baseName}}}"] = {{#collectionFormat}}@api_client.build_collection_param({{{paramName}}}, :{{{collectionFormat}}}){{/collectionFormat}}{{^collectionFormat}}{{{paramName}}}{{/collectionFormat}}
+ {{/queryParams}}
+
+ # header parameters
+ header_params = Hash(String, String).new
+ {{#hasProduces}}
+ # HTTP header "Accept" (if needed)
+ header_params["Accept"] = @api_client.select_header_accept([{{#produces}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/produces}}])
+ {{/hasProduces}}
+ {{#hasConsumes}}
+ # HTTP header "Content-Type"
+ header_params["Content-Type"] = @api_client.select_header_content_type([{{#consumes}}"{{{mediaType}}}"{{^-last}}, {{/-last}}{{/consumes}}])
+ {{/hasConsumes}}
+ {{#headerParams}}
+ header_params["{{{baseName}}}"] = {{#collectionFormat}}@api_client.build_collection_param({{{paramName}}}, :{{{collectionFormat}}}){{/collectionFormat}}{{^collectionFormat}}{{{paramName}}}{{/collectionFormat}}
+ {{/headerParams}}
+
+ # form parameters
+ form_params = Hash(Symbol, String).new
+ {{#formParams}}
+ form_params[:"{{baseName}}"] = {{#collectionFormat}}@api_client.build_collection_param({{{paramName}}}, :{{{collectionFormat}}}){{/collectionFormat}}{{^collectionFormat}}{{{paramName}}}{{/collectionFormat}}
+ {{/formParams}}
+
+ # http body (model)
+ post_body = {{#bodyParam}}{{{paramName}}}.to_json{{/bodyParam}}{{^bodyParam}}nil{{/bodyParam}}
+
+ # return_type
+ return_type = {{#returnType}}"{{{.}}}"{{/returnType}}{{^returnType}}nil{{/returnType}}
+
+ # auth_names
+ auth_names = {{#authMethods}}{{#-first}}[{{/-first}}"{{name}}"{{^-last}}, {{/-last}}{{#-last}}]{{/-last}}{{/authMethods}}{{^authMethods}}[] of String{{/authMethods}}
+
+ data, status_code, headers = @api_client.call_api(:{{httpMethod}},
+ local_var_path,
+ :"{{classname}}.{{operationId}}",
+ return_type,
+ post_body,
+ auth_names,
+ header_params,
+ query_params,
+ form_params)
+ if @api_client.config.debugging
+ Log.debug {"API called: {{classname}}#{{operationId}}\nData: #{data.inspect}\nStatus code: #{status_code}\nHeaders: #{headers}"}
+ end
+ return {{#returnType}}{{{.}}}.from_json(data){{/returnType}}{{^returnType}}nil{{/returnType}}, status_code, headers
+ end
+{{^-last}}
+
+{{/-last}}
+{{/operation}}
+ end
+{{/operations}}
+end
diff --git a/modules/openapi-generator/src/main/resources/crystal/api_client.mustache b/modules/openapi-generator/src/main/resources/crystal/api_client.mustache
new file mode 100644
index 00000000000..5352175b00e
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/crystal/api_client.mustache
@@ -0,0 +1,402 @@
+# {{#lambdaPrefixWithHash}}{{> api_info}}{{/lambdaPrefixWithHash}}
+
+require "json"
+require "time"
+
+module {{moduleName}}
+ class ApiClient
+ # The Configuration object holding settings to be used in the API client.
+ property config : Configuration
+
+ # Defines the headers to be used in HTTP requests of all API calls by default.
+ #
+ # @return [Hash]
+ property default_headers : Hash(String, String)
+
+ # Initializes the ApiClient
+ # @option config [Configuration] Configuration for initializing the object, default to Configuration.default
+ def initialize(@config = Configuration.default)
+ @user_agent = "{{#httpUserAgent}}{{{.}}}{{/httpUserAgent}}{{^httpUserAgent}}OpenAPI-Generator/#{VERSION}/crystal{{/httpUserAgent}}"
+ @default_headers = {
+ "User-Agent" => @user_agent
+ }
+ end
+
+ def self.default
+ @@default ||= ApiClient.new
+ end
+
+ # Check if the given MIME is a JSON MIME.
+ # JSON MIME examples:
+ # application/json
+ # application/json; charset=UTF8
+ # APPLICATION/JSON
+ # */*
+ # @param [String] mime MIME
+ # @return [Boolean] True if the MIME is application/json
+ def json_mime?(mime)
+ (mime == "*/*") || !(mime =~ /Application\/.*json(?!p)(;.*)?/i).nil?
+ end
+
+ # Deserialize the response to the given return type.
+ #
+ # @param [Response] response HTTP response
+ # @param [String] return_type some examples: "User", "Array", "Hash"
+ def deserialize(response, return_type)
+ body = response.body
+
+ # handle file downloading - return the File instance processed in request callbacks
+ # note that response body is empty when the file is written in chunks in request on_body callback
+ if return_type == "File"
+ content_disposition = response.headers["Content-Disposition"].to_s
+ if content_disposition && content_disposition =~ /filename=/i
+ filename = content_disposition.match(/filename=[""]?([^""\s]+)[""]?/i).try &.[0]
+ prefix = sanitize_filename(filename)
+ else
+ prefix = "download-"
+ end
+ if !prefix.nil? && prefix.ends_with?("-")
+ prefix = prefix + "-"
+ end
+ encoding = response.headers["Content-Encoding"].to_s
+
+ # TODO add file support
+ raise ApiError.new(code: 0, message: "File response not yet supported in the client.") if return_type
+ return nil
+
+ #@tempfile = Tempfile.open(prefix, @config.temp_folder_path, encoding: encoding)
+ #@tempfile.write(@stream.join.force_encoding(encoding))
+ #@tempfile.close
+ #Log.info { "Temp file written to #{@tempfile.path}, please copy the file to a proper folder "\
+ # "with e.g. `FileUtils.cp(tempfile.path, \"/new/file/path\")` otherwise the temp file "\
+ # "will be deleted automatically with GC. It's also recommended to delete the temp file "\
+ # "explicitly with `tempfile.delete`" }
+ #return @tempfile
+ end
+
+ return nil if body.nil? || body.empty?
+
+ # return response body directly for String return type
+ return body if return_type == "String"
+
+ # ensuring a default content type
+ content_type = response.headers["Content-Type"] || "application/json"
+
+ raise ApiError.new(code: 0, message: "Content-Type is not supported: #{content_type}") unless json_mime?(content_type)
+
+ begin
+ data = JSON.parse("[#{body}]")[0]
+ rescue e : Exception
+ if %w(String Date Time).includes?(return_type)
+ data = body
+ else
+ raise e
+ end
+ end
+
+ convert_to_type data, return_type
+ end
+
+ # Convert data to the given return type.
+ # @param [Object] data Data to be converted
+ # @param [String] return_type Return type
+ # @return [Mixed] Data in a particular type
+ def convert_to_type(data, return_type)
+ return nil if data.nil?
+ case return_type
+ when "String"
+ data.to_s
+ when "Integer"
+ data.to_s.to_i
+ when "Float"
+ data.to_s.to_f
+ when "Boolean"
+ data == true
+ when "Time"
+ # parse date time (expecting ISO 8601 format)
+ Time.parse! data.to_s, "%Y-%m-%dT%H:%M:%S%Z"
+ when "Date"
+ # parse date (expecting ISO 8601 format)
+ Time.parse! data.to_s, "%Y-%m-%d"
+ when "Object"
+ # generic object (usually a Hash), return directly
+ data
+ when /\AArray<(.+)>\z/
+ # e.g. Array
+ sub_type = $1
+ data.map { |item| convert_to_type(item, sub_type) }
+ when /\AHash\\z/
+ # e.g. Hash
+ sub_type = $1
+ ({} of Symbol => String).tap do |hash|
+ data.each { |k, v| hash[k] = convert_to_type(v, sub_type) }
+ end
+ else
+ # models (e.g. Pet) or oneOf
+ klass = Petstore.const_get(return_type)
+ klass.respond_to?(:openapi_one_of) ? klass.build(data) : klass.build_from_hash(data)
+ end
+ end
+
+ # Sanitize filename by removing path.
+ # e.g. ../../sun.gif becomes sun.gif
+ #
+ # @param [String] filename the filename to be sanitized
+ # @return [String] the sanitized filename
+ def sanitize_filename(filename)
+ if filename.nil?
+ return nil
+ else
+ filename.gsub(/.*[\/\\]/, "")
+ end
+ end
+
+ def build_request_url(path : String, operation : Symbol)
+ # Add leading and trailing slashes to path
+ path = "/#{path}".gsub(/\/+/, "/")
+ @config.base_url(operation) + path
+ end
+
+ # Update hearder and query params based on authentication settings.
+ #
+ # @param [Hash] header_params Header parameters
+ # @param [Hash] query_params Query parameters
+ # @param [String] auth_names Authentication scheme name
+ def update_params_for_auth!(header_params, query_params, auth_names)
+ Array{auth_names}.each do |auth_name|
+ auth_setting = @config.auth_settings[auth_name]
+ next unless auth_setting
+ case auth_setting[:in]
+ when "header" then header_params[auth_setting[:key]] = auth_setting[:value]
+ when "query" then query_params[auth_setting[:key]] = auth_setting[:value]
+ else raise ArgumentError.new("Authentication token must be in `query` of `header`")
+ end
+ end
+ end
+
+ # Sets user agent in HTTP header
+ #
+ # @param [String] user_agent User agent (e.g. openapi-generator/ruby/1.0.0)
+ def user_agent=(user_agent)
+ @user_agent = user_agent
+ @default_headers["User-Agent"] = @user_agent
+ end
+
+ # Return Accept header based on an array of accepts provided.
+ # @param [Array] accepts array for Accept
+ # @return [String] the Accept header (e.g. application/json)
+ def select_header_accept(accepts) : String
+ #return nil if accepts.nil? || accepts.empty?
+ # use JSON when present, otherwise use all of the provided
+ json_accept = accepts.find { |s| json_mime?(s) }
+ if json_accept.nil?
+ accepts.join(",")
+ else
+ json_accept
+ end
+ end
+
+ # Return Content-Type header based on an array of content types provided.
+ # @param [Array] content_types array for Content-Type
+ # @return [String] the Content-Type header (e.g. application/json)
+ def select_header_content_type(content_types)
+ # use application/json by default
+ return "application/json" if content_types.nil? || content_types.empty?
+ # use JSON when present, otherwise use the first one
+ json_content_type = content_types.find { |s| json_mime?(s) }
+ json_content_type || content_types.first
+ end
+
+ # Convert object (array, hash, object, etc) to JSON string.
+ # @param [Object] model object to be converted into JSON string
+ # @return [String] JSON string representation of the object
+ def object_to_http_body(model)
+ return model if model.nil? || model.is_a?(String)
+ local_body = nil
+ if model.is_a?(Array)
+ local_body = model.map { |m| object_to_hash(m) }
+ else
+ local_body = object_to_hash(model)
+ end
+ local_body.to_json
+ end
+
+ # Convert object(non-array) to hash.
+ # @param [Object] obj object to be converted into JSON string
+ # @return [String] JSON string representation of the object
+ def object_to_hash(obj)
+ if obj.respond_to?(:to_hash)
+ obj.to_hash
+ else
+ obj
+ end
+ end
+
+ # Build parameter value according to the given collection format.
+ # @param [String] collection_format one of :csv, :ssv, :tsv, :pipes and :multi
+ def build_collection_param(param, collection_format)
+ case collection_format
+ when :csv
+ param.join(",")
+ when :ssv
+ param.join(" ")
+ when :tsv
+ param.join("\t")
+ when :pipes
+ param.join("|")
+ when :multi
+ # return the array directly as typhoeus will handle it as expected
+ param
+ else
+ fail "unknown collection format: #{collection_format.inspect}"
+ end
+ end
+
+ # Call an API with given options.
+ #
+ # @return [Array<(Object, Integer, Hash)>] an array of 3 elements:
+ # the data deserialized from response body (could be nil), response status code and response headers.
+ def call_api(http_method : Symbol, path : String, operation : Symbol, return_type : String, post_body : String?, auth_names = [] of String, header_params = {} of String => String, query_params = {} of Symbol => String, form_params = {} of Symbol => String)
+ #ssl_options = {
+ # :ca_file => @config.ssl_ca_file,
+ # :verify => @config.ssl_verify,
+ # :verify_mode => @config.ssl_verify_mode,
+ # :client_cert => @config.ssl_client_cert,
+ # :client_key => @config.ssl_client_key
+ #}
+
+ #connection = Faraday.new(:url => config.base_url, :ssl => ssl_options) do |conn|
+ # conn.basic_auth(config.username, config.password)
+ # if opts[:header_params]["Content-Type"] == "multipart/form-data"
+ # conn.request :multipart
+ # conn.request :url_encoded
+ # end
+ # conn.adapter(Faraday.default_adapter)
+ #end
+
+ if !post_body.nil? && !post_body.empty?
+ # use JSON string in the payload
+ form_or_body = post_body
+ else
+ # use HTTP forms in the payload
+ # TDOD use HTTP form encoding
+ form_or_body = form_params
+ end
+
+ request = Crest::Request.new(http_method,
+ build_request_url(path, operation),
+ params: query_params,
+ headers: header_params,
+ #cookies: cookie_params, # TODO add cookies support
+ form: form_or_body,
+ logging: @config.debugging,
+ handle_errors: false
+ )
+
+ response = request.execute
+
+ if @config.debugging
+ Log.debug {"HTTP response body ~BEGIN~\n#{response.body}\n~END~\n"}
+ end
+
+ if !response.success?
+ if response.status == 0
+ # Errors from libcurl will be made visible here
+ raise ApiError.new(code: 0,
+ message: response.body)
+ else
+ raise ApiError.new(code: response.status_code,
+ response_headers: response.headers,
+ message: response.body)
+ end
+ end
+
+ return response.body, response.status_code, response.headers
+ end
+
+ # Builds the HTTP request
+ #
+ # @param [String] http_method HTTP method/verb (e.g. POST)
+ # @param [String] path URL path (e.g. /account/new)
+ # @option opts [Hash] :header_params Header parameters
+ # @option opts [Hash] :query_params Query parameters
+ # @option opts [Hash] :form_params Query parameters
+ # @option opts [Object] :body HTTP body (JSON/XML)
+ # @return [Typhoeus::Request] A Typhoeus Request
+ def build_request(http_method, path, request, opts = {} of Symbol => String)
+ url = build_request_url(path, opts)
+ http_method = http_method.to_sym.downcase
+
+ header_params = @default_headers.merge(opts[:header_params] || {} of Symbole => String)
+ query_params = opts[:query_params] || {} of Symbol => String
+ form_params = opts[:form_params] || {} of Symbol => String
+
+ update_params_for_auth! header_params, query_params, opts[:auth_names]
+
+ req_opts = {
+ :method => http_method,
+ :headers => header_params,
+ :params => query_params,
+ :params_encoding => @config.params_encoding,
+ :timeout => @config.timeout,
+ :verbose => @config.debugging
+ }
+
+ if [:post, :patch, :put, :delete].include?(http_method)
+ req_body = build_request_body(header_params, form_params, opts[:body])
+ req_opts.update body: req_body
+ if @config.debugging
+ Log.debug {"HTTP request body param ~BEGIN~\n#{req_body}\n~END~\n"}
+ end
+ end
+ request.headers = header_params
+ request.body = req_body
+ request.url url
+ request.params = query_params
+ download_file(request) if opts[:return_type] == "File"
+ request
+ end
+
+ # Builds the HTTP request body
+ #
+ # @param [Hash] header_params Header parameters
+ # @param [Hash] form_params Query parameters
+ # @param [Object] body HTTP body (JSON/XML)
+ # @return [String] HTTP body data in the form of string
+ def build_request_body(header_params, form_params, body)
+ # http form
+ if header_params["Content-Type"] == "application/x-www-form-urlencoded"
+ data = URI.encode_www_form(form_params)
+ elsif header_params["Content-Type"] == "multipart/form-data"
+ data = {} of Symbol => String
+ form_params.each do |key, value|
+ case value
+ when ::File, ::Tempfile
+ # TODO hardcode to application/octet-stream, need better way to detect content type
+ data[key] = Faraday::UploadIO.new(value.path, "application/octet-stream", value.path)
+ when ::Array, nil
+ # let Faraday handle Array and nil parameters
+ data[key] = value
+ else
+ data[key] = value.to_s
+ end
+ end
+ elsif body
+ data = body.is_a?(String) ? body : body.to_json
+ else
+ data = nil
+ end
+ data
+ end
+
+ # TODO fix streaming response
+ #def download_file(request)
+ # @stream = []
+
+ # # handle streaming Responses
+ # request.options.on_data = Proc.new do |chunk, overall_received_bytes|
+ # @stream << chunk
+ # end
+ #end
+ end
+end
diff --git a/modules/openapi-generator/src/main/resources/crystal/api_client_faraday_partial.mustache b/modules/openapi-generator/src/main/resources/crystal/api_client_faraday_partial.mustache
new file mode 100644
index 00000000000..dfef217e037
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/crystal/api_client_faraday_partial.mustache
@@ -0,0 +1,138 @@
+ # Call an API with given options.
+ #
+ # @return [Array<(Object, Integer, Hash)>] an array of 3 elements:
+ # the data deserialized from response body (could be nil), response status code and response headers.
+ def call_api(http_method, path, opts = {} of Symbol => String)
+ ssl_options = {
+ :ca_file => @config.ssl_ca_file,
+ :verify => @config.ssl_verify,
+ :verify_mode => @config.ssl_verify_mode,
+ :client_cert => @config.ssl_client_cert,
+ :client_key => @config.ssl_client_key
+ }
+
+ connection = Faraday.new(:url => config.base_url, :ssl => ssl_options) do |conn|
+ conn.basic_auth(config.username, config.password)
+ if opts[:header_params]["Content-Type"] == "multipart/form-data"
+ conn.request :multipart
+ conn.request :url_encoded
+ end
+ conn.adapter(Faraday.default_adapter)
+ end
+
+ begin
+ response = connection.public_send(http_method.to_sym.downcase) do |req|
+ build_request(http_method, path, req, opts)
+ end
+
+ if @config.debugging
+ Log.debug {"HTTP response body ~BEGIN~\n#{response.body}\n~END~\n"}
+ end
+
+ unless response.success?
+ if response.status == 0
+ # Errors from libcurl will be made visible here
+ fail ApiError.new(code: 0,
+ message: response.return_message)
+ else
+ fail ApiError.new(code: response.status,
+ response_headers: response.headers,
+ response_body: response.body),
+ response.reason_phrase
+ end
+ end
+ rescue Faraday::TimeoutError
+ fail ApiError.new("Connection timed out")
+ end
+
+ if opts[:return_type]
+ data = deserialize(response, opts[:return_type])
+ else
+ data = nil
+ end
+ return data, response.status, response.headers
+ end
+
+ # Builds the HTTP request
+ #
+ # @param [String] http_method HTTP method/verb (e.g. POST)
+ # @param [String] path URL path (e.g. /account/new)
+ # @option opts [Hash] :header_params Header parameters
+ # @option opts [Hash] :query_params Query parameters
+ # @option opts [Hash] :form_params Query parameters
+ # @option opts [Object] :body HTTP body (JSON/XML)
+ # @return [Typhoeus::Request] A Typhoeus Request
+ def build_request(http_method, path, request, opts = {} of Symbol => String)
+ url = build_request_url(path, opts)
+ http_method = http_method.to_sym.downcase
+
+ header_params = @default_headers.merge(opts[:header_params] || {} of Symbole => String)
+ query_params = opts[:query_params] || {} of Symbol => String
+ form_params = opts[:form_params] || {} of Symbol => String
+
+ update_params_for_auth! header_params, query_params, opts[:auth_names]
+
+ req_opts = {
+ :method => http_method,
+ :headers => header_params,
+ :params => query_params,
+ :params_encoding => @config.params_encoding,
+ :timeout => @config.timeout,
+ :verbose => @config.debugging
+ }
+
+ if [:post, :patch, :put, :delete].include?(http_method)
+ req_body = build_request_body(header_params, form_params, opts[:body])
+ req_opts.update body: req_body
+ if @config.debugging
+ Log.debug {"HTTP request body param ~BEGIN~\n#{req_body}\n~END~\n"}
+ end
+ end
+ request.headers = header_params
+ request.body = req_body
+ request.url url
+ request.params = query_params
+ download_file(request) if opts[:return_type] == "File"
+ request
+ end
+
+ # Builds the HTTP request body
+ #
+ # @param [Hash] header_params Header parameters
+ # @param [Hash] form_params Query parameters
+ # @param [Object] body HTTP body (JSON/XML)
+ # @return [String] HTTP body data in the form of string
+ def build_request_body(header_params, form_params, body)
+ # http form
+ if header_params["Content-Type"] == "application/x-www-form-urlencoded"
+ data = URI.encode_www_form(form_params)
+ elsif header_params["Content-Type"] == "multipart/form-data"
+ data = {} of Symbol => String
+ form_params.each do |key, value|
+ case value
+ when ::File, ::Tempfile
+ # TODO hardcode to application/octet-stream, need better way to detect content type
+ data[key] = Faraday::UploadIO.new(value.path, "application/octet-stream", value.path)
+ when ::Array, nil
+ # let Faraday handle Array and nil parameters
+ data[key] = value
+ else
+ data[key] = value.to_s
+ end
+ end
+ elsif body
+ data = body.is_a?(String) ? body : body.to_json
+ else
+ data = nil
+ end
+ data
+ end
+
+ def download_file(request)
+ @stream = []
+
+ # handle streaming Responses
+ request.options.on_data = Proc.new do |chunk, overall_received_bytes|
+ @stream << chunk
+ end
+ end
diff --git a/modules/openapi-generator/src/main/resources/crystal/api_client_typhoeus_partial.mustache b/modules/openapi-generator/src/main/resources/crystal/api_client_typhoeus_partial.mustache
new file mode 100644
index 00000000000..d8e95e77461
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/crystal/api_client_typhoeus_partial.mustache
@@ -0,0 +1,153 @@
+ # Call an API with given options.
+ #
+ # @return [Array<(Object, Integer, Hash)>] an array of 3 elements:
+ # the data deserialized from response body (could be nil), response status code and response headers.
+ def call_api(http_method, path, opts = {} of Symbol => String)
+ request = build_request(http_method, path, opts)
+ response = request.run
+
+ if @config.debugging
+ @config.logger.debug "HTTP response body ~BEGIN~\n#{response.body}\n~END~\n"
+ end
+
+ unless response.success?
+ if response.timed_out?
+ fail ApiError.new("Connection timed out")
+ elsif response.code == 0
+ # Errors from libcurl will be made visible here
+ fail ApiError.new(code: 0,
+ message: response.return_message)
+ else
+ fail ApiError.new(code: response.code,
+ response_headers: response.headers,
+ response_body: response.body),
+ response.status_message
+ end
+ end
+
+ if opts[:return_type]
+ data = deserialize(response, opts[:return_type])
+ else
+ data = nil
+ end
+ return data, response.code, response.headers
+ end
+
+ # Builds the HTTP request
+ #
+ # @param [String] http_method HTTP method/verb (e.g. POST)
+ # @param [String] path URL path (e.g. /account/new)
+ # @option opts [Hash] :header_params Header parameters
+ # @option opts [Hash] :query_params Query parameters
+ # @option opts [Hash] :form_params Query parameters
+ # @option opts [Object] :body HTTP body (JSON/XML)
+ # @return [Typhoeus::Request] A Typhoeus Request
+ def build_request(http_method, path, opts = {} of Symbol => String)
+ url = build_request_url(path, opts)
+ http_method = http_method.to_sym.downcase
+
+ header_params = @default_headers.merge(opts[:header_params] || {} of Symbol => String)
+ query_params = opts[:query_params] || {} of Symbol => String
+ form_params = opts[:form_params] || {} of Symbol => String
+
+ {{#hasAuthMethods}}
+ update_params_for_auth! header_params, query_params, opts[:auth_names]
+ {{/hasAuthMethods}}
+
+ # set ssl_verifyhosts option based on @config.verify_ssl_host (true/false)
+ _verify_ssl_host = @config.verify_ssl_host ? 2 : 0
+
+ req_opts = {
+ :method => http_method,
+ :headers => header_params,
+ :params => query_params,
+ :params_encoding => @config.params_encoding,
+ :timeout => @config.timeout,
+ :ssl_verifypeer => @config.verify_ssl,
+ :ssl_verifyhost => _verify_ssl_host,
+ :sslcert => @config.cert_file,
+ :sslkey => @config.key_file,
+ :verbose => @config.debugging
+ }
+
+ # set custom cert, if provided
+ req_opts[:cainfo] = @config.ssl_ca_cert if @config.ssl_ca_cert
+
+ if [:post, :patch, :put, :delete].include?(http_method)
+ req_body = build_request_body(header_params, form_params, opts[:body])
+ req_opts.update body: req_body
+ if @config.debugging
+ @config.logger.debug "HTTP request body param ~BEGIN~\n#{req_body}\n~END~\n"
+ end
+ end
+
+ request = Typhoeus::Request.new(url, req_opts)
+ download_file(request) if opts[:return_type] == "File"
+ request
+ end
+
+ # Builds the HTTP request body
+ #
+ # @param [Hash] header_params Header parameters
+ # @param [Hash] form_params Query parameters
+ # @param [Object] body HTTP body (JSON/XML)
+ # @return [String] HTTP body data in the form of string
+ def build_request_body(header_params, form_params, body)
+ # http form
+ if header_params["Content-Type"] == "application/x-www-form-urlencoded" ||
+ header_params["Content-Type"] == "multipart/form-data"
+ data = {} of Symbol => String
+ form_params.each do |key, value|
+ case value
+ when ::File, ::Array, nil
+ # let typhoeus handle File, Array and nil parameters
+ data[key] = value
+ else
+ data[key] = value.to_s
+ end
+ end
+ elsif body
+ data = body.is_a?(String) ? body : body.to_json
+ else
+ data = nil
+ end
+ data
+ end
+
+ # Save response body into a file in (the defined) temporary folder, using the filename
+ # from the "Content-Disposition" header if provided, otherwise a random filename.
+ # The response body is written to the file in chunks in order to handle files which
+ # size is larger than maximum Ruby String or even larger than the maximum memory a Ruby
+ # process can use.
+ #
+ # @see Configuration#temp_folder_path
+ def download_file(request)
+ tempfile = nil
+ encoding = nil
+ request.on_headers do |response|
+ content_disposition = response.headers["Content-Disposition"]
+ if content_disposition && content_disposition =~ /filename=/i
+ filename = content_disposition[/filename=[""]?([^""\s]+)[""]?/, 1]
+ prefix = sanitize_filename(filename)
+ else
+ prefix = "download-"
+ end
+ prefix = prefix + "-" unless prefix.end_with?("-")
+ encoding = response.body.encoding
+ tempfile = Tempfile.open(prefix, @config.temp_folder_path, encoding: encoding)
+ @tempfile = tempfile
+ end
+ request.on_body do |chunk|
+ chunk.force_encoding(encoding)
+ tempfile.write(chunk)
+ end
+ request.on_complete do |response|
+ if tempfile
+ tempfile.close
+ @config.logger.info "Temp file written to #{tempfile.path}, please copy the file to a proper folder "\
+ "with e.g. `FileUtils.cp(tempfile.path, \"/new/file/path\")` otherwise the temp file "\
+ "will be deleted automatically with GC. It's also recommended to delete the temp file "\
+ "explicitly with `tempfile.delete`"
+ end
+ end
+ end
diff --git a/modules/openapi-generator/src/main/resources/crystal/api_doc.mustache b/modules/openapi-generator/src/main/resources/crystal/api_doc.mustache
new file mode 100644
index 00000000000..bdeeb568912
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/crystal/api_doc.mustache
@@ -0,0 +1,118 @@
+# {{moduleName}}::{{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}}
+
+## {{operationId}}
+
+> {{#returnType}}{{#returnTypeIsPrimitive}}{{returnType}}{{/returnTypeIsPrimitive}}{{^returnTypeIsPrimitive}}<{{{returnType}}}>{{/returnTypeIsPrimitive}} {{/returnType}}{{operationId}}{{#hasParams}}({{#requiredParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/requiredParams}}{{#optionalParams}}{{#-last}}{{#hasRequiredParams}}, {{/hasRequiredParams}}opts{{/-last}}{{/optionalParams}}){{/hasParams}}
+
+{{{summary}}}{{#notes}}
+
+{{{notes}}}{{/notes}}
+
+### Examples
+
+```ruby
+require 'time'
+require '{{{gemName}}}'
+{{#hasAuthMethods}}
+# setup authorization
+{{{moduleName}}}.configure do |config|{{#authMethods}}{{#isBasic}}{{#isBasicBasic}}
+ # Configure HTTP basic authorization: {{{name}}}
+ config.username = 'YOUR USERNAME'
+ config.password = 'YOUR PASSWORD'{{/isBasicBasic}}{{#isBasicBearer}}
+ # Configure Bearer authorization{{#bearerFormat}} ({{{.}}}){{/bearerFormat}}: {{{name}}}
+ config.access_token = 'YOUR_BEARER_TOKEN'{{/isBasicBearer}}{{/isBasic}}{{#isApiKey}}
+ # Configure API key authorization: {{{name}}}
+ config.api_key['{{{keyParamName}}}'] = 'YOUR API KEY'
+ # Uncomment the following line to set a prefix for the API key, e.g. 'Bearer' (defaults to nil)
+ # config.api_key_prefix['{{{keyParamName}}}'] = 'Bearer'{{/isApiKey}}{{#isOAuth}}
+ # Configure OAuth2 access token for authorization: {{{name}}}
+ config.access_token = 'YOUR ACCESS TOKEN'{{/isOAuth}}
+{{/authMethods}}end
+{{/hasAuthMethods}}
+
+api_instance = {{{moduleName}}}::{{{classname}}}.new
+{{#requiredParams}}
+{{{paramName}}} = {{{vendorExtensions.x-ruby-example}}} # {{{dataType}}} | {{{description}}}
+{{/requiredParams}}
+{{#optionalParams}}
+{{#-first}}
+opts = {
+{{/-first}}
+ {{{paramName}}}: {{{vendorExtensions.x-ruby-example}}}{{^-last}},{{/-last}} # {{{dataType}}} | {{{description}}}
+{{#-last}}
+}
+{{/-last}}
+{{/optionalParams}}
+
+begin
+ {{#summary}}# {{{.}}}{{/summary}}
+ {{#returnType}}result = {{/returnType}}api_instance.{{{operationId}}}{{#hasParams}}({{#requiredParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/requiredParams}}{{#optionalParams}}{{#-last}}{{#hasRequiredParams}}, {{/hasRequiredParams}}opts{{/-last}}{{/optionalParams}}){{/hasParams}}
+ {{#returnType}}
+ p result
+ {{/returnType}}
+rescue {{{moduleName}}}::ApiError => e
+ puts "Error when calling {{classname}}->{{{operationId}}}: #{e}"
+end
+```
+
+#### Using the {{operationId}}_with_http_info variant
+
+This returns an Array which contains the response data{{^returnType}} (`nil` in this case){{/returnType}}, status code and headers.
+
+> {{/returnTypeIsPrimitive}}{{/returnType}}{{^returnType}}nil{{/returnType}}, Integer, Hash)> {{operationId}}_with_http_info{{#hasParams}}({{#requiredParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/requiredParams}}{{#optionalParams}}{{#-last}}{{#hasRequiredParams}}, {{/hasRequiredParams}}opts{{/-last}}{{/optionalParams}}){{/hasParams}}
+
+```ruby
+begin
+ {{#summary}}# {{{.}}}{{/summary}}
+ data, status_code, headers = api_instance.{{{operationId}}}_with_http_info{{#hasParams}}({{#requiredParams}}{{{paramName}}}{{^-last}}, {{/-last}}{{/requiredParams}}{{#optionalParams}}{{#-last}}{{#hasRequiredParams}}, {{/hasRequiredParams}}opts{{/-last}}{{/optionalParams}}){{/hasParams}}
+ p status_code # => 2xx
+ p headers # => { ... }
+ p data # => {{#returnType}}{{#returnTypeIsPrimitive}}{{returnType}}{{/returnTypeIsPrimitive}}{{^returnTypeIsPrimitive}}<{{{returnType}}}>{{/returnTypeIsPrimitive}}{{/returnType}}{{^returnType}}nil{{/returnType}}
+rescue {{{moduleName}}}::ApiError => e
+ puts "Error when calling {{classname}}->{{{operationId}}}_with_http_info: #{e}"
+end
+```
+
+### Parameters
+
+{{^allParams}}
+This endpoint does not need any parameter.
+{{/allParams}}
+{{#allParams}}
+{{#-first}}
+| Name | Type | Description | Notes |
+| ---- | ---- | ----------- | ----- |
+{{/-first}}
+| **{{paramName}}** | {{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}{{#isFile}}**{{dataType}}**{{/isFile}}{{^isFile}}[**{{dataType}}**]({{baseType}}.md){{/isFile}}{{/isPrimitiveType}} | {{description}} | {{^required}}[optional]{{/required}}{{#defaultValue}}[default to {{defaultValue}}]{{/defaultValue}} |
+{{/allParams}}
+
+### Return type
+
+{{#returnType}}{{#returnTypeIsPrimitive}}**{{returnType}}**{{/returnTypeIsPrimitive}}{{^returnTypeIsPrimitive}}[**{{returnType}}**]({{returnBaseType}}.md){{/returnTypeIsPrimitive}}{{/returnType}}{{^returnType}}nil (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}}
diff --git a/modules/openapi-generator/src/main/resources/crystal/api_error.mustache b/modules/openapi-generator/src/main/resources/crystal/api_error.mustache
new file mode 100644
index 00000000000..fd39b5bef27
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/crystal/api_error.mustache
@@ -0,0 +1,33 @@
+# {{#lambdaPrefixWithHash}}{{> api_info}}{{/lambdaPrefixWithHash}}
+
+module {{moduleName}}
+ class ApiError < Exception
+ getter code : Int32?
+ getter response_headers : Hash(String, Array(String) | String)?
+
+ # Usage examples:
+ # ApiError.new
+ # ApiError.new(message: "message")
+ # ApiError.new(code: 500, response_headers: {}, message: "")
+ # ApiError.new(code: 404, message: "Not Found")
+ def initialize(@code , @message, @response_headers)
+ end
+
+ def initialize(@code , @message)
+ end
+
+ # Override to_s to display a friendly error message
+ def to_s
+ msg = ""
+ msg = msg + "\nHTTP status code: #{code}" if @code
+ msg = msg + "\nResponse headers: #{response_headers}" if @response_headers
+ if @message.nil? || @message.empty?
+ msg = msg + "\nError message: the server returns an error but the HTTP respone body is empty."
+ else
+ msg = msg + "\nResponse body: #{@message}"
+ end
+
+ msg
+ end
+ end
+end
diff --git a/modules/openapi-generator/src/main/resources/crystal/api_info.mustache b/modules/openapi-generator/src/main/resources/crystal/api_info.mustache
new file mode 100644
index 00000000000..1b3f9cb5ac4
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/crystal/api_info.mustache
@@ -0,0 +1,12 @@
+{{#appName}}
+#{{{appName}}}
+
+{{/appName}}
+{{#appDescription}}
+#{{{appDescription}}}
+
+{{/appDescription}}
+{{#version}}The version of the OpenAPI document: {{version}}{{/version}}
+{{#infoEmail}}Contact: {{{infoEmail}}}{{/infoEmail}}
+Generated by: https://openapi-generator.tech
+OpenAPI Generator version: {{{generatorVersion}}}
diff --git a/modules/openapi-generator/src/main/resources/crystal/api_test.mustache b/modules/openapi-generator/src/main/resources/crystal/api_test.mustache
new file mode 100644
index 00000000000..f4e0eb49e64
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/crystal/api_test.mustache
@@ -0,0 +1,38 @@
+# {{#lambdaPrefixWithHash}}{{> api_info}}{{/lambdaPrefixWithHash}}
+
+require "../spec_helper"
+require "json"
+require "time"
+
+# Unit tests for {{moduleName}}::{{classname}}
+# Automatically generated by openapi-generator (https://openapi-generator.tech)
+# Please update as you see appropriate
+{{#operations}}describe "{{classname}}" do
+ describe "test an instance of {{classname}}" do
+ it "should create an instance of {{classname}}" do
+ api_instance = {{moduleName}}::{{classname}}.new
+ # TODO expect(api_instance).to be_instance_of({{moduleName}}::{{classname}})
+ end
+ end
+
+{{#operation}}
+ # unit tests for {{operationId}}
+ {{#summary}}
+ # {{summary}}
+ {{/summary}}
+ {{#notes}}
+ # {{notes}}
+ {{/notes}}
+{{#allParams}}{{#required}} # @param {{paramName}} {{description}}
+{{/required}}{{/allParams}} # @param [Hash] opts the optional parameters
+{{#allParams}}{{^required}} # @option opts [{{{dataType}}}] :{{paramName}} {{description}}
+{{/required}}{{/allParams}} # @return [{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}nil{{/returnType}}]
+ describe "{{operationId}} test" do
+ it "should work" do
+ # assertion here. ref: https://crystal-lang.org/reference/guides/testing.html
+ end
+ end
+
+{{/operation}}
+end
+{{/operations}}
diff --git a/modules/openapi-generator/src/main/resources/crystal/base_object.mustache b/modules/openapi-generator/src/main/resources/crystal/base_object.mustache
new file mode 100644
index 00000000000..c7abd09b36b
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/crystal/base_object.mustache
@@ -0,0 +1,120 @@
+ # Builds the object from hash
+ # @param [Hash] attributes Model attributes in the form of hash
+ # @return [Object] Returns the model itself
+ def self.build_from_hash(attributes)
+ new.build_from_hash(attributes)
+ end
+
+ # Builds the object from hash
+ # @param [Hash] attributes Model attributes in the form of hash
+ # @return [Object] Returns the model itself
+ def build_from_hash(attributes)
+ return nil unless attributes.is_a?(Hash)
+ {{#parent}}
+ super(attributes)
+ {{/parent}}
+ self.class.openapi_types.each_pair do |key, type|
+ if attributes[self.class.attribute_map[key]].nil? && self.class.openapi_nullable.include?(key)
+ self.send("#{key}=", nil)
+ elsif type =~ /\AArray<(.*)>/i
+ # check to ensure the input is an array given that the attribute
+ # is documented as an array but the input is not
+ if attributes[self.class.attribute_map[key]].is_a?(Array)
+ self.send("#{key}=", attributes[self.class.attribute_map[key]].map { |v| _deserialize($1, v) })
+ end
+ elsif !attributes[self.class.attribute_map[key]].nil?
+ self.send("#{key}=", _deserialize(type, attributes[self.class.attribute_map[key]]))
+ end
+ end
+
+ self
+ end
+
+ # Deserializes the data based on type
+ # @param string type Data type
+ # @param string value Value to be deserialized
+ # @return [Object] Deserialized data
+ def _deserialize(type, value)
+ case type.to_sym
+ when :Time
+ Time.parse(value)
+ when :Date
+ Date.parse(value)
+ when :String
+ value.to_s
+ when :Integer
+ value.to_i
+ when :Float
+ value.to_f
+ when :Boolean
+ if value.to_s =~ /\A(true|t|yes|y|1)\z/i
+ true
+ else
+ false
+ end
+ when :Object
+ # generic object (usually a Hash), return directly
+ value
+ when /\AArray<(?.+)>\z/
+ inner_type = Regexp.last_match[:inner_type]
+ value.map { |v| _deserialize(inner_type, v) }
+ when /\AHash<(?.+?), (?.+)>\z/
+ k_type = Regexp.last_match[:k_type]
+ v_type = Regexp.last_match[:v_type]
+ ({} of Symbol => String).tap do |hash|
+ value.each do |k, v|
+ hash[_deserialize(k_type, k)] = _deserialize(v_type, v)
+ end
+ end
+ else # model
+ # models (e.g. Pet) or oneOf
+ klass = {{moduleName}}.const_get(type)
+ klass.respond_to?(:openapi_one_of) ? klass.build(value) : klass.build_from_hash(value)
+ end
+ end
+
+ # Returns the string representation of the object
+ # @return [String] String presentation of the object
+ def to_s
+ to_hash.to_s
+ end
+
+ # to_body is an alias to to_hash (backward compatibility)
+ # @return [Hash] Returns the object in the form of hash
+ def to_body
+ to_hash
+ end
+
+ # Returns the object in the form of hash
+ # @return [Hash] Returns the object in the form of hash
+ def to_hash
+ hash = {{^parent}}{} of Symbol => String{{/parent}}{{#parent}}super{{/parent}}
+ self.class.attribute_map.each_pair do |attr, param|
+ value = self.send(attr)
+ if value.nil?
+ is_nullable = self.class.openapi_nullable.include?(attr)
+ next if !is_nullable || (is_nullable && !instance_variable_defined?(:"@#{attr}"))
+ end
+
+ hash[param] = _to_hash(value)
+ end
+ hash
+ end
+
+ # Outputs non-array value in the form of hash
+ # For object, use to_hash. Otherwise, just return the value
+ # @param [Object] value Any valid value
+ # @return [Hash] Returns the value in the form of hash
+ def _to_hash(value)
+ if value.is_a?(Array)
+ value.compact.map { |v| _to_hash(v) }
+ elsif value.is_a?(Hash)
+ ({} of Symbol => String).tap do |hash|
+ value.each { |k, v| hash[k] = _to_hash(v) }
+ end
+ elsif value.respond_to? :to_hash
+ value.to_hash
+ else
+ value
+ end
+ end
diff --git a/modules/openapi-generator/src/main/resources/crystal/configuration.mustache b/modules/openapi-generator/src/main/resources/crystal/configuration.mustache
new file mode 100644
index 00000000000..a3a815fc042
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/crystal/configuration.mustache
@@ -0,0 +1,356 @@
+# {{#lambdaPrefixWithHash}}{{> api_info}}{{/lambdaPrefixWithHash}}
+
+require "log"
+
+module {{moduleName}}
+ class Configuration
+ # Defines url scheme
+ property scheme : String
+
+ # Defines url host
+ property host : String
+
+ # Defines url base path
+ property base_path : String
+
+ # Define server configuration index
+ property server_index : Int32
+
+ # Define server operation configuration index
+ property server_operation_index : Hash(Symbol, String)
+
+ # Default server variables
+ property server_variables : Hash(Symbol, String)
+
+ # Default server operation variables
+ property server_operation_variables : Hash(Symbol, String)
+
+ # Defines API keys used with API Key authentications.
+ #
+ # @return [Hash] key: parameter name, value: parameter value (API key)
+ #
+ # @example parameter name is "api_key", API key is "xxx" (e.g. "api_key=xxx" in query string)
+ # config.api_key[:"api_key"] = "xxx"
+ property api_key : Hash(Symbol, String)
+
+ # Defines API key prefixes used with API Key authentications.
+ #
+ # @return [Hash] key: parameter name, value: API key prefix
+ #
+ # @example parameter name is "Authorization", API key prefix is "Token" (e.g. "Authorization: Token xxx" in headers)
+ # config.api_key_prefix[:"api_key"] = "Token"
+ property api_key_prefix : Hash(Symbol, String)
+
+ # Defines the username used with HTTP basic authentication.
+ #
+ # @return [String]
+ property username : String?
+
+ # Defines the password used with HTTP basic authentication.
+ #
+ # @return [String]
+ property password : String?
+
+ # Defines the access token (Bearer) used with OAuth2.
+ property access_token : String?
+
+ # Set this to enable/disable debugging. When enabled (set to true), HTTP request/response
+ # details will be logged with `logger.debug` (see the `logger` attribute).
+ # Default to false.
+ #
+ # @return [true, false]
+ property debugging : Bool
+
+ # Defines the temporary folder to store downloaded files
+ # (for API endpoints that have file response).
+ # Default to use `Tempfile`.
+ #
+ # @return [String]
+ property temp_folder_path : String?
+
+ # The time limit for HTTP request in seconds.
+ # Default to 0 (never times out).
+ property timeout : Int32
+
+ # Set this to false to skip client side validation in the operation.
+ # Default to true.
+ # @return [true, false]
+ property client_side_validation : Bool
+
+ ### TLS/SSL setting
+ # Set this to false to skip verifying SSL certificate when calling API from https server.
+ # Default to true.
+ #
+ # @note Do NOT set it to false in production code, otherwise you would face multiple types of cryptographic attacks.
+ #
+ # @return [true, false]
+ #TODO attr_accessor :verify_ssl
+
+ ### TLS/SSL setting
+ # Set this to false to skip verifying SSL host name
+ # Default to true.
+ #
+ # @note Do NOT set it to false in production code, otherwise you would face multiple types of cryptographic attacks.
+ #
+ # @return [true, false]
+ # TODO attr_accessor :verify_ssl_host
+
+ ### TLS/SSL setting
+ # Set this to customize the certificate file to verify the peer.
+ #
+ # @return [String] the path to the certificate file
+ #
+ # @see The `cainfo` option of Typhoeus, `--cert` option of libcurl. Related source code:
+ # https://github.com/typhoeus/typhoeus/blob/master/lib/typhoeus/easy_factory.rb#L145
+ # TODO attr_accessor :ssl_ca_cert
+
+ ### TLS/SSL setting
+ # Client certificate file (for client certificate)
+ # TODO attr_accessor :cert_file
+
+ ### TLS/SSL setting
+ # Client private key file (for client certificate)
+ # TODO attr_accessor :key_file
+
+ # Set this to customize parameters encoding of array parameter with multi collectionFormat.
+ # Default to Nil.
+ #
+ # @see The params_encoding option of Ethon. Related source code:
+ # https://github.com/typhoeus/ethon/blob/master/lib/ethon/easy/queryable.rb#L96
+ #property params_encoding : String?
+
+ def initialize
+ @scheme = "{{scheme}}"
+ @host = "{{host}}{{#port}}:{{{.}}}{{/port}}"
+ @base_path = "{{contextPath}}"
+ @server_index = 0
+ @server_operation_index = {} of Symbol => String
+ @server_variables = {} of Symbol => String
+ @server_operation_variables = {} of Symbol => String
+ @api_key = {} of Symbol => String
+ @api_key_prefix = {} of Symbol => String
+ @timeout = 0
+ @client_side_validation = true
+ @verify_ssl = true
+ @verify_ssl_host = true
+ #@params_encoding = nil
+ #@cert_file = nil
+ #@key_file = nil
+ @debugging = false
+ @username = nil
+ @password = nil
+ @access_token = nil
+ @temp_folder_path = nil
+
+ # TODO revise below to support block
+ #yield(self) if block_given?
+ end
+
+ # The default Configuration object.
+ def self.default
+ @@default ||= Configuration.new
+ end
+
+ def configure
+ yield(self) if block_given?
+ end
+
+ def scheme=(scheme)
+ # remove :// from scheme
+ @scheme = scheme.sub(/:\/\//, "")
+ end
+
+ def host=(host)
+ # remove http(s):// and anything after a slash
+ @host = host.sub(/https?:\/\//, "").split("/").first
+ end
+
+ def base_path=(base_path)
+ # Add leading and trailing slashes to base_path
+ @base_path = "/#{base_path}".gsub(/\/+/, "/")
+ @base_path = "" if @base_path == "/"
+ end
+
+ # Returns base URL for specified operation based on server settings
+ def base_url(operation = Nil)
+ # TODO revise below to support operation-level server setting
+ #index = server_operation_index.fetch(operation, server_index)
+ return "#{scheme}://#{[host, base_path].join("/").gsub(/\/+/, "/")}".sub(/\/+\z/, "") #if index == Nil
+
+ #server_url(index, server_operation_variables.fetch(operation, server_variables), operation_server_settings[operation])
+ end
+
+ # Gets API key (with prefix if set).
+ # @param [String] param_name the parameter name of API key auth
+ def api_key_with_prefix(param_name)
+ if @api_key_prefix[param_name]
+ "#{@api_key_prefix[param_name]} #{@api_key[param_name]}"
+ else
+ @api_key[param_name]
+ end
+ end
+
+ # Gets Basic Auth token string
+ def basic_auth_token
+ "Basic " + ["#{username}:#{password}"].pack("m").delete("\r\n")
+ end
+
+ # 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}}")
+ },
+{{/isApiKey}}
+{{#isBasic}}
+{{#isBasicBasic}}
+ "{{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}"
+ },
+{{/isBasicBearer}}
+{{/isBasic}}
+{{#isOAuth}}
+ "{{name}}" =>
+ {
+ type: "oauth2",
+ in: "header",
+ key: "Authorization",
+ value: "Bearer #{access_token}"
+ },
+{{/isOAuth}}
+{{/authMethods}}
+ }
+ end
+
+ # Returns an array of Server setting
+ def server_settings
+ [
+ {{#servers}}
+ {
+ url: "{{{url}}}",
+ description: "{{{description}}}{{^description}}No description provided{{/description}}",
+ {{#variables}}
+ {{#-first}}
+ variables: {
+ {{/-first}}
+ {{{name}}}: {
+ description: "{{{description}}}{{^description}}No description provided{{/description}}",
+ default_value: "{{{defaultValue}}}",
+ {{#enumValues}}
+ {{#-first}}
+ enum_values: [
+ {{/-first}}
+ "{{{.}}}"{{^-last}},{{/-last}}
+ {{#-last}}
+ ]
+ {{/-last}}
+ {{/enumValues}}
+ }{{^-last}},{{/-last}}
+ {{#-last}}
+ }
+ {{/-last}}
+ {{/variables}}
+ }{{^-last}},{{/-last}}
+ {{/servers}}
+ ]
+ end
+
+ def operation_server_settings
+ {{#apiInfo}}
+ {{#apis}}
+ {{#operations}}
+ {{#operation}}
+ {{#servers}}
+ {{#-first}}
+ {
+ "{{{classname}}}.{{{nickname}}}": [
+ {{/-first}}
+ {
+ url: "{{{url}}}",
+ description: "{{{description}}}{{^description}}No description provided{{/description}}",
+ {{#variables}}
+ {{#-first}}
+ variables: {
+ {{/-first}}
+ {{{name}}}: {
+ description: "{{{description}}}{{^description}}No description provided{{/description}}",
+ default_value: "{{{defaultValue}}}",
+ {{#enumValues}}
+ {{#-first}}
+ enum_values: [
+ {{/-first}}
+ "{{{.}}}"{{^-last}},{{/-last}}
+ {{#-last}}
+ ]
+ {{/-last}}
+ {{/enumValues}}
+ }{{^-last}},{{/-last}}
+ {{#-last}}
+ }
+ {{/-last}}
+ {{/variables}}
+ }{{^-last}},{{/-last}}
+ {{#-last}}
+ ],
+ }
+ {{/-last}}
+ {{/servers}}
+ {{/operation}}
+ {{/operations}}
+ {{/apis}}
+ {{/apiInfo}}
+ end
+
+ # Returns URL based on server settings
+ #
+ # @param index array index of the server settings
+ # @param variables hash of variable and the corresponding value
+ def server_url(index, variables = {} of Symbol => String, servers = Nil)
+ servers = server_settings if servers == Nil
+
+ # check array index out of bound
+ if (index < 0 || index >= servers.size)
+ raise ArgumentError.new("Invalid index #{index} when selecting the server. Must be less than #{servers.size}")
+ end
+
+ server = servers[index]
+ url = server[:url]
+
+ return url unless server.key? :variables
+
+ # go through variable and assign a value
+ server[:variables].each do |name, variable|
+ if variables.key?(name)
+ if (!server[:variables][name].key?(:enum_values) || server[:variables][name][:enum_values].include?(variables[name]))
+ url.gsub! "{" + name.to_s + "}", variables[name]
+ else
+ raise ArgumentError.new("The variable `#{name}` in the server URL has invalid value #{variables[name]}. Must be #{server[:variables][name][:enum_values]}.")
+ end
+ else
+ # use default value
+ url.gsub! "{" + name.to_s + "}", server[:variables][name][:default_value]
+ end
+ end
+
+ url
+ end
+ end
+end
diff --git a/modules/openapi-generator/src/main/resources/crystal/configuration_spec.mustache b/modules/openapi-generator/src/main/resources/crystal/configuration_spec.mustache
new file mode 100644
index 00000000000..22a113e8e32
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/crystal/configuration_spec.mustache
@@ -0,0 +1,34 @@
+=begin
+{{> api_info}}
+=end
+
+require 'spec_helper'
+
+describe {{moduleName}}::Configuration do
+ let(:config) { {{moduleName}}::Configuration.default }
+
+ before(:each) do
+ # uncomment below to setup host and base_path
+ # require 'URI'
+ # uri = URI.parse("{{{basePath}}}")
+ # {{moduleName}}.configure do |c|
+ # c.host = uri.host
+ # c.base_path = uri.path
+ # end
+ end
+
+ describe '#base_url' do
+ it 'should have the default value' do
+ # uncomment below to test default value of the base path
+ # expect(config.base_url).to eq("{{{basePath}}}")
+ end
+
+ it 'should remove trailing slashes' do
+ [nil, '', '/', '//'].each do |base_path|
+ config.base_path = base_path
+ # uncomment below to test trailing slashes
+ # expect(config.base_url).to eq("{{{basePath}}}")
+ end
+ end
+ end
+end
diff --git a/modules/openapi-generator/src/main/resources/crystal/configuration_tls_faraday_partial.mustache b/modules/openapi-generator/src/main/resources/crystal/configuration_tls_faraday_partial.mustache
new file mode 100644
index 00000000000..c35c988f933
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/crystal/configuration_tls_faraday_partial.mustache
@@ -0,0 +1,29 @@
+ ### TLS/SSL setting
+ # Set this to false to skip verifying SSL certificate when calling API from https server.
+ # Default to true.
+ #
+ # @note Do NOT set it to false in production code, otherwise you would face multiple types of cryptographic attacks.
+ #
+ # @return [true, false]
+ #TODO attr_accessor :ssl_verify
+
+ ### TLS/SSL setting
+ # Any `OpenSSL::SSL::` constant (see https://ruby-doc.org/stdlib-2.5.1/libdoc/openssl/rdoc/OpenSSL/SSL.html)
+ #
+ # @note Do NOT set it to false in production code, otherwise you would face multiple types of cryptographic attacks.
+ #
+ #TODO attr_accessor :ssl_verify_mode
+
+ ### TLS/SSL setting
+ # Set this to customize the certificate file to verify the peer.
+ #
+ # @return [String] the path to the certificate file
+ #TODO attr_accessor :ssl_ca_file
+
+ ### TLS/SSL setting
+ # Client certificate file (for client certificate)
+ #TODO attr_accessor :ssl_client_cert
+
+ ### TLS/SSL setting
+ # Client private key file (for client certificate)
+ #TODO attr_accessor :ssl_client_key
diff --git a/modules/openapi-generator/src/main/resources/crystal/configuration_tls_typhoeus_partial.mustache b/modules/openapi-generator/src/main/resources/crystal/configuration_tls_typhoeus_partial.mustache
new file mode 100644
index 00000000000..1ab1b2d0307
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/crystal/configuration_tls_typhoeus_partial.mustache
@@ -0,0 +1,34 @@
+ ### TLS/SSL setting
+ # Set this to false to skip verifying SSL certificate when calling API from https server.
+ # Default to true.
+ #
+ # @note Do NOT set it to false in production code, otherwise you would face multiple types of cryptographic attacks.
+ #
+ # @return [true, false]
+ #TODO attr_accessor :verify_ssl
+
+ ### TLS/SSL setting
+ # Set this to false to skip verifying SSL host name
+ # Default to true.
+ #
+ # @note Do NOT set it to false in production code, otherwise you would face multiple types of cryptographic attacks.
+ #
+ # @return [true, false]
+ # TODO attr_accessor :verify_ssl_host
+
+ ### TLS/SSL setting
+ # Set this to customize the certificate file to verify the peer.
+ #
+ # @return [String] the path to the certificate file
+ #
+ # @see The `cainfo` option of Typhoeus, `--cert` option of libcurl. Related source code:
+ # https://github.com/typhoeus/typhoeus/blob/master/lib/typhoeus/easy_factory.rb#L145
+ # TODO attr_accessor :ssl_ca_cert
+
+ ### TLS/SSL setting
+ # Client certificate file (for client certificate)
+ # TODO attr_accessor :cert_file
+
+ ### TLS/SSL setting
+ # Client private key file (for client certificate)
+ # TODO attr_accessor :key_file
diff --git a/modules/openapi-generator/src/main/resources/crystal/git_push.sh.mustache b/modules/openapi-generator/src/main/resources/crystal/git_push.sh.mustache
new file mode 100755
index 00000000000..8b3f689c912
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/crystal/git_push.sh.mustache
@@ -0,0 +1,58 @@
+#!/bin/sh
+# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/
+#
+# Usage example: /bin/sh ./git_push.sh wing328 openapi-pestore-perl "minor update" "gitlab.com"
+
+git_user_id=$1
+git_repo_id=$2
+release_note=$3
+git_host=$4
+
+if [ "$git_host" = "" ]; then
+ git_host="{{{gitHost}}}"
+ echo "[INFO] No command line input provided. Set \$git_host to $git_host"
+fi
+
+if [ "$git_user_id" = "" ]; then
+ git_user_id="{{{gitUserId}}}"
+ echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id"
+fi
+
+if [ "$git_repo_id" = "" ]; then
+ git_repo_id="{{{gitRepoId}}}"
+ echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id"
+fi
+
+if [ "$release_note" = "" ]; then
+ release_note="{{{releaseNote}}}"
+ echo "[INFO] No command line input provided. Set \$release_note to $release_note"
+fi
+
+# Initialize the local directory as a Git repository
+git init
+
+# Adds the files in the local repository and stages them for commit.
+git add .
+
+# Commits the tracked changes and prepares them to be pushed to a remote repository.
+git commit -m "$release_note"
+
+# Sets the new remote
+git_remote=`git remote`
+if [ "$git_remote" = "" ]; then # git remote not defined
+
+ if [ "$GIT_TOKEN" = "" ]; then
+ echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment."
+ git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git
+ else
+ git remote add origin https://${git_user_id}:${GIT_TOKEN}@${git_host}/${git_user_id}/${git_repo_id}.git
+ fi
+
+fi
+
+git pull origin master
+
+# Pushes (Forces) the changes in the local repository up to the remote repository
+echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git"
+git push origin master 2>&1 | grep -v 'To https'
+
diff --git a/modules/openapi-generator/src/main/resources/crystal/gitignore.mustache b/modules/openapi-generator/src/main/resources/crystal/gitignore.mustache
new file mode 100644
index 00000000000..05a17cb8f0a
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/crystal/gitignore.mustache
@@ -0,0 +1,39 @@
+# Generated by: https://openapi-generator.tech
+#
+
+*.gem
+*.rbc
+/.config
+/coverage/
+/InstalledFiles
+/pkg/
+/spec/reports/
+/spec/examples.txt
+/test/tmp/
+/test/version_tmp/
+/tmp/
+
+## Specific to RubyMotion:
+.dat*
+.repl_history
+build/
+
+## Documentation cache and generated files:
+/.yardoc/
+/_yardoc/
+/doc/
+/rdoc/
+
+## Environment normalization:
+/.bundle/
+/vendor/bundle
+/lib/bundler/man/
+
+# for a library or gem, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# Gemfile.lock
+# .ruby-version
+# .ruby-gemset
+
+# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
+.rvmrc
diff --git a/modules/openapi-generator/src/main/resources/crystal/model.mustache b/modules/openapi-generator/src/main/resources/crystal/model.mustache
new file mode 100644
index 00000000000..a19cc5f3f6a
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/crystal/model.mustache
@@ -0,0 +1,23 @@
+# {{#lambdaPrefixWithHash}}{{> api_info}}{{/lambdaPrefixWithHash}}
+
+require "time"
+
+module {{moduleName}}
+{{#models}}
+{{#model}}
+{{#isEnum}}
+{{>partial_model_enum_class}}
+{{/isEnum}}
+{{^isEnum}}
+{{#oneOf}}
+{{#-first}}
+{{>partial_oneof_module}}
+{{/-first}}
+{{/oneOf}}
+{{^oneOf}}
+{{>partial_model_generic}}
+{{/oneOf}}
+{{/isEnum}}
+{{/model}}
+{{/models}}
+end
diff --git a/modules/openapi-generator/src/main/resources/crystal/model_doc.mustache b/modules/openapi-generator/src/main/resources/crystal/model_doc.mustache
new file mode 100644
index 00000000000..37809685d1c
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/crystal/model_doc.mustache
@@ -0,0 +1,12 @@
+{{#models}}
+{{#model}}
+{{#oneOf}}
+{{#-first}}
+{{>partial_oneof_module_doc}}
+{{/-first}}
+{{/oneOf}}
+{{^oneOf}}
+{{>partial_model_generic_doc}}
+{{/oneOf}}
+{{/model}}
+{{/models}}
diff --git a/modules/openapi-generator/src/main/resources/crystal/model_test.mustache b/modules/openapi-generator/src/main/resources/crystal/model_test.mustache
new file mode 100644
index 00000000000..3cc572b5878
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/crystal/model_test.mustache
@@ -0,0 +1,75 @@
+# {{#lambdaPrefixWithHash}}{{> api_info}}{{/lambdaPrefixWithHash}}
+
+require "../spec_helper"
+require "json"
+require "time"
+
+# Unit tests for {{moduleName}}::{{classname}}
+# Automatically generated by openapi-generator (https://openapi-generator.tech)
+# Please update as you see appropriate
+{{#models}}
+{{#model}}
+describe {{moduleName}}::{{classname}} do
+{{^oneOf}}
+
+ describe "test an instance of {{classname}}" do
+ it "should create an instance of {{classname}}" do
+ #instance = {{moduleName}}::{{classname}}.new
+ #expect(instance).to be_instance_of({{moduleName}}::{{classname}})
+ end
+ end
+{{#vars}}
+ describe "test attribute '{{{name}}}'" do
+ it "should work" do
+ {{#isEnum}}
+ # assertion here. ref: https://crystal-lang.org/reference/guides/testing.html
+ # validator = Petstore::EnumTest::EnumAttributeValidator.new("{{{dataType}}}", [{{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}}, {{/-last}}{{/enumVars}}{{/allowableValues}}])
+ # validator.allowable_values.each do |value|
+ # expect { instance.{{name}} = value }.not_to raise_error
+ # end
+ {{/isEnum}}
+ {{^isEnum}}
+ # assertion here. ref: https://crystal-lang.org/reference/guides/testing.html
+ {{/isEnum}}
+ end
+ end
+
+{{/vars}}
+{{/oneOf}}
+{{#oneOf}}
+{{#-first}}
+ describe ".openapi_one_of" do
+ it "lists the items referenced in the oneOf array" do
+ expect(described_class.openapi_one_of).to_not be_empty
+ end
+ end
+
+ {{#discriminator}}
+ {{#propertyName}}
+ describe ".openapi_discriminator_name" do
+ it "returns the value of the "discriminator" property" do
+ expect(described_class.openapi_discriminator_name).to_not be_empty
+ end
+ end
+
+ {{/propertyName}}
+ {{#mappedModels}}
+ {{#-first}}
+ describe ".openapi_discriminator_mapping" do
+ it "returns the key/values of the "mapping" property" do
+ expect(described_class.openapi_discriminator_mapping.values.sort).to eq(described_class.openapi_one_of.sort)
+ end
+ end
+
+ {{/-first}}
+ {{/mappedModels}}
+ {{/discriminator}}
+ describe ".build" do
+ it "returns the correct model" do
+ end
+ end
+{{/-first}}
+{{/oneOf}}
+end
+{{/model}}
+{{/models}}
diff --git a/modules/openapi-generator/src/main/resources/crystal/partial_model_enum_class.mustache b/modules/openapi-generator/src/main/resources/crystal/partial_model_enum_class.mustache
new file mode 100644
index 00000000000..4b8b5a0ffda
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/crystal/partial_model_enum_class.mustache
@@ -0,0 +1,20 @@
+ class {{classname}}{{#allowableValues}}{{#enumVars}}
+ {{{name}}} = {{{value}}}.freeze{{/enumVars}}
+
+{{/allowableValues}}
+ # Builds the enum from string
+ # @param [String] The enum value in the form of the string
+ # @return [String] The enum value
+ def self.build_from_hash(value)
+ new.build_from_hash(value)
+ end
+
+ # Builds the enum from string
+ # @param [String] The enum value in the form of the string
+ # @return [String] The enum value
+ def build_from_hash(value)
+ constantValues = {{classname}}.constants.select { |c| {{classname}}::const_get(c) == value }
+ raise "Invalid ENUM value #{value} for class #{{{classname}}}" if constantValues.empty?
+ value
+ end
+ end
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/crystal/partial_model_generic.mustache b/modules/openapi-generator/src/main/resources/crystal/partial_model_generic.mustache
new file mode 100644
index 00000000000..18f00b1d529
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/crystal/partial_model_generic.mustache
@@ -0,0 +1,296 @@
+ {{#description}}
+ # {{{description}}}
+ {{/description}}
+ class {{classname}}{{#parent}} < {{{.}}}{{/parent}} include JSON::Serializable
+ include JSON::Serializable {{#vars}}
+ {{#description}}
+ # {{{description}}}
+ {{/description}}
+ @[JSON::Field(key: {{{baseName}}}, type: {{{dataType}}}{{#default}}, default: {{{.}}}{{/default}}{{#isNullable}}, nilable: true, emit_null: true{{/isNullable}})]
+ property {{{name}}} : {{{dataType}}}
+
+ {{/vars}}
+{{#hasEnums}}
+ class EnumAttributeValidator
+ getter datatype : String
+ getter allowable_values : Array(String)
+
+ def initialize(datatype, allowable_values)
+ @datatype = datatype
+ @allowable_values = allowable_values.map do |value|
+ case datatype.to_s
+ when /Integer/i
+ value.to_i
+ when /Float/i
+ value.to_f
+ else
+ value
+ end
+ end
+ end
+
+ def valid?(value)
+ !value || allowable_values.include?(value)
+ end
+ end
+
+{{/hasEnums}}
+ {{#anyOf}}
+ {{#-first}}
+ # List of class defined in anyOf (OpenAPI v3)
+ def self.openapi_any_of
+ [
+ {{/-first}}
+ :"{{{.}}}"{{^-last}},{{/-last}}
+ {{#-last}}
+ ]
+ end
+
+ {{/-last}}
+ {{/anyOf}}
+ {{#allOf}}
+ {{#-first}}
+ # List of class defined in allOf (OpenAPI v3)
+ def self.openapi_all_of
+ [
+ {{/-first}}
+ :"{{{.}}}"{{^-last}},{{/-last}}
+ {{#-last}}
+ ]
+ end
+
+ {{/-last}}
+ {{/allOf}}
+ {{#discriminator}}
+ {{#propertyName}}
+ # discriminator's property name in OpenAPI v3
+ def self.openapi_discriminator_name
+ :"{{{.}}}"
+ end
+
+ {{/propertyName}}
+ {{/discriminator}}
+ # Initializes the object
+ # @param [Hash] attributes Model attributes in the form of hash
+ def initialize({{#vars}}@{{{name}}} : {{{dataType}}}{{^required}} | Nil{{/required}}{{^-last}}, {{/-last}}{{/vars}})
+ end
+
+ # Show invalid properties with the reasons. Usually used together with valid?
+ # @return Array for valid properties with the reasons
+ def list_invalid_properties
+ invalid_properties = {{^parent}}Array.new{{/parent}}{{#parent}}super{{/parent}}
+ {{#vars}}
+ {{^isNullable}}
+ {{#required}}
+ if @{{{name}}}.nil?
+ invalid_properties.push("invalid value for \"{{{name}}}\", {{{name}}} cannot be nil.")
+ end
+
+ {{/required}}
+ {{/isNullable}}
+ {{#hasValidation}}
+ {{#maxLength}}
+ if {{^required}}!@{{{name}}}.nil? && {{/required}}@{{{name}}}.to_s.length > {{{maxLength}}}
+ invalid_properties.push("invalid value for \"{{{name}}}\", the character length must be smaller than or equal to {{{maxLength}}}.")
+ end
+
+ {{/maxLength}}
+ {{#minLength}}
+ if {{^required}}!@{{{name}}}.nil? && {{/required}}@{{{name}}}.to_s.length < {{{minLength}}}
+ invalid_properties.push("invalid value for \"{{{name}}}\", the character length must be great than or equal to {{{minLength}}}.")
+ end
+
+ {{/minLength}}
+ {{#maximum}}
+ if {{^required}}!@{{{name}}}.nil? && {{/required}}@{{{name}}} >{{#exclusiveMaximum}}={{/exclusiveMaximum}} {{{maximum}}}
+ invalid_properties.push("invalid value for \"{{{name}}}\", must be smaller than {{^exclusiveMaximum}}or equal to {{/exclusiveMaximum}}{{{maximum}}}.")
+ end
+
+ {{/maximum}}
+ {{#minimum}}
+ if {{^required}}!@{{{name}}}.nil? && {{/required}}@{{{name}}} <{{#exclusiveMinimum}}={{/exclusiveMinimum}} {{{minimum}}}
+ invalid_properties.push("invalid value for \"{{{name}}}\", must be greater than {{^exclusiveMinimum}}or equal to {{/exclusiveMinimum}}{{{minimum}}}.")
+ end
+
+ {{/minimum}}
+ {{#pattern}}
+ pattern = Regexp.new({{{pattern}}})
+ if {{^required}}!@{{{name}}}.nil? && {{/required}}@{{{name}}} !~ pattern
+ invalid_properties.push("invalid value for \"{{{name}}}\", must conform to the pattern #{pattern}.")
+ end
+
+ {{/pattern}}
+ {{#maxItems}}
+ if {{^required}}!@{{{name}}}.nil? && {{/required}}@{{{name}}}.length > {{{maxItems}}}
+ invalid_properties.push("invalid value for \"{{{name}}}\", number of items must be less than or equal to {{{maxItems}}}."
+ end
+
+ {{/maxItems}}
+ {{#minItems}}
+ if {{^required}}!@{{{name}}}.nil? && {{/required}}@{{{name}}}.length < {{{minItems}}}
+ invalid_properties.push("invalid value for \"{{{name}}}\", number of items must be greater than or equal to {{{minItems}}}."
+ end
+
+ {{/minItems}}
+ {{/hasValidation}}
+ {{/vars}}
+ invalid_properties
+ end
+
+ # Check to see if the all the properties in the model are valid
+ # @return true if the model is valid
+ def valid?
+ {{#vars}}
+ {{^isNullable}}
+ {{#required}}
+ return false if @{{{name}}}.nil?
+ {{/required}}
+ {{/isNullable}}
+ {{#isEnum}}
+ {{^isContainer}}
+ {{{name}}}_validator = EnumAttributeValidator.new("{{{dataType}}}", [{{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}}, {{/-last}}{{/enumVars}}{{/allowableValues}}])
+ return false unless {{{name}}}_validator.valid?(@{{{name}}})
+ {{/isContainer}}
+ {{/isEnum}}
+ {{#hasValidation}}
+ {{#maxLength}}
+ return false if {{^required}}!@{{{name}}}.nil? && {{/required}}@{{{name}}}.to_s.length > {{{maxLength}}}
+ {{/maxLength}}
+ {{#minLength}}
+ return false if {{^required}}!@{{{name}}}.nil? && {{/required}}@{{{name}}}.to_s.length < {{{minLength}}}
+ {{/minLength}}
+ {{#maximum}}
+ return false if {{^required}}!@{{{name}}}.nil? && {{/required}}@{{{name}}} >{{#exclusiveMaximum}}={{/exclusiveMaximum}} {{{maximum}}}
+ {{/maximum}}
+ {{#minimum}}
+ return false if {{^required}}!@{{{name}}}.nil? && {{/required}}@{{{name}}} <{{#exclusiveMinimum}}={{/exclusiveMinimum}} {{{minimum}}}
+ {{/minimum}}
+ {{#pattern}}
+ return false if {{^required}}!@{{{name}}}.nil? && {{/required}}@{{{name}}} !~ Regexp.new({{{pattern}}})
+ {{/pattern}}
+ {{#maxItems}}
+ return false if {{^required}}!@{{{name}}}.nil? && {{/required}}@{{{name}}}.length > {{{maxItems}}}
+ {{/maxItems}}
+ {{#minItems}}
+ return false if {{^required}}!@{{{name}}}.nil? && {{/required}}@{{{name}}}.length < {{{minItems}}}
+ {{/minItems}}
+ {{/hasValidation}}
+ {{/vars}}
+ {{#anyOf}}
+ {{#-first}}
+ _any_of_found = false
+ self.class.openapi_any_of.each do |_class|
+ _any_of = {{moduleName}}.const_get(_class).build_from_hash(self.to_hash)
+ if _any_of.valid?
+ _any_of_found = true
+ end
+ end
+
+ if !_any_of_found
+ return false
+ end
+
+ {{/-first}}
+ {{/anyOf}}
+ true{{#parent}} && super{{/parent}}
+ end
+
+ {{#vars}}
+ {{#isEnum}}
+ {{^isContainer}}
+ # Custom attribute writer method checking allowed values (enum).
+ # @param [Object] {{{name}}} Object to be assigned
+ def {{{name}}}=({{{name}}})
+ validator = EnumAttributeValidator.new("{{{dataType}}}", [{{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}}, {{/-last}}{{/enumVars}}{{/allowableValues}}])
+ unless validator.valid?({{{name}}})
+ raise ArgumentError.new("invalid value for \"{{{name}}}\", must be one of #{validator.allowable_values}.")
+ end
+ @{{{name}}} = {{{name}}}
+ end
+
+ {{/isContainer}}
+ {{/isEnum}}
+ {{^isEnum}}
+ {{#hasValidation}}
+ # Custom attribute writer method with validation
+ # @param [Object] {{{name}}} Value to be assigned
+ def {{{name}}}=({{{name}}})
+ {{^isNullable}}
+ {{#required}}
+ if {{{name}}}.nil?
+ raise ArgumentError.new("{{{name}}} cannot be nil")
+ end
+
+ {{/required}}
+ {{/isNullable}}
+ {{#maxLength}}
+ if {{^required}}!{{{name}}}.nil? && {{/required}}{{{name}}}.to_s.length > {{{maxLength}}}
+ raise ArgumentError.new("invalid value for "{{{name}}}", the character length must be smaller than or equal to {{{maxLength}}}.")
+ end
+
+ {{/maxLength}}
+ {{#minLength}}
+ if {{^required}}!{{{name}}}.nil? && {{/required}}{{{name}}}.to_s.length < {{{minLength}}}
+ raise ArgumentError.new("invalid value for \"{{{name}}}\", the character length must be great than or equal to {{{minLength}}}.")
+ end
+
+ {{/minLength}}
+ {{#maximum}}
+ if {{^required}}!{{{name}}}.nil? && {{/required}}{{{name}}} >{{#exclusiveMaximum}}={{/exclusiveMaximum}} {{{maximum}}}
+ raise ArgumentError.new("invalid value for \"{{{name}}}\", must be smaller than {{^exclusiveMaximum}}or equal to {{/exclusiveMaximum}}{{{maximum}}}.")
+ end
+
+ {{/maximum}}
+ {{#minimum}}
+ if {{^required}}!{{{name}}}.nil? && {{/required}}{{{name}}} <{{#exclusiveMinimum}}={{/exclusiveMinimum}} {{{minimum}}}
+ raise ArgumentError.new("invalid value for \"{{{name}}}\", must be greater than {{^exclusiveMinimum}}or equal to {{/exclusiveMinimum}}{{{minimum}}}.")
+ end
+
+ {{/minimum}}
+ {{#pattern}}
+ pattern = Regexp.new({{{pattern}}})
+ if {{^required}}!{{{name}}}.nil? && {{/required}}{{{name}}} !~ pattern
+ raise ArgumentError.new("invalid value for \"{{{name}}}\", must conform to the pattern #{pattern}.")
+ end
+
+ {{/pattern}}
+ {{#maxItems}}
+ if {{^required}}!{{{name}}}.nil? && {{/required}}{{{name}}}.length > {{{maxItems}}}
+ raise ArgumentError.new("invalid value for \"{{{name}}}\", number of items must be less than or equal to {{{maxItems}}}.")
+ end
+
+ {{/maxItems}}
+ {{#minItems}}
+ if {{^required}}!{{{name}}}.nil? && {{/required}}{{{name}}}.length < {{{minItems}}}
+ raise ArgumentError.new("invalid value for \"{{{name}}}\", number of items must be greater than or equal to {{{minItems}}}.")
+ end
+
+ {{/minItems}}
+ @{{{name}}} = {{{name}}}
+ end
+
+ {{/hasValidation}}
+ {{/isEnum}}
+ {{/vars}}
+ # Checks equality by comparing each attribute.
+ # @param [Object] Object to be compared
+ def ==(o)
+ return true if self.equal?(o)
+ self.class == o.class{{#vars}} &&
+ {{name}} == o.{{name}}{{/vars}}{{#parent}} && super(o){{/parent}}
+ end
+
+ # @see the `==` method
+ # @param [Object] Object to be compared
+ def eql?(o)
+ self == o
+ end
+
+ # Calculates hash code according to all attributes.
+ # @return [Integer] Hash code
+ def hash
+ [{{#vars}}{{name}}{{^-last}}, {{/-last}}{{/vars}}].hash
+ end
+
+{{> base_object}}
+ end
diff --git a/modules/openapi-generator/src/main/resources/crystal/partial_model_generic_doc.mustache b/modules/openapi-generator/src/main/resources/crystal/partial_model_generic_doc.mustache
new file mode 100644
index 00000000000..f188dd23e6e
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/crystal/partial_model_generic_doc.mustache
@@ -0,0 +1,28 @@
+# {{moduleName}}::{{classname}}
+
+## Properties
+
+| Name | Type | Description | Notes |
+| ---- | ---- | ----------- | ----- |
+{{#vars}}
+| **{{name}}** | {{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}[**{{dataType}}**]({{complexType}}.md){{/isPrimitiveType}} | {{description}} | {{^required}}[optional]{{/required}}{{#isReadOnly}}[readonly]{{/isReadOnly}}{{#defaultValue}}[default to {{defaultValue}}]{{/defaultValue}} |
+{{/vars}}
+
+## Example
+
+```ruby
+require '{{{gemName}}}'
+
+{{^vars}}
+instance = {{moduleName}}::{{classname}}.new()
+{{/vars}}
+{{#vars}}
+{{#-first}}
+instance = {{moduleName}}::{{classname}}.new(
+{{/-first}}
+ {{name}}: {{example}}{{^-last}},{{/-last}}
+{{#-last}}
+)
+{{/-last}}
+{{/vars}}
+```
diff --git a/modules/openapi-generator/src/main/resources/crystal/partial_oneof_module.mustache b/modules/openapi-generator/src/main/resources/crystal/partial_oneof_module.mustache
new file mode 100644
index 00000000000..14dc1635bb7
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/crystal/partial_oneof_module.mustache
@@ -0,0 +1,137 @@
+ {{#description}}
+ # {{{description}}}
+ {{/description}}
+ module {{classname}}
+ class << self
+ {{#oneOf}}
+ {{#-first}}
+ # List of class defined in oneOf (OpenAPI v3)
+ def openapi_one_of
+ [
+ {{/-first}}
+ :'{{{.}}}'{{^-last}},{{/-last}}
+ {{#-last}}
+ ]
+ end
+
+ {{/-last}}
+ {{/oneOf}}
+ {{#discriminator}}
+ {{#propertyName}}
+ # Discriminator's property name (OpenAPI v3)
+ def openapi_discriminator_name
+ :'{{{.}}}'
+ end
+
+ {{/propertyName}}
+ {{#mappedModels}}
+ {{#-first}}
+ # Discriminator's mapping (OpenAPI v3)
+ def openapi_discriminator_mapping
+ {
+ {{/-first}}
+ :'{{{mappingName}}}' => :'{{{modelName}}}'{{^-last}},{{/-last}}
+ {{#-last}}
+ }
+ end
+
+ {{/-last}}
+ {{/mappedModels}}
+ {{/discriminator}}
+ # Builds the object
+ # @param [Mixed] Data to be matched against the list of oneOf items
+ # @return [Object] Returns the model or the data itself
+ def build(data)
+ {{#discriminator}}
+ discriminator_value = data[openapi_discriminator_name]
+ return nil unless discriminator_value
+ {{#mappedModels}}
+ {{#-first}}
+
+ klass = openapi_discriminator_mapping[discriminator_value.to_sym]
+ return nil unless klass
+
+ {{moduleName}}.const_get(klass).build_from_hash(data)
+ {{/-first}}
+ {{/mappedModels}}
+ {{^mappedModels}}
+ {{moduleName}}.const_get(discriminator_value).build_from_hash(data)
+ {{/mappedModels}}
+ {{/discriminator}}
+ {{^discriminator}}
+ # Go through the list of oneOf items and attempt to identify the appropriate one.
+ # Note:
+ # - We do not attempt to check whether exactly one item matches.
+ # - No advanced validation of types in some cases (e.g. "x: { type: string }" will happily match { x: 123 })
+ # due to the way the deserialization is made in the base_object template (it just casts without verifying).
+ # - TODO: scalar values are defacto behaving as if they were nullable.
+ # - TODO: logging when debugging is set.
+ openapi_one_of.each do |klass|
+ begin
+ next if klass == :AnyType # "nullable: true"
+ typed_data = find_and_cast_into_type(klass, data)
+ return typed_data if typed_data
+ rescue # rescue all errors so we keep iterating even if the current item lookup raises
+ end
+ end
+
+ openapi_one_of.include?(:AnyType) ? data : nil
+ {{/discriminator}}
+ end
+ {{^discriminator}}
+
+ private
+
+ SchemaMismatchError = Class.new(StandardError)
+
+ # Note: 'File' is missing here because in the regular case we get the data _after_ a call to JSON.parse.
+ def find_and_cast_into_type(klass, data)
+ return if data.nil?
+
+ case klass.to_s
+ when 'Boolean'
+ return data if data.instance_of?(TrueClass) || data.instance_of?(FalseClass)
+ when 'Float'
+ return data if data.instance_of?(Float)
+ when 'Integer'
+ return data if data.instance_of?(Integer)
+ when 'Time'
+ return Time.parse(data)
+ when 'Date'
+ return Date.parse(data)
+ when 'String'
+ return data if data.instance_of?(String)
+ when 'Object' # "type: object"
+ return data if data.instance_of?(Hash)
+ when /\AArray<(?.+)>\z/ # "type: array"
+ if data.instance_of?(Array)
+ sub_type = Regexp.last_match[:sub_type]
+ return data.map { |item| find_and_cast_into_type(sub_type, item) }
+ end
+ when /\AHash.+)>\z/ # "type: object" with "additionalProperties: { ... }"
+ if data.instance_of?(Hash) && data.keys.all? { |k| k.instance_of?(Symbol) || k.instance_of?(String) }
+ sub_type = Regexp.last_match[:sub_type]
+ return data.each_with_object({}) { |(k, v), hsh| hsh[k] = find_and_cast_into_type(sub_type, v) }
+ end
+ else # model
+ const = {{moduleName}}.const_get(klass)
+ if const
+ if const.respond_to?(:openapi_one_of) # nested oneOf model
+ model = const.build(data)
+ return model if model
+ else
+ # raise if data contains keys that are not known to the model
+ raise unless (data.keys - const.acceptable_attributes).empty?
+ model = const.build_from_hash(data)
+ return model if model && model.valid?
+ end
+ end
+ end
+
+ raise # if no match by now, raise
+ rescue
+ raise SchemaMismatchError, "#{data} doesn't match the #{klass} type"
+ end
+ {{/discriminator}}
+ end
+ end
diff --git a/modules/openapi-generator/src/main/resources/crystal/partial_oneof_module_doc.mustache b/modules/openapi-generator/src/main/resources/crystal/partial_oneof_module_doc.mustache
new file mode 100644
index 00000000000..64a6c32dc85
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/crystal/partial_oneof_module_doc.mustache
@@ -0,0 +1,92 @@
+# {{moduleName}}::{{classname}}
+
+## Class instance methods
+
+### `openapi_one_of`
+
+Returns the list of classes defined in oneOf.
+
+#### Example
+
+```ruby
+require '{{{gemName}}}'
+
+{{moduleName}}::{{classname}}.openapi_one_of
+# =>
+{{#oneOf}}
+{{#-first}}
+# [
+{{/-first}}
+# :'{{{.}}}'{{^-last}},{{/-last}}
+{{#-last}}
+# ]
+{{/-last}}
+{{/oneOf}}
+```
+{{#discriminator}}
+{{#propertyName}}
+
+### `openapi_discriminator_name`
+
+Returns the discriminator's property name.
+
+#### Example
+
+```ruby
+require '{{{gemName}}}'
+
+{{moduleName}}::{{classname}}.openapi_discriminator_name
+# => :'{{{.}}}'
+```
+{{/propertyName}}
+{{#mappedModels}}
+{{#-first}}
+
+### `openapi_discriminator_name`
+
+Returns the discriminator's mapping.
+
+#### Example
+
+```ruby
+require '{{{gemName}}}'
+
+{{moduleName}}::{{classname}}.openapi_discriminator_mapping
+# =>
+# {
+{{/-first}}
+# :'{{{mappingName}}}' => :'{{{modelName}}}'{{^-last}},{{/-last}}
+{{#-last}}
+# }
+{{/-last}}
+{{/mappedModels}}
+{{/discriminator}}
+
+### build
+
+Find the appropriate object from the `openapi_one_of` list and casts the data into it.
+
+#### Example
+
+```ruby
+require '{{{gemName}}}'
+
+{{moduleName}}::{{classname}}.build(data)
+# => {{#oneOf}}{{#-first}}#<{{{.}}}:0x00007fdd4aab02a0>{{/-first}}{{/oneOf}}
+
+{{moduleName}}::{{classname}}.build(data_that_doesnt_match)
+# => nil
+```
+
+#### Parameters
+
+| Name | Type | Description |
+| ---- | ---- | ----------- |
+| **data** | **Mixed** | data to be matched against the list of oneOf items |
+
+#### Return type
+
+{{#oneOf}}
+- `{{{.}}}`
+{{/oneOf}}
+- `nil` (if no type matches)
diff --git a/modules/openapi-generator/src/main/resources/crystal/rspec.mustache b/modules/openapi-generator/src/main/resources/crystal/rspec.mustache
new file mode 100644
index 00000000000..83e16f80447
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/crystal/rspec.mustache
@@ -0,0 +1,2 @@
+--color
+--require spec_helper
diff --git a/modules/openapi-generator/src/main/resources/crystal/rubocop.mustache b/modules/openapi-generator/src/main/resources/crystal/rubocop.mustache
new file mode 100644
index 00000000000..d32b2b1cdab
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/crystal/rubocop.mustache
@@ -0,0 +1,148 @@
+# This file is based on https://github.com/rails/rails/blob/master/.rubocop.yml (MIT license)
+# Automatically generated by OpenAPI Generator (https://openapi-generator.tech)
+AllCops:
+ TargetRubyVersion: 2.4
+ # RuboCop has a bunch of cops enabled by default. This setting tells RuboCop
+ # to ignore them, so only the ones explicitly set in this file are enabled.
+ DisabledByDefault: true
+ Exclude:
+ - '**/templates/**/*'
+ - '**/vendor/**/*'
+ - 'actionpack/lib/action_dispatch/journey/parser.rb'
+
+# Prefer &&/|| over and/or.
+Style/AndOr:
+ Enabled: true
+
+# Align `when` with `case`.
+Layout/CaseIndentation:
+ Enabled: true
+
+# Align comments with method definitions.
+Layout/CommentIndentation:
+ Enabled: true
+
+Layout/ElseAlignment:
+ Enabled: true
+
+Layout/EmptyLineAfterMagicComment:
+ Enabled: true
+
+# In a regular class definition, no empty lines around the body.
+Layout/EmptyLinesAroundClassBody:
+ Enabled: true
+
+# In a regular method definition, no empty lines around the body.
+Layout/EmptyLinesAroundMethodBody:
+ Enabled: true
+
+# In a regular module definition, no empty lines around the body.
+Layout/EmptyLinesAroundModuleBody:
+ Enabled: true
+
+Layout/FirstArgumentIndentation:
+ Enabled: true
+
+# Use Ruby >= 1.9 syntax for hashes. Prefer { a: :b } over { :a => :b }.
+Style/HashSyntax:
+ Enabled: false
+
+# Method definitions after `private` or `protected` isolated calls need one
+# extra level of indentation.
+Layout/IndentationConsistency:
+ Enabled: true
+ EnforcedStyle: indented_internal_methods
+
+# Two spaces, no tabs (for indentation).
+Layout/IndentationWidth:
+ Enabled: true
+
+Layout/LeadingCommentSpace:
+ Enabled: true
+
+Layout/SpaceAfterColon:
+ Enabled: true
+
+Layout/SpaceAfterComma:
+ Enabled: true
+
+Layout/SpaceAroundEqualsInParameterDefault:
+ Enabled: true
+
+Layout/SpaceAroundKeyword:
+ Enabled: true
+
+Layout/SpaceAroundOperators:
+ Enabled: true
+
+Layout/SpaceBeforeComma:
+ Enabled: true
+
+Layout/SpaceBeforeFirstArg:
+ Enabled: true
+
+Style/DefWithParentheses:
+ Enabled: true
+
+# Defining a method with parameters needs parentheses.
+Style/MethodDefParentheses:
+ Enabled: true
+
+Style/FrozenStringLiteralComment:
+ Enabled: false
+ EnforcedStyle: always
+
+# Use `foo {}` not `foo{}`.
+Layout/SpaceBeforeBlockBraces:
+ Enabled: true
+
+# Use `foo { bar }` not `foo {bar}`.
+Layout/SpaceInsideBlockBraces:
+ Enabled: true
+
+# Use `{ a: 1 }` not `{a:1}`.
+Layout/SpaceInsideHashLiteralBraces:
+ Enabled: true
+
+Layout/SpaceInsideParens:
+ Enabled: true
+
+# Check quotes usage according to lint rule below.
+#Style/StringLiterals:
+# Enabled: true
+# EnforcedStyle: single_quotes
+
+# Detect hard tabs, no hard tabs.
+Layout/IndentationStyle:
+ Enabled: true
+
+# Blank lines should not have any spaces.
+Layout/TrailingEmptyLines:
+ Enabled: true
+
+# No trailing whitespace.
+Layout/TrailingWhitespace:
+ Enabled: false
+
+# Use quotes for string literals when they are enough.
+Style/RedundantPercentQ:
+ Enabled: true
+
+# Align `end` with the matching keyword or starting expression except for
+# assignments, where it should be aligned with the LHS.
+Layout/EndAlignment:
+ Enabled: true
+ EnforcedStyleAlignWith: variable
+ AutoCorrect: true
+
+# Use my_method(my_arg) not my_method( my_arg ) or my_method my_arg.
+Lint/RequireParentheses:
+ Enabled: true
+
+Style/RedundantReturn:
+ Enabled: true
+ AllowMultipleReturnValues: true
+
+Style/Semicolon:
+ Enabled: true
+ AllowAsExpressionSeparator: true
diff --git a/modules/openapi-generator/src/main/resources/crystal/shard.mustache b/modules/openapi-generator/src/main/resources/crystal/shard.mustache
new file mode 100644
index 00000000000..89376ca4593
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/crystal/shard.mustache
@@ -0,0 +1,20 @@
+name: {{{moduleName}}}
+version: {{{shardVersion}}}
+authors:
+ - {{{shardAuthors}}}
+description: |
+ - {{{ shardDescription}}}
+crystal: ">= 0.35.1"
+dependencies:
+ crest:
+ github: mamantoha/crest
+ version: ~> 0.26.0
+
+development_dependencies:
+ kemal:
+ github: kemalcr/kemal
+ version: ~>0.27.0
+ ameba:
+ github: crystal-ameba/ameba
+
+license: {{{shardLicense}}}
diff --git a/modules/openapi-generator/src/main/resources/crystal/shard_name.mustache b/modules/openapi-generator/src/main/resources/crystal/shard_name.mustache
new file mode 100644
index 00000000000..7e3e9b162d4
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/crystal/shard_name.mustache
@@ -0,0 +1,27 @@
+# {{#lambdaPrefixWithHash}}{{> api_info}}{{/lambdaPrefixWithHash}}
+
+# Dependencies
+require "crest"
+require "log"
+
+module {{moduleName}}
+ Log = ::Log.for("{{moduleName}}") # => Log for {{moduleName}} source
+
+ VERSION = {{ `shards version #{__DIR__}`.chomp.stringify }}
+
+ # Customize default settings for the SDK using block.
+ # {{moduleName}}.configure do |config|
+ # config.username = "xxx"
+ # config.password = "xxx"
+ # end
+ # If no block given, return the default Configuration object.
+ def configure
+ if block_given?
+ yield(Configuration.default)
+ else
+ Configuration.default
+ end
+ end
+end
+
+require "./{{shardName}}/**"
diff --git a/modules/openapi-generator/src/main/resources/crystal/spec_helper.mustache b/modules/openapi-generator/src/main/resources/crystal/spec_helper.mustache
new file mode 100644
index 00000000000..9facafa4a93
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/crystal/spec_helper.mustache
@@ -0,0 +1,6 @@
+# {{#lambdaPrefixWithHash}}{{> api_info}}{{/lambdaPrefixWithHash}}
+
+# load modules
+require "spec"
+require "json"
+require "../src/{{{shardName}}}"
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/crystal/travis.mustache b/modules/openapi-generator/src/main/resources/crystal/travis.mustache
new file mode 100644
index 00000000000..21509cfe82a
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/crystal/travis.mustache
@@ -0,0 +1,8 @@
+# {{#lambdaPrefixWithHash}}{{> api_info}}{{/lambdaPrefixWithHash}}
+
+language: crystal
+
+script:
+ - crystal spec
+# uncomment below to check the code format
+# - crystal tool format --check
diff --git a/modules/openapi-generator/src/main/resources/crystal/version.mustache b/modules/openapi-generator/src/main/resources/crystal/version.mustache
new file mode 100644
index 00000000000..9be1d633971
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/crystal/version.mustache
@@ -0,0 +1,5 @@
+# {{#lambdaPrefixWithHash}}{{> api_info}}{{/lambdaPrefixWithHash}}
+
+module {{moduleName}}
+ VERSION = '{{shardVersion}}'
+end
diff --git a/modules/openapi-generator/src/main/resources/csharp-netcore/ApiClient.mustache b/modules/openapi-generator/src/main/resources/csharp-netcore/ApiClient.mustache
index 504b5a22439..c1d025d7f03 100644
--- a/modules/openapi-generator/src/main/resources/csharp-netcore/ApiClient.mustache
+++ b/modules/openapi-generator/src/main/resources/csharp-netcore/ApiClient.mustache
@@ -65,10 +65,10 @@ namespace {{packageName}}.Client
/// A JSON string.
public string Serialize(object obj)
{
- if (obj != null && obj is {{{packageName}}}.Model.AbstractOpenAPISchema)
+ if (obj != null && obj is {{{packageName}}}.{{modelPackage}}.AbstractOpenAPISchema)
{
// the object to be serialized is an oneOf/anyOf schema
- return (({{{packageName}}}.Model.AbstractOpenAPISchema)obj).ToJson();
+ return (({{{packageName}}}.{{modelPackage}}.AbstractOpenAPISchema)obj).ToJson();
}
else
{
@@ -576,7 +576,7 @@ namespace {{packageName}}.Client
}
// if the response type is oneOf/anyOf, call FromJSON to deserialize the data
- if (typeof({{{packageName}}}.Model.AbstractOpenAPISchema).IsAssignableFrom(typeof(T)))
+ if (typeof({{{packageName}}}.{{modelPackage}}.AbstractOpenAPISchema).IsAssignableFrom(typeof(T)))
{
T instance = (T)Activator.CreateInstance(typeof(T));
MethodInfo method = typeof(T).GetMethod("FromJson");
diff --git a/modules/openapi-generator/src/main/resources/dart-dio/analysis_options.mustache b/modules/openapi-generator/src/main/resources/dart-dio/analysis_options.mustache
index 655cb67bd88..a611887d3ac 100644
--- a/modules/openapi-generator/src/main/resources/dart-dio/analysis_options.mustache
+++ b/modules/openapi-generator/src/main/resources/dart-dio/analysis_options.mustache
@@ -1 +1,9 @@
-analyzer:
\ No newline at end of file
+analyzer:
+ language:
+ strict-inference: true
+ strict-raw-types: true
+ strong-mode:
+ implicit-dynamic: false
+ implicit-casts: false
+ exclude:
+ - test/*.dart
diff --git a/modules/openapi-generator/src/main/resources/dart-dio/api.mustache b/modules/openapi-generator/src/main/resources/dart-dio/api.mustache
index a7f830a5135..ee4b9a0579c 100644
--- a/modules/openapi-generator/src/main/resources/dart-dio/api.mustache
+++ b/modules/openapi-generator/src/main/resources/dart-dio/api.mustache
@@ -1,3 +1,4 @@
+{{>header}}
import 'dart:async';
import 'dart:convert';
import 'package:dio/dio.dart';
@@ -31,8 +32,8 @@ class {{classname}} {
}) async {
final String _path = '{{{path}}}'{{#pathParams}}.replaceAll('{' r'{{baseName}}' '}', {{{paramName}}}.toString()){{/pathParams}};
- final Map queryParams = {};
- final Map headerParams = {
+ final queryParams = {};
+ final headerParams = {
if (headers != null) ...headers,
};
dynamic bodyData;
@@ -43,15 +44,15 @@ class {{classname}} {
{{#queryParams}}
queryParams[r'{{baseName}}'] = {{paramName}};
{{/queryParams}}
- queryParams.removeWhere((key, value) => value == null);
- headerParams.removeWhere((key, value) => value == null);
+ queryParams.removeWhere((key, dynamic value) => value == null);
+ headerParams.removeWhere((key, dynamic value) => value == null);
- final List contentTypes = [{{^hasConsumes}}];{{/hasConsumes}}{{#hasConsumes}}{{#consumes}}
+ final contentTypes = [{{^hasConsumes}}];{{/hasConsumes}}{{#hasConsumes}}{{#consumes}}
'{{{mediaType}}}',{{/consumes}}
];{{/hasConsumes}}
{{#hasFormParams}}
- final Map formData = {};
+ final formData = {};
{{#isMultipart}}
{{#formParams}}
{{^isFile}}
@@ -76,18 +77,30 @@ class {{classname}} {
{{/hasFormParams}}
{{#bodyParam}}
- {{#isArray}}
+ {{#isContainer}}
+ {{#isArray}}
const type = FullType(BuiltList, [FullType({{baseType}})]);
final serializedBody = _serializers.serialize({{paramName}}, specifiedType: type);
- {{/isArray}}
- {{^isArray}}
- final serializedBody = _serializers.serialize({{paramName}});
- {{/isArray}}
+ {{/isArray}}
+ {{#isMap}}
+ const type = FullType(BuiltMap, [FullType(String), FullType({{baseType}})]);
+ final serializedBody = _serializers.serialize({{paramName}}, specifiedType: type);
+ {{/isMap}}
+ {{/isContainer}}
+ {{^isContainer}}
+ {{#isPrimitiveType}}
+ var serializedBody = {{paramName}};
+ {{/isPrimitiveType}}
+ {{^isPrimitiveType}}
+ final bodySerializer = _serializers.serializerForType({{{baseType}}}) as Serializer<{{{baseType}}}>;
+ final serializedBody = _serializers.serializeWith(bodySerializer, {{paramName}});
+ {{/isPrimitiveType}}
+ {{/isContainer}}
final json{{paramName}} = json.encode(serializedBody);
bodyData = json{{paramName}};
{{/bodyParam}}
- return _dio.request(
+ return _dio.request(
_path,
queryParameters: queryParams,
data: bodyData,
@@ -97,8 +110,8 @@ class {{classname}} {
responseType: ResponseType.bytes,
{{/isResponseFile}}
headers: headerParams,
- extra: {
- 'secure': [{{^hasAuthMethods}}],{{/hasAuthMethods}}{{#hasAuthMethods}}
+ extra: {
+ 'secure': >[{{^hasAuthMethods}}],{{/hasAuthMethods}}{{#hasAuthMethods}}
{{#authMethods}}{
'type': '{{type}}',
'name': '{{name}}',{{#isApiKey}}
@@ -124,14 +137,22 @@ class {{classname}} {
final data = response.data as {{{returnType}}};
{{/returnTypeIsPrimitive}}
{{^returnTypeIsPrimitive}}
- final serializer = _serializers.serializerForType({{{returnType}}});
- final data = _serializers.deserializeWith<{{{returnType}}}>(serializer, response.data is String ? jsonDecode(response.data) : response.data);
+ final serializer = _serializers.serializerForType({{{returnType}}}) as Serializer<{{{returnType}}}>;
+ final data = _serializers.deserializeWith<{{{returnType}}}>(
+ serializer,
+ response.data is String ? jsonDecode(response.data as String) : response.data,
+ );
{{/returnTypeIsPrimitive}}
{{/returnSimpleType}}
{{^returnSimpleType}}
const collectionType = {{#isMap}}BuiltMap{{/isMap}}{{^isMap}}BuiltList{{/isMap}};
const type = FullType(collectionType, [{{#isMap}}FullType(String), {{/isMap}}FullType({{{returnBaseType}}})]);
- final {{{returnType}}} data = _serializers.deserialize(response.data is String ? jsonDecode(response.data) : response.data, specifiedType: type);
+ final data = _serializers.deserialize(
+ response.data is String
+ ? jsonDecode(response.data as String)
+ : response.data,
+ specifiedType: type,
+ ) as {{{returnType}}};
{{/returnSimpleType}}
{{/isResponseFile}}
diff --git a/modules/openapi-generator/src/main/resources/dart-dio/api_util.mustache b/modules/openapi-generator/src/main/resources/dart-dio/api_util.mustache
index e99530f6c39..c25fc859c05 100644
--- a/modules/openapi-generator/src/main/resources/dart-dio/api_util.mustache
+++ b/modules/openapi-generator/src/main/resources/dart-dio/api_util.mustache
@@ -1,3 +1,4 @@
+{{>header}}
import 'dart:convert';
import 'package:built_value/serializer.dart';
diff --git a/modules/openapi-generator/src/main/resources/dart-dio/apilib.mustache b/modules/openapi-generator/src/main/resources/dart-dio/apilib.mustache
index 1ad200a5a7b..645a5fe7877 100644
--- a/modules/openapi-generator/src/main/resources/dart-dio/apilib.mustache
+++ b/modules/openapi-generator/src/main/resources/dart-dio/apilib.mustache
@@ -1,3 +1,4 @@
+{{>header}}
library {{pubName}}.api;
import 'package:dio/dio.dart';
diff --git a/modules/openapi-generator/src/main/resources/dart-dio/auth/api_key_auth.mustache b/modules/openapi-generator/src/main/resources/dart-dio/auth/api_key_auth.mustache
index dac23a9fcbc..f5dd98b6b09 100644
--- a/modules/openapi-generator/src/main/resources/dart-dio/auth/api_key_auth.mustache
+++ b/modules/openapi-generator/src/main/resources/dart-dio/auth/api_key_auth.mustache
@@ -1,3 +1,4 @@
+{{>header}}
import 'dart:async';
import 'package:{{pubName}}/auth/auth.dart';
import 'package:dio/dio.dart';
@@ -6,12 +7,12 @@ class ApiKeyAuthInterceptor extends AuthInterceptor {
Map apiKeys = {};
@override
- Future onRequest(RequestOptions options) {
- final authInfo = getAuthInfo(options, "apiKey");
- for (var info in authInfo) {
- final authName = info["name"];
- final authKeyName = info["keyName"];
- final authWhere = info["where"];
+ Future onRequest(RequestOptions options) {
+ final authInfo = getAuthInfo(options, 'apiKey');
+ for (final info in authInfo) {
+ final authName = info['name'] as String;
+ final authKeyName = info['keyName'] as String;
+ final authWhere = info['where'] as String;
final apiKey = apiKeys[authName];
if (apiKey != null) {
if (authWhere == 'query') {
diff --git a/modules/openapi-generator/src/main/resources/dart-dio/auth/auth.mustache b/modules/openapi-generator/src/main/resources/dart-dio/auth/auth.mustache
index 703127c762b..e6dbe70849d 100644
--- a/modules/openapi-generator/src/main/resources/dart-dio/auth/auth.mustache
+++ b/modules/openapi-generator/src/main/resources/dart-dio/auth/auth.mustache
@@ -1,5 +1,4 @@
-import 'dart:async';
-
+{{>header}}
import 'package:dio/dio.dart';
abstract class AuthInterceptor extends Interceptor {
@@ -8,11 +7,11 @@ abstract class AuthInterceptor extends Interceptor {
* Can return null if type is not present on auth data or if route doesn't need authentication
*/
List> getAuthInfo(RequestOptions route, String type) {
- if (route.extra.containsKey("secure")) {
- final auth = route.extra["secure"];
- List> results = [];
- for (var info in auth) {
- if(info["type"] == type) {
+ if (route.extra.containsKey('secure')) {
+ final auth = route.extra['secure'] as List>;
+ final results = >[];
+ for (final info in auth) {
+ if (info['type'] == type) {
results.add(info);
}
}
diff --git a/modules/openapi-generator/src/main/resources/dart-dio/auth/basic_auth.mustache b/modules/openapi-generator/src/main/resources/dart-dio/auth/basic_auth.mustache
index bcf4b50f59e..cd5faa2dbdd 100644
--- a/modules/openapi-generator/src/main/resources/dart-dio/auth/basic_auth.mustache
+++ b/modules/openapi-generator/src/main/resources/dart-dio/auth/basic_auth.mustache
@@ -1,3 +1,4 @@
+{{>header}}
import 'dart:async';
import 'dart:convert';
import 'package:{{pubName}}/auth/auth.dart';
@@ -14,13 +15,13 @@ class BasicAuthInterceptor extends AuthInterceptor {
Map authInfo = {};
@override
- Future onRequest(RequestOptions options) {
+ Future onRequest(RequestOptions options) {
final metadataAuthInfo = getAuthInfo(options, 'basic');
- for (var info in metadataAuthInfo) {
- final authName = info['name'];
+ for (final info in metadataAuthInfo) {
+ final authName = info['name'] as String;
final basicAuthInfo = authInfo[authName];
- if(basicAuthInfo != null) {
- String basicAuth = 'Basic ' + base64Encode(utf8.encode('${basicAuthInfo.username}:${basicAuthInfo.password}'));
+ if (basicAuthInfo != null) {
+ final basicAuth = 'Basic ' + base64Encode(utf8.encode('${basicAuthInfo.username}:${basicAuthInfo.password}'));
options.headers['Authorization'] = basicAuth;
break;
}
diff --git a/modules/openapi-generator/src/main/resources/dart-dio/auth/oauth.mustache b/modules/openapi-generator/src/main/resources/dart-dio/auth/oauth.mustache
index 282428297a8..d70e7bb380f 100644
--- a/modules/openapi-generator/src/main/resources/dart-dio/auth/oauth.mustache
+++ b/modules/openapi-generator/src/main/resources/dart-dio/auth/oauth.mustache
@@ -1,3 +1,4 @@
+{{>header}}
import 'dart:async';
import 'package:{{pubName}}/auth/auth.dart';
import 'package:dio/dio.dart';
@@ -6,12 +7,12 @@ class OAuthInterceptor extends AuthInterceptor {
Map tokens = {};
@override
- Future onRequest(RequestOptions options) {
- final authInfo = getAuthInfo(options, "oauth");
- for (var info in authInfo) {
- final token = tokens[info["name"]];
- if(token != null) {
- options.headers["Authorization"] = "Bearer ${token}";
+ Future onRequest(RequestOptions options) {
+ final authInfo = getAuthInfo(options, 'oauth');
+ for (final info in authInfo) {
+ final token = tokens[info['name']];
+ if (token != null) {
+ options.headers['Authorization'] = 'Bearer ${token}';
break;
}
}
diff --git a/modules/openapi-generator/src/main/resources/dart-dio/class.mustache b/modules/openapi-generator/src/main/resources/dart-dio/class.mustache
index 37499b5a822..aef4915cc0b 100644
--- a/modules/openapi-generator/src/main/resources/dart-dio/class.mustache
+++ b/modules/openapi-generator/src/main/resources/dart-dio/class.mustache
@@ -25,7 +25,7 @@ abstract class {{classname}} implements Built<{{classname}}, {{classname}}Builde
static void _initializeBuilder({{{classname}}}Builder b) => b{{#vars}}{{#defaultValue}}
..{{{name}}} = {{#isEnum}}{{^isContainer}}const {{{classname}}}{{{enumName}}}._({{/isContainer}}{{/isEnum}}{{{defaultValue}}}{{#isEnum}}{{^isContainer}}){{/isContainer}}{{/isEnum}}{{/defaultValue}}{{/vars}};
- factory {{classname}}([updates({{classname}}Builder b)]) = _${{classname}};
+ factory {{classname}}([void updates({{classname}}Builder b)]) = _${{classname}};
static Serializer<{{classname}}> get serializer => _${{#lambda.camelcase}}{{{classname}}}{{/lambda.camelcase}}Serializer;
}
{{!
diff --git a/modules/openapi-generator/src/main/resources/dart-dio/header.mustache b/modules/openapi-generator/src/main/resources/dart-dio/header.mustache
new file mode 100644
index 00000000000..2c023d48899
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/dart-dio/header.mustache
@@ -0,0 +1,6 @@
+//
+// AUTO-GENERATED FILE, DO NOT MODIFY!
+//
+// @dart=2.6
+
+// ignore_for_file: unused_import
diff --git a/modules/openapi-generator/src/main/resources/dart-dio/local_date_serializer.mustache b/modules/openapi-generator/src/main/resources/dart-dio/local_date_serializer.mustache
index 21b52dc2bc2..31440035f4a 100644
--- a/modules/openapi-generator/src/main/resources/dart-dio/local_date_serializer.mustache
+++ b/modules/openapi-generator/src/main/resources/dart-dio/local_date_serializer.mustache
@@ -1,3 +1,4 @@
+{{>header}}
import 'package:built_collection/built_collection.dart';
import 'package:built_value/serializer.dart';
import 'package:time_machine/time_machine.dart';
diff --git a/modules/openapi-generator/src/main/resources/dart-dio/model.mustache b/modules/openapi-generator/src/main/resources/dart-dio/model.mustache
index b696da9cdb4..5fdbb1b77c8 100644
--- a/modules/openapi-generator/src/main/resources/dart-dio/model.mustache
+++ b/modules/openapi-generator/src/main/resources/dart-dio/model.mustache
@@ -1,3 +1,4 @@
+{{>header}}
{{#models}}
{{#model}}
{{#imports}}
diff --git a/modules/openapi-generator/src/main/resources/dart-dio/pubspec.mustache b/modules/openapi-generator/src/main/resources/dart-dio/pubspec.mustache
index c7e8bfd75f7..aefe59a62e5 100644
--- a/modules/openapi-generator/src/main/resources/dart-dio/pubspec.mustache
+++ b/modules/openapi-generator/src/main/resources/dart-dio/pubspec.mustache
@@ -2,7 +2,7 @@ name: {{pubName}}
version: {{pubVersion}}
description: {{pubDescription}}
environment:
- sdk: ">=2.3.0 <3.0.0"
+ sdk: ">=2.6.0 <3.0.0"
dependencies:
dio: ^3.0.9
built_value: ^7.1.0
diff --git a/modules/openapi-generator/src/main/resources/dart-dio/serializers.mustache b/modules/openapi-generator/src/main/resources/dart-dio/serializers.mustache
index 16998d739db..eb73bd7453a 100644
--- a/modules/openapi-generator/src/main/resources/dart-dio/serializers.mustache
+++ b/modules/openapi-generator/src/main/resources/dart-dio/serializers.mustache
@@ -1,3 +1,4 @@
+{{>header}}
library serializers;
import 'package:built_value/iso_8601_date_time_serializer.dart';
@@ -9,25 +10,26 @@ import 'package:built_value/standard_json_plugin.dart';
import 'package:{{pubName}}/local_date_serializer.dart';{{/timeMachine}}
{{#models}}{{#model}}import 'package:{{pubName}}/model/{{classFilename}}.dart';
{{/model}}{{/models}}
-
part 'serializers.g.dart';
-@SerializersFor(const [
-{{#models}}{{#model}}{{classname}},
-{{/model}}{{/models}}
+@SerializersFor(const [{{#models}}{{#model}}
+ {{classname}},{{/model}}{{/models}}
])
-
-//allow all models to be serialized within a list
-Serializers serializers = (_$serializers.toBuilder()
-{{#models}}{{#model}}..addBuilderFactory(
-const FullType(BuiltList, const [const FullType({{classname}})]),
-() => new ListBuilder<{{classname}}>())
-{{/model}}{{/models}}
-..add(Iso8601DateTimeSerializer())
-).build();
+Serializers serializers = (_$serializers.toBuilder(){{#apiInfo}}{{#apis}}{{#serializers}}
+ ..addBuilderFactory(
+{{#isArray}}
+ const FullType(BuiltList, [FullType({{baseType}})]),
+ () => ListBuilder<{{baseType}}>(),
+{{/isArray}}
+{{#isMap}}
+ const FullType(BuiltMap, [FullType(String), FullType({{baseType}})]),
+ () => MapBuilder(),
+{{/isMap}}
+ ){{/serializers}}{{/apis}}{{/apiInfo}}{{#timeMachine}}
+ ..add(OffsetDateSerializer())
+ ..add(OffsetDateTimeSerializer()){{/timeMachine}}
+ ..add(Iso8601DateTimeSerializer()))
+ .build();
Serializers standardSerializers =
-(serializers.toBuilder()
-{{#timeMachine}}..add(OffsetDateSerializer())
-..add(OffsetDateTimeSerializer())
-{{/timeMachine}}..addPlugin(StandardJsonPlugin())).build();
+ (serializers.toBuilder()..addPlugin(StandardJsonPlugin())).build();
diff --git a/modules/openapi-generator/src/main/resources/elm/operation.mustache b/modules/openapi-generator/src/main/resources/elm/operation.mustache
index 0f4035a8188..de4d54d1798 100644
--- a/modules/openapi-generator/src/main/resources/elm/operation.mustache
+++ b/modules/openapi-generator/src/main/resources/elm/operation.mustache
@@ -33,7 +33,7 @@ import Uuid exposing (Uuid){{/includeUuid}}
{-| {{{notes}}}
-}
{{/notes}}
-{{operationId}} : {{#allParams}}{{^required}}Maybe {{/required}}{{#isArray}}List {{/isArray}}{{#datatypeWithEnum}}{{.}}{{/datatypeWithEnum}}{{^datatypeWithEnum}}{{#isModel}}Api.Data.{{/isModel}}{{dataType}}{{/datatypeWithEnum}} -> {{/allParams}}{{#authMethods}}{{#isBasicBearer}}String -> {{/isBasicBearer}}{{/authMethods}}Api.Request {{^responses}}(){{/responses}}{{#responses}}{{#is2xx}}{{^dataType}}(){{/dataType}}{{#isMap}}(Dict.Dict String {{/isMap}}{{#isArray}}(List {{/isArray}}{{^primitiveType}}{{^isUuid}}Api.Data.{{/isUuid}}{{/primitiveType}}{{#items}}{{#isModel}}Api.Data.{{/isModel}}{{/items}}{{dataType}}{{#isArray}}){{/isArray}}{{#isMap}}){{/isMap}}{{/is2xx}}{{/responses}}
+{{operationId}} : {{#allParams}}{{^required}}Maybe {{/required}}{{#isArray}}List {{/isArray}}{{#datatypeWithEnum}}{{.}}{{/datatypeWithEnum}}{{^datatypeWithEnum}}{{#isModel}}Api.Data.{{/isModel}}{{dataType}}{{/datatypeWithEnum}} -> {{/allParams}}{{#authMethods}}{{#isBasicBearer}}String -> {{/isBasicBearer}}{{/authMethods}}Api.Request {{^responses}}(){{/responses}}{{#responses}}{{#is2xx}}{{^dataType}}(){{/dataType}}{{#isMap}}(Dict.Dict String {{/isMap}}{{#isArray}}(List {{#items}}{{#isModel}}Api.Data.{{/isModel}}{{/items}}{{/isArray}}{{^isArray}}{{^primitiveType}}{{^isUuid}}Api.Data.{{/isUuid}}{{/primitiveType}}{{/isArray}}{{dataType}}{{#isArray}}){{/isArray}}{{#isMap}}){{/isMap}}{{/is2xx}}{{/responses}}
{{operationId}}{{#allParams}} {{>paramName}}{{/allParams}}{{#authMethods}}{{#isBasicBearer}} auth_token{{/isBasicBearer}}{{/authMethods}} =
Api.request
"{{httpMethod}}"
diff --git a/modules/openapi-generator/src/main/resources/elm/recordFieldValueDecoder.mustache b/modules/openapi-generator/src/main/resources/elm/recordFieldValueDecoder.mustache
index d182a19974e..d01b75352d4 100644
--- a/modules/openapi-generator/src/main/resources/elm/recordFieldValueDecoder.mustache
+++ b/modules/openapi-generator/src/main/resources/elm/recordFieldValueDecoder.mustache
@@ -1 +1 @@
-{{#isArray}}(Json.Decode.list {{/isArray}}{{#isMap}}(Json.Decode.dict {{/isMap}}{{#items}}{{>recordFieldValueDecoder}}{{/items}}{{^isContainer}}{{#isCircularReference}}(Json.Decode.lazy (\_ -> {{/isCircularReference}}{{>fieldDecoder}}{{#isCircularReference}})){{/isCircularReference}}{{/isContainer}}{{#isArray}}){{/isArray}}{{#isMap}}){{/isMap}}
\ No newline at end of file
+{{#isArray}}(Json.Decode.list {{/isArray}}{{#isMap}}(Json.Decode.dict {{/isMap}}{{#items}}{{>recordFieldValueDecoder}}{{/items}}{{^isArray}}{{#isCircularReference}}(Json.Decode.lazy (\_ -> {{/isCircularReference}}{{>fieldDecoder}}{{#isCircularReference}})){{/isCircularReference}}{{/isArray}}{{#isArray}}){{/isArray}}{{#isMap}}){{/isMap}}
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/kotlin-spring/apiDelegate.mustache b/modules/openapi-generator/src/main/resources/kotlin-spring/apiDelegate.mustache
index db054a92c30..d45f9f79f2e 100644
--- a/modules/openapi-generator/src/main/resources/kotlin-spring/apiDelegate.mustache
+++ b/modules/openapi-generator/src/main/resources/kotlin-spring/apiDelegate.mustache
@@ -32,7 +32,7 @@ interface {{classname}}Delegate {
/**
* @see {{classname}}#{{operationId}}
*/
- fun {{operationId}}({{#allParams}}{{paramName}}: {{^isFile}}{{^isBodyParam}}{{>optionalDataType}}{{/isBodyParam}}{{#isBodyParam}}{{{dataType}}}{{/isBodyParam}}{{/isFile}}{{#isFile}}Resource?{{/isFile}}{{^-last}},
+ fun {{operationId}}({{#allParams}}{{paramName}}: {{^isFile}}{{>optionalDataType}}{{/isFile}}{{#isFile}}Resource?{{/isFile}}{{^-last}},
{{/-last}}{{/allParams}}): {{#responseWrapper}}{{.}}<{{/responseWrapper}}ResponseEntity<{{>returnTypes}}>{{#responseWrapper}}>{{/responseWrapper}} {
{{>methodBody}}
}
diff --git a/modules/openapi-generator/src/main/resources/kotlin-spring/dataClassOptVar.mustache b/modules/openapi-generator/src/main/resources/kotlin-spring/dataClassOptVar.mustache
index b44f43adf85..ec3c8fd6983 100644
--- a/modules/openapi-generator/src/main/resources/kotlin-spring/dataClassOptVar.mustache
+++ b/modules/openapi-generator/src/main/resources/kotlin-spring/dataClassOptVar.mustache
@@ -1,5 +1,4 @@
-{{#useBeanValidation}}{{#required}}{{^isReadOnly}}
- @get:NotNull{{/isReadOnly}}{{/required}}{{>beanValidation}}{{>beanValidationModel}}{{/useBeanValidation}}{{#swaggerAnnotations}}
- @ApiModelProperty({{#example}}example = "{{{example}}}", {{/example}}{{#required}}required = {{required}}, {{/required}}{{#isReadOnly}}readOnly = {{{isReadOnly}}}, {{/isReadOnly}}value = "{{{description}}}"){{/swaggerAnnotations}}{{#deprecated}}
+{{#useBeanValidation}}{{>beanValidation}}{{>beanValidationModel}}{{/useBeanValidation}}{{#swaggerAnnotations}}
+ @ApiModelProperty({{#example}}example = "{{{example}}}", {{/example}}{{#isReadOnly}}readOnly = {{{isReadOnly}}}, {{/isReadOnly}}value = "{{{description}}}"){{/swaggerAnnotations}}{{#deprecated}}
@Deprecated(message = ""){{/deprecated}}
@field:JsonProperty("{{{baseName}}}"){{#isInherited}} override{{/isInherited}} {{>modelMutable}} {{{name}}}: {{#isEnum}}{{#isArray}}{{baseType}}<{{/isArray}}{{classname}}.{{nameInCamelCase}}{{#isArray}}>{{/isArray}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}? = {{#defaultvalue}}{{defaultvalue}}{{/defaultvalue}}{{^defaultvalue}}null{{/defaultvalue}}
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/kotlin-spring/dataClassReqVar.mustache b/modules/openapi-generator/src/main/resources/kotlin-spring/dataClassReqVar.mustache
index b67cb59905b..963baa8f9af 100644
--- a/modules/openapi-generator/src/main/resources/kotlin-spring/dataClassReqVar.mustache
+++ b/modules/openapi-generator/src/main/resources/kotlin-spring/dataClassReqVar.mustache
@@ -1,4 +1,3 @@
-{{#useBeanValidation}}{{#required}}{{^isReadOnly}}
- @get:NotNull {{/isReadOnly}}{{/required}}{{>beanValidation}}{{>beanValidationModel}}{{/useBeanValidation}}{{#swaggerAnnotations}}
- @ApiModelProperty({{#example}}example = "{{{example}}}", {{/example}}{{#required}}required = {{required}}, {{/required}}{{#isReadOnly}}readOnly = {{{isReadOnly}}}, {{/isReadOnly}}value = "{{{description}}}"){{/swaggerAnnotations}}
- @field:JsonProperty("{{{baseName}}}"){{#isInherited}} override{{/isInherited}} {{>modelMutable}} {{{name}}}: {{#isEnum}}{{#isArray}}{{baseType}}<{{/isArray}}{{classname}}.{{nameInCamelCase}}{{#isArray}}>{{/isArray}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#isReadOnly}}? = {{#defaultvalue}}{{defaultvalue}}{{/defaultvalue}}{{^defaultvalue}}null{{/defaultvalue}}{{/isReadOnly}}
\ No newline at end of file
+{{#useBeanValidation}}{{>beanValidation}}{{>beanValidationModel}}{{/useBeanValidation}}{{#swaggerAnnotations}}
+ @ApiModelProperty({{#example}}example = "{{{example}}}", {{/example}}required = true, {{#isReadOnly}}readOnly = {{{isReadOnly}}}, {{/isReadOnly}}value = "{{{description}}}"){{/swaggerAnnotations}}
+ @field:JsonProperty("{{{baseName}}}", required = true){{#isInherited}} override{{/isInherited}} {{>modelMutable}} {{{name}}}: {{#isEnum}}{{#isArray}}{{baseType}}<{{/isArray}}{{classname}}.{{nameInCamelCase}}{{#isArray}}>{{/isArray}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{#isReadOnly}}? = {{#defaultvalue}}{{defaultvalue}}{{/defaultvalue}}{{^defaultvalue}}null{{/defaultvalue}}{{/isReadOnly}}
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/ktorm-schema/README.mustache b/modules/openapi-generator/src/main/resources/ktorm-schema/README.mustache
new file mode 100644
index 00000000000..ecf0a6edb02
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/ktorm-schema/README.mustache
@@ -0,0 +1,48 @@
+# {{packageName}} - Kotlin database library for {{appName}}
+
+## Requires
+
+{{#jvm}}
+* Kotlin 1.3.61
+* Gradle 4.9
+{{/jvm}}
+{{#multiplatform}}
+* Kotlin 1.3.50
+{{/multiplatform}}
+
+## Build
+
+{{#jvm}}
+First, create the gradle wrapper script:
+
+```
+gradle wrapper
+```
+
+Then, run:
+
+{{/jvm}}
+```
+./gradlew check assemble
+```
+
+This runs all tests and packages the library.
+
+## Features/Implementation Notes
+
+* Some Kotlin and Java types are fully qualified to avoid conflicts with types defined in OpenAPI definitions.
+* Supports Mapper using API model classes.
+* Supports SQLite types.
+
+{{#generateModelDocs}}
+
+## Documentation for Models
+
+{{#modelPackage}}
+{{#models}}{{#model}} - [{{{modelPackage}}}.{{{classname}}}]({{modelDocPath}}{{{classname}}}.md)
+{{/model}}{{/models}}
+{{/modelPackage}}
+{{^modelPackage}}
+No model defined in this package
+{{/modelPackage}}
+{{/generateModelDocs}}
diff --git a/modules/openapi-generator/src/main/resources/ktorm-schema/build.gradle.mustache b/modules/openapi-generator/src/main/resources/ktorm-schema/build.gradle.mustache
new file mode 100644
index 00000000000..55621198b63
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/ktorm-schema/build.gradle.mustache
@@ -0,0 +1,35 @@
+group '{{groupId}}'
+version '{{artifactVersion}}'
+
+wrapper {
+ gradleVersion = '4.9'
+ distributionUrl = "https://services.gradle.org/distributions/gradle-$gradleVersion-all.zip"
+}
+
+buildscript {
+ ext.kotlin_version = '1.3.72'
+ ext.ktorm_version = '3.2.0'
+
+ repositories {
+ maven { url "https://repo1.maven.org/maven2" }
+ }
+ dependencies {
+ classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
+ }
+}
+
+apply plugin: 'kotlin'
+
+repositories {
+ maven { url "https://repo1.maven.org/maven2" }
+}
+
+test {
+ useJUnitPlatform()
+}
+
+dependencies {
+ implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
+ implementation "org.ktorm:ktorm-core:$ktorm_version"
+ testImplementation "io.kotlintest:kotlintest-runner-junit5:3.1.0"
+}
diff --git a/modules/openapi-generator/src/main/resources/ktorm-schema/data_class.mustache b/modules/openapi-generator/src/main/resources/ktorm-schema/data_class.mustache
new file mode 100644
index 00000000000..e15a7b7a90c
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/ktorm-schema/data_class.mustache
@@ -0,0 +1,102 @@
+{{#vendorExtensions}}{{#x-ktorm-schema}}{{^discriminator}}
+/**
+ * {{{description}}}
+{{#allVars}}
+ * @param {{{name}}} {{{description}}}
+{{/allVars}}
+ */
+{{#isDeprecated}}
+@Deprecated(message = "This schema is deprecated.")
+{{/isDeprecated}}
+{{#nonPublicApi}}internal {{/nonPublicApi}}object {{{classname}}}s : BaseTable<{{{classname}}}>("{{#tableDefinition}}{{{tblName}}}{{/tableDefinition}}") {
+{{#allVars}}
+{{^isArray}}
+{{#isEnum}}
+{{#required}}{{>data_class_enum_req}}{{/required}}{{^required}}{{>data_class_enum_opt}}{{/required}}
+{{/isEnum}}
+{{^isEnum}}
+{{#required}}{{>data_class_field_req}}{{/required}}{{^required}}{{>data_class_field_opt}}{{/required}}
+{{/isEnum}}
+{{/isArray}}
+{{/allVars}}
+
+ /**
+ * Create an entity of type {{{classname}}} from the model
+ */
+ override fun doCreateEntity(row: QueryRowSet, withReferences: Boolean) = {{{classname}}}(
+{{#allVars}}
+{{#vendorExtensions}}
+{{#x-ktorm-schema}}
+{{^isArray}}
+{{#relation}}
+{{#required}}{{>data_class_bind_ref_req}}{{/required}}{{^required}}{{>data_class_bind_ref_opt}}{{/required}}{{^-last}},{{/-last}}
+{{/relation}}
+{{^relation}}
+{{#required}}{{>data_class_bind_field_req}}{{/required}}{{^required}}{{>data_class_bind_field_opt}}{{/required}}{{^-last}},{{/-last}}
+{{/relation}}
+{{/isArray}}
+{{#isArray}}
+{{#required}}{{>data_class_bind_list_req}}{{/required}}{{^required}}{{>data_class_bind_list_opt}}{{/required}}{{^-last}},{{/-last}}
+{{/isArray}}
+{{/x-ktorm-schema}}
+{{/vendorExtensions}}
+{{/allVars}}
+ )
+
+ /**
+ * Assign all the columns from the entity of type {{{classname}}} to the DML expression.
+ *
+ * Usage:
+ *
+ * ```kotlin
+ * let entity = {{{classname}}}()
+ * database.update({{{classname}}}s, {
+ * assignFrom(entity)
+ * })
+ * ```
+ * @return the builder with the columns for the update or insert.
+ */
+ fun AssignmentsBuilder.assignFrom(entity: {{{classname}}}) {
+ this.apply {
+{{#allVars}}
+{{^isArray}}
+{{>data_class_set}}
+{{/isArray}}
+{{/allVars}}
+ }
+ }
+
+}
+
+{{#allVars}}
+{{#isArray}}
+{{#vendorExtensions}}
+{{#x-ktorm-schema}}
+{{#isDeprecated}}
+@Deprecated(message = "This schema is deprecated.")
+{{/isDeprecated}}
+{{#relationDefinition}}
+
+{{#nonPublicApi}}internal {{/nonPublicApi}}object {{{relName}}} : BaseTable>("{{{relTblName}}}") {
+ val {{{pkName}}} = {{{pkColType}}}("{{{pkColName}}}")
+ val {{{fkName}}} = {{{fkColType}}}("{{{fkColName}}}")
+
+ override fun doCreateEntity(row: QueryRowSet, withReferences: Boolean): Pair<{{{pkColKotlinType}}}, {{{fkColKotlinType}}}> =
+ Pair(row[{{{pkName}}}] ?: {{#pkIsString}}""{{/pkIsString}}{{#pkIsNumeric}}0{{/pkIsNumeric}}, row[{{{fkName}}}] ?: {{#fkIsString}}""{{/fkIsString}}{{#fkIsNumeric}}0{{/fkIsNumeric}})
+
+ fun AssignmentsBuilder.assignFrom(entity: Pair<{{{pkColKotlinType}}}, {{{fkColKotlinType}}}>) {
+ this.apply {
+ set({{{relName}}}.{{{pkName}}}, entity.first)
+ set({{{relName}}}.{{{fkName}}}, entity.second)
+ }
+ }
+
+}
+{{/relationDefinition}}
+{{/x-ktorm-schema}}
+{{/vendorExtensions}}
+{{/isArray}}
+{{/allVars}}
+{{/discriminator}}
+{{/x-ktorm-schema}}
+{{/vendorExtensions}}
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/ktorm-schema/data_class_bind_field_opt.mustache b/modules/openapi-generator/src/main/resources/ktorm-schema/data_class_bind_field_opt.mustache
new file mode 100644
index 00000000000..9cb5892ecb7
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/ktorm-schema/data_class_bind_field_opt.mustache
@@ -0,0 +1 @@
+{{#vendorExtensions}}{{#x-ktorm-schema}}{{#columnDefinition}} {{{name}}} = row[{{{name}}}] {{#defaultvalue}}?: {{{defaultvalue}}}{{/defaultvalue}} /* {{{colKotlinType}}}? */{{/columnDefinition}}{{/x-ktorm-schema}}{{/vendorExtensions}}{{#description}} /* {{{description}}} */{{/description}}
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/ktorm-schema/data_class_bind_field_req.mustache b/modules/openapi-generator/src/main/resources/ktorm-schema/data_class_bind_field_req.mustache
new file mode 100644
index 00000000000..b3af9c69906
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/ktorm-schema/data_class_bind_field_req.mustache
@@ -0,0 +1 @@
+{{#vendorExtensions}}{{#x-ktorm-schema}}{{#columnDefinition}} {{{name}}} = row[{{{name}}}]{{#isEnum}} ?: {{{classname}}}.{{{nameInCamelCase}}}.valueOf({{#isString}}""{{/isString}}{{#isNumeric}}0{{/isNumeric}}){{/isEnum}}{{^isEnum}}{{#isString}} ?: ""{{/isString}}{{#isNumeric}} ?: 0{{/isNumeric}}{{#isBoolean}} ?: false{{/isBoolean}}{{/isEnum}} /* {{{colKotlinType}}} */{{/columnDefinition}}{{/x-ktorm-schema}}{{/vendorExtensions}}{{#description}} /* {{{description}}} */{{/description}}
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/ktorm-schema/data_class_bind_list_opt.mustache b/modules/openapi-generator/src/main/resources/ktorm-schema/data_class_bind_list_opt.mustache
new file mode 100644
index 00000000000..7820b16e3bf
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/ktorm-schema/data_class_bind_list_opt.mustache
@@ -0,0 +1 @@
+{{#vendorExtensions}}{{#x-ktorm-schema}}{{#columnDefinition}} {{{name}}} = emptyList() /* {{{colKotlinType}}}? */{{/columnDefinition}}{{/x-ktorm-schema}}{{/vendorExtensions}}{{#description}} /* {{{description}}} */{{/description}}
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/ktorm-schema/data_class_bind_list_req.mustache b/modules/openapi-generator/src/main/resources/ktorm-schema/data_class_bind_list_req.mustache
new file mode 100644
index 00000000000..bc668b27fd4
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/ktorm-schema/data_class_bind_list_req.mustache
@@ -0,0 +1 @@
+{{#vendorExtensions}}{{#x-ktorm-schema}}{{#columnDefinition}} {{{name}}} = emptyList() /* {{{colKotlinType}}} */{{/columnDefinition}}{{/x-ktorm-schema}}{{/vendorExtensions}}{{#description}} /* {{{description}}} */{{/description}}
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/ktorm-schema/data_class_bind_ref_opt.mustache b/modules/openapi-generator/src/main/resources/ktorm-schema/data_class_bind_ref_opt.mustache
new file mode 100644
index 00000000000..d422a1a812c
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/ktorm-schema/data_class_bind_ref_opt.mustache
@@ -0,0 +1 @@
+{{#vendorExtensions}}{{#x-ktorm-schema}}{{#columnDefinition}} {{{name}}} = {{{colKotlinType}}}s.createEntity(row, withReferences) /* {{{colKotlinType}}}? */{{/columnDefinition}}{{/x-ktorm-schema}}{{/vendorExtensions}}{{#description}} /* {{{description}}} */{{/description}}
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/ktorm-schema/data_class_bind_ref_req.mustache b/modules/openapi-generator/src/main/resources/ktorm-schema/data_class_bind_ref_req.mustache
new file mode 100644
index 00000000000..0974295d86f
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/ktorm-schema/data_class_bind_ref_req.mustache
@@ -0,0 +1 @@
+{{#vendorExtensions}}{{#x-ktorm-schema}}{{#columnDefinition}} {{{name}}} = {{{colKotlinType}}}s.createEntity(row, withReferences) /* {{{colKotlinType}}} */{{/columnDefinition}}{{/x-ktorm-schema}}{{/vendorExtensions}}{{#description}} /* {{{description}}} */{{/description}}
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/ktorm-schema/data_class_doc.mustache b/modules/openapi-generator/src/main/resources/ktorm-schema/data_class_doc.mustache
new file mode 100644
index 00000000000..0046d259079
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/ktorm-schema/data_class_doc.mustache
@@ -0,0 +1,20 @@
+# Table {{#vendorExtensions}}{{#x-ktorm-schema}}{{#tableDefinition}}`{{tblName}}`{{/tableDefinition}}{{/x-ktorm-schema}}{{/vendorExtensions}}
+(mapped from: {{classname}})
+
+## Properties
+Name | Mapping | SQL Type | Default | Type | Description | Notes
+---- | ------- | -------- | ------- | ---- | ----------- | -----
+{{#allVars}}**{{name}}** | {{^isArray}}{{#vendorExtensions}}{{#x-ktorm-schema}}{{#columnDefinition}}{{colName}} | {{colType}}{{#colTypeArgs}}{{#-first}}({{/-first}}{{#isString}}'{{/isString}}{{argVal}}{{#isString}}'{{/isString}}{{^-last}}, {{/-last}}{{#-last}}){{/-last}}{{/colTypeArgs}}{{#colUnsigned}} UNSIGNED{{/colUnsigned}}{{#colNotNull}} NOT NULL{{/colNotNull}}{{#colPrimaryKey}} PRIMARY KEY{{#isNumeric}} AUTOINCREMENT{{/isNumeric}}{{/colPrimaryKey}} | {{#colDefault}}{{#isString}}'{{defaultValue}}'{{/isString}}{{^isString}}{{defaultValue}}{{/isString}}{{/colDefault}}{{/columnDefinition}}{{/x-ktorm-schema}}{{/vendorExtensions}}{{/isArray}}{{#isArray}}`One-To-Many` | `----` | `----` {{/isArray}} | {{#isEnum}}[**{{baseName}}**](#{{datatypeWithEnum}}){{/isEnum}}{{^isEnum}}{{#isPrimitiveType}}**{{dataType}}**{{/isPrimitiveType}}{{^isPrimitiveType}}[**{{dataType}}**]({{complexType}}.md){{/isPrimitiveType}}{{/isEnum}} | {{description}} | {{^required}} [optional]{{/required}}{{#isReadOnly}} [readonly]{{/isReadOnly}}{{#vendorExtensions}}{{#x-ktorm-schema}}{{^isArray}}{{#relation}} [foreignkey]{{/relation}}{{/isArray}}{{/x-ktorm-schema}}{{/vendorExtensions}}
+{{/allVars}}
+{{#allVars}}{{#isArray}}{{#vendorExtensions}}{{#x-ktorm-schema}}{{#relationDefinition}}
+
+# **Table `{{{relTblName}}}`**
+(mapped from: {{relName}})
+
+## Properties
+Name | Mapping | SQL Type | Default | Type | Description | Notes
+---- | ------- | -------- | ------- | ---- | ----------- | -----
+{{{pkName}}} | {{{pkColName}}} | {{{pkColType}}} | | {{{pkColKotlinType}}} | Primary Key | *one*
+{{{fkName}}} | {{{fkColName}}} | {{{fkColType}}} | | {{{fkColKotlinType}}} | Foreign Key | *many*
+{{/relationDefinition}}{{/x-ktorm-schema}}{{/vendorExtensions}}{{/isArray}}
+{{/allVars}}
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/ktorm-schema/data_class_enum_opt.mustache b/modules/openapi-generator/src/main/resources/ktorm-schema/data_class_enum_opt.mustache
new file mode 100644
index 00000000000..03d7f8d40db
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/ktorm-schema/data_class_enum_opt.mustache
@@ -0,0 +1,2 @@
+{{#deprecated}} @Deprecated(message = "This property is deprecated.")
+{{/deprecated}}{{#vendorExtensions}}{{#x-ktorm-schema}}{{#columnDefinition}} val {{{name}}} = {{{colType}}}("{{{colName}}}").transform({ {{{classname}}}.{{{nameInCamelCase}}}.valueOf(it ?: {{#allowableValues}}{{#enumVars}}{{#-first}}{{{value}}}{{/-first}}{{/enumVars}}{{/allowableValues}}) }, { it.value }) /* null */{{/columnDefinition}}{{/x-ktorm-schema}}{{/vendorExtensions}}{{#description}} /* {{{description}}} */{{/description}}
diff --git a/modules/openapi-generator/src/main/resources/ktorm-schema/data_class_enum_req.mustache b/modules/openapi-generator/src/main/resources/ktorm-schema/data_class_enum_req.mustache
new file mode 100644
index 00000000000..33a3004fbc7
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/ktorm-schema/data_class_enum_req.mustache
@@ -0,0 +1,2 @@
+{{#deprecated}} @Deprecated(message = "This property is deprecated.")
+{{/deprecated}}{{#vendorExtensions}}{{#x-ktorm-schema}}{{#columnDefinition}} val {{{name}}} = {{{colType}}}("{{{colName}}}").transform({ {{{classname}}}.{{{nameInCamelCase}}}.valueOf(it) }, { it.value }){{/columnDefinition}}{{/x-ktorm-schema}}{{/vendorExtensions}}{{#description}} /* {{{description}}} */{{/description}}
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/ktorm-schema/data_class_field_opt.mustache b/modules/openapi-generator/src/main/resources/ktorm-schema/data_class_field_opt.mustache
new file mode 100644
index 00000000000..134330d4539
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/ktorm-schema/data_class_field_opt.mustache
@@ -0,0 +1,2 @@
+{{#deprecated}} @Deprecated(message = "This property is deprecated.")
+{{/deprecated}}{{#vendorExtensions}}{{#x-ktorm-schema}}{{#columnDefinition}} val {{{name}}} = {{{colType}}}("{{{colName}}}") /* null */{{/columnDefinition}}{{/x-ktorm-schema}}{{/vendorExtensions}}{{#description}} /* {{{description}}} */{{/description}}
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/ktorm-schema/data_class_field_req.mustache b/modules/openapi-generator/src/main/resources/ktorm-schema/data_class_field_req.mustache
new file mode 100644
index 00000000000..09a42c1a1e5
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/ktorm-schema/data_class_field_req.mustache
@@ -0,0 +1,2 @@
+{{#deprecated}} @Deprecated(message = "This property is deprecated.")
+{{/deprecated}}{{#vendorExtensions}}{{#x-ktorm-schema}}{{#columnDefinition}} val {{{name}}} = {{{colType}}}("{{{colName}}}"){{/columnDefinition}}{{/x-ktorm-schema}}{{/vendorExtensions}}{{#description}} /* {{{description}}} */{{/description}}
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/ktorm-schema/data_class_set.mustache b/modules/openapi-generator/src/main/resources/ktorm-schema/data_class_set.mustache
new file mode 100644
index 00000000000..39ce7c54fae
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/ktorm-schema/data_class_set.mustache
@@ -0,0 +1 @@
+ set({{{classname}}}s.{{{name}}}, entity.{{{name}}})
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/ktorm-schema/enum_class.mustache b/modules/openapi-generator/src/main/resources/ktorm-schema/enum_class.mustache
new file mode 100644
index 00000000000..f1a8b67eeff
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/ktorm-schema/enum_class.mustache
@@ -0,0 +1,61 @@
+{{^multiplatform}}
+{{#gson}}
+import com.google.gson.annotations.SerializedName
+{{/gson}}
+{{#moshi}}
+import com.squareup.moshi.Json
+{{/moshi}}
+{{#jackson}}
+import com.fasterxml.jackson.annotation.JsonProperty
+{{/jackson}}
+{{/multiplatform}}
+{{#multiplatform}}
+import kotlinx.serialization.*
+import kotlinx.serialization.internal.CommonEnumSerializer
+{{/multiplatform}}
+
+/**
+* {{{description}}}
+* Values: {{#allowableValues}}{{#enumVars}}{{&name}}{{^-last}},{{/-last}}{{/enumVars}}{{/allowableValues}}
+*/
+{{#multiplatform}}@Serializable(with = {{classname}}.Serializer::class){{/multiplatform}}
+{{#nonPublicApi}}internal {{/nonPublicApi}}enum class {{classname}}(val value: {{{dataType}}}){
+
+{{#allowableValues}}{{#enumVars}}
+ {{^multiplatform}}
+ {{#moshi}}
+ @Json(name = {{^isString}}"{{/isString}}{{{value}}}{{^isString}}"{{/isString}})
+ {{/moshi}}
+ {{#gson}}
+ @SerializedName(value = {{^isString}}"{{/isString}}{{{value}}}{{^isString}}"{{/isString}})
+ {{/gson}}
+ {{#jackson}}
+ @JsonProperty(value = {{^isString}}"{{/isString}}{{{value}}}{{^isString}}"{{/isString}})
+ {{/jackson}}
+ {{/multiplatform}}
+ {{#isArray}}
+ {{#isList}}
+ {{&name}}(listOf({{{value}}})){{^-last}},{{/-last}}{{#-last}};{{/-last}}
+ {{/isList}}
+ {{^isList}}
+ {{&name}}(arrayOf({{{value}}})){{^-last}},{{/-last}}{{#-last}};{{/-last}}
+ {{/isList}}
+ {{/isArray}}
+ {{^isArray}}
+ {{&name}}({{{value}}}){{^-last}},{{/-last}}{{#-last}};{{/-last}}
+ {{/isArray}}
+
+{{/enumVars}}{{/allowableValues}}
+
+ /**
+ This override toString avoids using the enum var name and uses the actual api value instead.
+ In cases the var name and value are different, the client would send incorrect enums to the server.
+ **/
+ override fun toString(): String {
+ return value{{^isString}}.toString(){{/isString}}
+ }
+
+{{#multiplatform}}
+ {{#nonPublicApi}}internal {{/nonPublicApi}}object Serializer : CommonEnumSerializer<{{classname}}>("{{classname}}", values(), values().map { it.value.toString() }.toTypedArray())
+{{/multiplatform}}
+}
diff --git a/modules/openapi-generator/src/main/resources/ktorm-schema/enum_doc.mustache b/modules/openapi-generator/src/main/resources/ktorm-schema/enum_doc.mustache
new file mode 100644
index 00000000000..fcb3d7e61aa
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/ktorm-schema/enum_doc.mustache
@@ -0,0 +1,7 @@
+# {{classname}}
+
+## Enum
+
+{{#allowableValues}}{{#enumVars}}
+ * `{{name}}` (value: `{{{value}}}`)
+{{/enumVars}}{{/allowableValues}}
diff --git a/modules/openapi-generator/src/main/resources/ktorm-schema/ktorm_schema.mustache b/modules/openapi-generator/src/main/resources/ktorm-schema/ktorm_schema.mustache
new file mode 100644
index 00000000000..56c627d2d60
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/ktorm-schema/ktorm_schema.mustache
@@ -0,0 +1,179 @@
+{{#defaultDatabaseName}}
+--
+-- Database: `{{{defaultDatabaseName}}}`
+--
+CREATE DATABASE IF NOT EXISTS `{{{defaultDatabaseName}}}` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
+{{/defaultDatabaseName}}
+
+{{#models}}{{#model}}{{#hasVars}}{{^isArrayModel}}
+-- --------------------------------------------------------------------------
+-- Table structure{{#vendorExtensions}}{{#x-ktorm-schema}}{{#tableDefinition}} for table `{{tblName}}`{{/tableDefinition}}{{/x-ktorm-schema}}{{/vendorExtensions}} generated from model '{{classVarName}}'
+{{#description}}
+-- {{description}}
+{{/description}}
+--
+
+{{#vendorExtensions}}
+{{#x-ktorm-schema}}
+{{#tableDefinition}}
+CREATE TABLE IF NOT EXISTS {{#defaultDatabaseName}}`{{{defaultDatabaseName}}}`.{{/defaultDatabaseName}}`{{tblName}}` (
+{{/tableDefinition}}
+{{/x-ktorm-schema}}
+{{/vendorExtensions}}
+{{#allVars}}
+{{#vendorExtensions}}
+{{#x-ktorm-schema}}
+{{#columnDefinition}}
+{{^isArray}}
+ `{{colName}}` {{colType}}{{#colTypeArgs}}{{#-first}}({{/-first}}{{#isString}}'{{/isString}}{{argVal}}{{#isString}}'{{/isString}}{{^-last}}, {{/-last}}{{#-last}}){{/-last}}{{/colTypeArgs}}{{#colUnsigned}} UNSIGNED{{/colUnsigned}}{{#colNotNull}} NOT NULL{{/colNotNull}}{{#colPrimaryKey}} PRIMARY KEY{{#isNumeric}} AUTOINCREMENT{{/isNumeric}}{{/colPrimaryKey}}{{#colDefault}} DEFAULT {{#isString}}'{{defaultValue}}'{{/isString}}{{^isString}}{{defaultValue}}{{/isString}}{{/colDefault}}{{#colComment}} /*{{colComment}}*/{{/colComment}}{{^-last}},{{/-last}}
+{{/isArray}}
+{{/columnDefinition}}
+{{/x-ktorm-schema}}
+{{/vendorExtensions}}
+{{/allVars}}
+{{#vendorExtensions}}
+{{#x-ktorm-schema}}
+{{#tableDefinition}}
+); {{#tblComment}} /*{{tblComment}}*/{{/tblComment}}
+
+{{/tableDefinition}}
+{{/x-ktorm-schema}}
+{{/vendorExtensions}}
+{{#allVars}}
+{{#isArray}}
+{{#vendorExtensions}}
+{{#x-ktorm-schema}}
+{{#relationDefinition}}
+-- --------------------------------------------------------------------------
+-- Table structure for table `{{relTblName}}` generated from model '{{relName}}'
+{{#colComment}}
+-- {{colComment}}
+{{/colComment}}
+
+CREATE TABLE IF NOT EXISTS {{#defaultDatabaseName}}`{{{defaultDatabaseName}}}`.{{/defaultDatabaseName}}`{{relTblName}}` (
+ `{{{pkColName}}}` {{{pkColType}}} NOT NULL
+ `{{{fkColName}}}` {{{fkColType}}} NOT NULL
+);
+
+{{/relationDefinition}}
+{{/x-ktorm-schema}}
+{{/vendorExtensions}}
+{{/isArray}}
+{{/allVars}}
+{{/isArrayModel}}{{/hasVars}}{{/model}}{{/models}}
+{{#hasOAuthMethods}}
+--
+-- OAuth2 framework tables
+-- Thanks to https://github.com/dsquier/oauth2-server-php-mysql repo
+--
+
+--
+-- Table structure for table `oauth_clients`
+--
+CREATE TABLE IF NOT EXISTS `oauth_clients` (
+ `client_id` VARCHAR(80) NOT NULL,
+ `client_secret` VARCHAR(80) DEFAULT NULL,
+ `redirect_uri` VARCHAR(2000) DEFAULT NULL,
+ `grant_types` VARCHAR(80) DEFAULT NULL,
+ `scope` VARCHAR(4000) DEFAULT NULL,
+ `user_id` VARCHAR(80) DEFAULT NULL,
+ PRIMARY KEY (`client_id`)
+);
+
+--
+-- Table structure for table `oauth_access_tokens`
+--
+CREATE TABLE IF NOT EXISTS `oauth_access_tokens` (
+ `access_token` VARCHAR(40) NOT NULL,
+ `client_id` VARCHAR(80) DEFAULT NULL,
+ `user_id` VARCHAR(80) DEFAULT NULL,
+ `expires` TIMESTAMP NOT NULL,
+ `scope` VARCHAR(4000) DEFAULT NULL,
+ PRIMARY KEY (`access_token`)
+);
+
+--
+-- Table structure for table `oauth_authorization_codes`
+--
+CREATE TABLE IF NOT EXISTS `oauth_authorization_codes` (
+ `authorization_code` VARCHAR(40) NOT NULL,
+ `client_id` VARCHAR(80) DEFAULT NULL,
+ `user_id` VARCHAR(80) DEFAULT NULL,
+ `redirect_uri` VARCHAR(2000) NOT NULL,
+ `expires` TIMESTAMP NOT NULL,
+ `scope` VARCHAR(4000) DEFAULT NULL,
+ `id_token` VARCHAR(1000) DEFAULT NULL,
+ PRIMARY KEY (`authorization_code`)
+);
+
+--
+-- Table structure for table `oauth_refresh_tokens`
+--
+CREATE TABLE IF NOT EXISTS `oauth_refresh_tokens` (
+ `refresh_token` VARCHAR(40) NOT NULL,
+ `client_id` VARCHAR(80) DEFAULT NULL,
+ `user_id` VARCHAR(80) DEFAULT NULL,
+ `expires` TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
+ `scope` VARCHAR(4000) DEFAULT NULL,
+ PRIMARY KEY (`refresh_token`)
+);
+
+--
+-- Table structure for table `oauth_users`
+--
+CREATE TABLE IF NOT EXISTS `oauth_users` (
+ `username` VARCHAR(80) DEFAULT NULL,
+ `password` VARCHAR(255) DEFAULT NULL,
+ `first_name` VARCHAR(80) DEFAULT NULL,
+ `last_name` VARCHAR(80) DEFAULT NULL,
+ `email` VARCHAR(2000) DEFAULT NULL,
+ `email_verified` TINYINT(1) DEFAULT NULL,
+ `scope` VARCHAR(4000) DEFAULT NULL
+);
+
+--
+-- Table structure for table `oauth_scopes`
+--
+CREATE TABLE IF NOT EXISTS `oauth_scopes` (
+ `scope` VARCHAR(80) NOT NULL,
+ `is_default` TINYINT(1) DEFAULT NULL,
+ PRIMARY KEY (`scope`)
+);
+
+--
+-- Table structure for table `oauth_jwt`
+--
+CREATE TABLE IF NOT EXISTS `oauth_jwt` (
+ `client_id` VARCHAR(80) NOT NULL,
+ `subject` VARCHAR(80) DEFAULT NULL,
+ `public_key` VARCHAR(2000) NOT NULL
+);
+
+--
+-- Table structure for table `oauth_jti`
+--
+CREATE TABLE IF NOT EXISTS `oauth_jti` (
+ `issuer` VARCHAR(80) NOT NULL,
+ `subject` VARCHAR(80) DEFAULT NULL,
+ `audiance` VARCHAR(80) DEFAULT NULL,
+ `expires` TIMESTAMP NOT NULL,
+ `jti` VARCHAR(2000) NOT NULL
+);
+
+--
+-- Table structure for table `oauth_public_keys`
+--
+CREATE TABLE IF NOT EXISTS `oauth_public_keys` (
+ `client_id` VARCHAR(80) DEFAULT NULL,
+ `public_key` VARCHAR(2000) DEFAULT NULL,
+ `private_key` VARCHAR(2000) DEFAULT NULL,
+ `encryption_algorithm` VARCHAR(100) DEFAULT 'RS256'
+);
+{{/hasOAuthMethods}}
+
+--
+-- Table structure for table `_db_version`
+--
+CREATE TABLE IF NOT EXISTS `_db_version` (
+ `version` LONG DEFAULT 1
+);
diff --git a/modules/openapi-generator/src/main/resources/ktorm-schema/licenseInfo.mustache b/modules/openapi-generator/src/main/resources/ktorm-schema/licenseInfo.mustache
new file mode 100644
index 00000000000..3a547de74bb
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/ktorm-schema/licenseInfo.mustache
@@ -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.
+*/
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/ktorm-schema/model.mustache b/modules/openapi-generator/src/main/resources/ktorm-schema/model.mustache
new file mode 100644
index 00000000000..20216d0dbd8
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/ktorm-schema/model.mustache
@@ -0,0 +1,13 @@
+{{>licenseInfo}}
+package {{modelPackage}}
+
+import org.ktorm.dsl.*
+import org.ktorm.schema.*
+import org.ktorm.database.Database
+import {{{importModelPackageName}}}.*
+
+{{#models}}
+{{#model}}
+{{#isEnum}}{{>enum_class}}{{/isEnum}}{{^isEnum}}{{#isAlias}}typealias {{classname}} = {{{dataType}}}{{/isAlias}}{{^isAlias}}{{>data_class}}{{/isAlias}}{{/isEnum}}
+{{/model}}
+{{/models}}
diff --git a/modules/openapi-generator/src/main/resources/ktorm-schema/model_doc.mustache b/modules/openapi-generator/src/main/resources/ktorm-schema/model_doc.mustache
new file mode 100644
index 00000000000..198ea33c57c
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/ktorm-schema/model_doc.mustache
@@ -0,0 +1,3 @@
+{{#models}}{{#model}}
+{{#isEnum}}{{>enum_doc}}{{/isEnum}}{{^isEnum}}{{>data_class_doc}}{{/isEnum}}
+{{/model}}{{/models}}
diff --git a/modules/openapi-generator/src/main/resources/ktorm-schema/settings.gradle.mustache b/modules/openapi-generator/src/main/resources/ktorm-schema/settings.gradle.mustache
new file mode 100644
index 00000000000..0fab92c481a
--- /dev/null
+++ b/modules/openapi-generator/src/main/resources/ktorm-schema/settings.gradle.mustache
@@ -0,0 +1,2 @@
+{{#multiplatform}}enableFeaturePreview('GRADLE_METADATA')
+{{/multiplatform}}rootProject.name = '{{artifactId}}'
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/powershell/model_simple.mustache b/modules/openapi-generator/src/main/resources/powershell/model_simple.mustache
index ef1f3e350df..1ac78f767bd 100644
--- a/modules/openapi-generator/src/main/resources/powershell/model_simple.mustache
+++ b/modules/openapi-generator/src/main/resources/powershell/model_simple.mustache
@@ -73,7 +73,7 @@ function Initialize-{{{apiNamePrefix}}}{{{classname}}} {
{{#vars}}
{{^isNullable}}
{{#required}}
- if (!${{{name}}}) {
+ if (${{{name}}} -eq $null) {
throw "invalid value for '{{{name}}}', '{{{name}}}' cannot be null."
}
@@ -246,12 +246,12 @@ function ConvertFrom-{{{apiNamePrefix}}}JsonTo{{{classname}}} {
{{#requiredVars}}
{{#-first}}
If ([string]::IsNullOrEmpty($Json) -or $Json -eq "{}") { # empty json
- throw "Error! Empty JSON cannot be serialized due to the required property `{{{baseName}}}` missing."
+ throw "Error! Empty JSON cannot be serialized due to the required property '{{{baseName}}}' missing."
}
{{/-first}}
if (!([bool]($JsonParameters.PSobject.Properties.name -match "{{{baseName}}}"))) {
- throw "Error! JSON cannot be serialized due to the required property `{{{baseName}}}` missing."
+ throw "Error! JSON cannot be serialized due to the required property '{{{baseName}}}' missing."
} else {
${{name}} = $JsonParameters.PSobject.Properties["{{{baseName}}}"].value
}
diff --git a/modules/openapi-generator/src/main/resources/ruby-client/README.mustache b/modules/openapi-generator/src/main/resources/ruby-client/README.mustache
index dafacf98ad5..e35995e6339 100644
--- a/modules/openapi-generator/src/main/resources/ruby-client/README.mustache
+++ b/modules/openapi-generator/src/main/resources/ruby-client/README.mustache
@@ -72,9 +72,9 @@ require '{{{gemName}}}'
# Configure Bearer authorization{{#bearerFormat}} ({{{.}}}){{/bearerFormat}}: {{{name}}}
config.access_token = 'YOUR_BEARER_TOKEN'{{/isBasicBearer}}{{/isBasic}}{{#isApiKey}}
# Configure API key authorization: {{{name}}}
- config.api_key['{{{keyParamName}}}'] = 'YOUR API KEY'
+ config.api_key['{{{name}}}'] = 'YOUR API KEY'
# Uncomment the following line to set a prefix for the API key, e.g. 'Bearer' (defaults to nil)
- #config.api_key_prefix['{{{keyParamName}}}'] = 'Bearer'{{/isApiKey}}{{#isOAuth}}
+ # config.api_key_prefix['{{{name}}}'] = 'Bearer'{{/isApiKey}}{{#isOAuth}}
# Configure OAuth2 access token for authorization: {{{name}}}
config.access_token = 'YOUR ACCESS TOKEN'{{/isOAuth}}
{{/authMethods}}end
diff --git a/modules/openapi-generator/src/main/resources/ruby-client/api_client.mustache b/modules/openapi-generator/src/main/resources/ruby-client/api_client.mustache
index 6723bb756c6..2741a73794e 100644
--- a/modules/openapi-generator/src/main/resources/ruby-client/api_client.mustache
+++ b/modules/openapi-generator/src/main/resources/ruby-client/api_client.mustache
@@ -182,7 +182,7 @@ module {{moduleName}}
case auth_setting[:in]
when 'header' then header_params[auth_setting[:key]] = auth_setting[:value]
when 'query' then query_params[auth_setting[:key]] = auth_setting[:value]
- else fail ArgumentError, 'Authentication token must be in `query` of `header`'
+ else fail ArgumentError, 'Authentication token must be in `query` or `header`'
end
end
end
diff --git a/modules/openapi-generator/src/main/resources/ruby-client/api_doc.mustache b/modules/openapi-generator/src/main/resources/ruby-client/api_doc.mustache
index bdeeb568912..81fc865534d 100644
--- a/modules/openapi-generator/src/main/resources/ruby-client/api_doc.mustache
+++ b/modules/openapi-generator/src/main/resources/ruby-client/api_doc.mustache
@@ -37,9 +37,9 @@ require '{{{gemName}}}'
# Configure Bearer authorization{{#bearerFormat}} ({{{.}}}){{/bearerFormat}}: {{{name}}}
config.access_token = 'YOUR_BEARER_TOKEN'{{/isBasicBearer}}{{/isBasic}}{{#isApiKey}}
# Configure API key authorization: {{{name}}}
- config.api_key['{{{keyParamName}}}'] = 'YOUR API KEY'
+ config.api_key['{{{name}}}'] = 'YOUR API KEY'
# Uncomment the following line to set a prefix for the API key, e.g. 'Bearer' (defaults to nil)
- # config.api_key_prefix['{{{keyParamName}}}'] = 'Bearer'{{/isApiKey}}{{#isOAuth}}
+ # config.api_key_prefix['{{{name}}}'] = 'Bearer'{{/isApiKey}}{{#isOAuth}}
# Configure OAuth2 access token for authorization: {{{name}}}
config.access_token = 'YOUR ACCESS TOKEN'{{/isOAuth}}
{{/authMethods}}end
diff --git a/modules/openapi-generator/src/main/resources/ruby-client/configuration.mustache b/modules/openapi-generator/src/main/resources/ruby-client/configuration.mustache
index fd1ad422ddb..26d324489b9 100644
--- a/modules/openapi-generator/src/main/resources/ruby-client/configuration.mustache
+++ b/modules/openapi-generator/src/main/resources/ruby-client/configuration.mustache
@@ -169,11 +169,13 @@ module {{moduleName}}
# Gets API key (with prefix if set).
# @param [String] param_name the parameter name of API key auth
- def api_key_with_prefix(param_name)
+ def api_key_with_prefix(param_name, param_alias = nil)
+ key = @api_key[param_name]
+ key = @api_key.fetch(param_alias, key) unless param_alias.nil?
if @api_key_prefix[param_name]
- "#{@api_key_prefix[param_name]} #{@api_key[param_name]}"
+ "#{@api_key_prefix[param_name]} #{key}"
else
- @api_key[param_name]
+ key
end
end
@@ -192,7 +194,7 @@ module {{moduleName}}
type: 'api_key',
in: {{#isKeyInHeader}}'header'{{/isKeyInHeader}}{{#isKeyInQuery}}'query'{{/isKeyInQuery}},
key: '{{keyParamName}}',
- value: api_key_with_prefix('{{keyParamName}}')
+ value: api_key_with_prefix('{{name}}'{{#vendorExtensions.x-auth-id-alias}}, '{{.}}'{{/vendorExtensions.x-auth-id-alias}})
},
{{/isApiKey}}
{{#isBasic}}
diff --git a/modules/openapi-generator/src/main/resources/swift5/APIHelper.mustache b/modules/openapi-generator/src/main/resources/swift5/APIHelper.mustache
index 68d624fb64f..2630944598b 100644
--- a/modules/openapi-generator/src/main/resources/swift5/APIHelper.mustache
+++ b/modules/openapi-generator/src/main/resources/swift5/APIHelper.mustache
@@ -7,7 +7,7 @@
import Foundation
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct APIHelper {
- {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func rejectNil(_ source: [String:Any?]) -> [String:Any]? {
+ {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func rejectNil(_ source: [String: Any?]) -> [String: Any]? {
let destination = source.reduce(into: [String: Any]()) { (result, item) in
if let value = item.value {
result[item.key] = value
@@ -20,17 +20,17 @@ import Foundation
return destination
}
- {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func rejectNilHeaders(_ source: [String:Any?]) -> [String:String] {
+ {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func rejectNilHeaders(_ source: [String: Any?]) -> [String: String] {
return source.reduce(into: [String: String]()) { (result, item) in
- if let collection = item.value as? Array {
- result[item.key] = collection.filter({ $0 != nil }).map{ "\($0!)" }.joined(separator: ",")
+ if let collection = item.value as? [Any?] {
+ result[item.key] = collection.filter { $0 != nil }.map { "\($0!)" }.joined(separator: ",")
} else if let value: Any = item.value {
result[item.key] = "\(value)"
}
}
}
- {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func convertBoolToString(_ source: [String: Any]?) -> [String:Any]? {
+ {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func convertBoolToString(_ source: [String: Any]?) -> [String: Any]? {
guard let source = source else {
return nil
}
@@ -46,16 +46,16 @@ import Foundation
}
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func mapValueToPathItem(_ source: Any) -> Any {
- if let collection = source as? Array {
- return collection.filter({ $0 != nil }).map({"\($0!)"}).joined(separator: ",")
+ if let collection = source as? [Any?] {
+ return collection.filter { $0 != nil }.map {"\($0!)"}.joined(separator: ",")
}
return source
}
- {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func mapValuesToQueryItems(_ source: [String:Any?]) -> [URLQueryItem]? {
+ {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func mapValuesToQueryItems(_ source: [String: Any?]) -> [URLQueryItem]? {
let destination = source.filter({ $0.value != nil}).reduce(into: [URLQueryItem]()) { (result, item) in
- if let collection = item.value as? Array {
- collection.filter({ $0 != nil }).map({"\($0!)"}).forEach { value in
+ if let collection = item.value as? [Any?] {
+ collection.filter { $0 != nil }.map {"\($0!)"}.forEach { value in
result.append(URLQueryItem(name: item.key, value: value))
}
} else if let value = item.value {
@@ -69,4 +69,3 @@ import Foundation
return destination
}
}
-
diff --git a/modules/openapi-generator/src/main/resources/swift5/APIs.mustache b/modules/openapi-generator/src/main/resources/swift5/APIs.mustache
index 5429af6973f..d672115546f 100644
--- a/modules/openapi-generator/src/main/resources/swift5/APIs.mustache
+++ b/modules/openapi-generator/src/main/resources/swift5/APIs.mustache
@@ -9,7 +9,7 @@ import Foundation
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class {{projectName}}API {
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var basePath = "{{{basePath}}}"
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var credential: URLCredential?
- {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var customHeaders: [String:String] = [:]{{#useAlamofire}}
+ {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var customHeaders: [String: String] = [:]{{#useAlamofire}}
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var requestBuilderFactory: RequestBuilderFactory = AlamofireRequestBuilderFactory(){{/useAlamofire}}{{#useURLSession}}
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var requestBuilderFactory: RequestBuilderFactory = URLSessionRequestBuilderFactory(){{/useURLSession}}
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var apiResponseQueue: DispatchQueue = .main
@@ -17,28 +17,26 @@ import Foundation
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class RequestBuilder {
var credential: URLCredential?
- var headers: [String:String]
- {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let parameters: [String:Any]?
- {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let isBody: Bool
+ var headers: [String: String]
+ {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let parameters: [String: Any]?
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let method: String
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let URLString: String
/// Optional block to obtain a reference to the request's progress instance when available.{{#useURLSession}}
/// With the URLSession http client the request's progress only works on iOS 11.0, macOS 10.13, macCatalyst 13.0, tvOS 11.0, watchOS 4.0.
/// If you need to get the request's progress in older OS versions, please use Alamofire http client.{{/useURLSession}}
- {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var onProgressReady: ((Progress) -> ())?
+ {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var onProgressReady: ((Progress) -> Void)?
- required {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(method: String, URLString: String, parameters: [String:Any]?, isBody: Bool, headers: [String:String] = [:]) {
+ required {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(method: String, URLString: String, parameters: [String: Any]?, headers: [String: String] = [:]) {
self.method = method
self.URLString = URLString
self.parameters = parameters
- self.isBody = isBody
self.headers = headers
addHeaders({{projectName}}API.customHeaders)
}
- {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func addHeaders(_ aHeaders:[String:String]) {
+ {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func addHeaders(_ aHeaders: [String: String]) {
for (header, value) in aHeaders {
headers[header] = value
}
@@ -61,5 +59,5 @@ import Foundation
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} protocol RequestBuilderFactory {
func getNonDecodableBuilder() -> RequestBuilder.Type
- func getBuilder() -> RequestBuilder.Type
+ func getBuilder() -> RequestBuilder.Type
}
diff --git a/modules/openapi-generator/src/main/resources/swift5/Cartfile.mustache b/modules/openapi-generator/src/main/resources/swift5/Cartfile.mustache
index 9c30d7413b9..3f6269c23ab 100644
--- a/modules/openapi-generator/src/main/resources/swift5/Cartfile.mustache
+++ b/modules/openapi-generator/src/main/resources/swift5/Cartfile.mustache
@@ -1,3 +1,3 @@
{{#useAlamofire}}github "Alamofire/Alamofire" ~> 4.9.1{{/useAlamofire}}{{#usePromiseKit}}
-github "mxcl/PromiseKit" ~> 6.12.0{{/usePromiseKit}}{{#useRxSwift}}
-github "ReactiveX/RxSwift" ~> 5.0.1{{/useRxSwift}}
+github "mxcl/PromiseKit" ~> 6.13.1{{/usePromiseKit}}{{#useRxSwift}}
+github "ReactiveX/RxSwift" ~> 5.1.1{{/useRxSwift}}
diff --git a/modules/openapi-generator/src/main/resources/swift5/CodableHelper.mustache b/modules/openapi-generator/src/main/resources/swift5/CodableHelper.mustache
index baad56cca2f..d5b1c1e0a29 100644
--- a/modules/openapi-generator/src/main/resources/swift5/CodableHelper.mustache
+++ b/modules/openapi-generator/src/main/resources/swift5/CodableHelper.mustache
@@ -45,4 +45,4 @@ import Foundation
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func encode(_ value: T) -> Swift.Result where T: Encodable {
return Swift.Result { try self.jsonEncoder.encode(value) }
}
-}
\ No newline at end of file
+}
diff --git a/modules/openapi-generator/src/main/resources/swift5/Configuration.mustache b/modules/openapi-generator/src/main/resources/swift5/Configuration.mustache
index b6e8d28fe4b..47c874815b9 100644
--- a/modules/openapi-generator/src/main/resources/swift5/Configuration.mustache
+++ b/modules/openapi-generator/src/main/resources/swift5/Configuration.mustache
@@ -7,10 +7,10 @@
import Foundation
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class Configuration {
-
+
// This value is used to configure the date formatter that is used to serialize dates into JSON format.
// You must set it prior to encoding any dates, and it will only be read once.
@available(*, unavailable, message: "To set a different date format, use CodableHelper.dateFormatter instead.")
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static var dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSSZZZZZ"
-
-}
\ No newline at end of file
+
+}
diff --git a/modules/openapi-generator/src/main/resources/swift5/Extensions.mustache b/modules/openapi-generator/src/main/resources/swift5/Extensions.mustache
index 311b4bf3b31..1a23697a04b 100644
--- a/modules/openapi-generator/src/main/resources/swift5/Extensions.mustache
+++ b/modules/openapi-generator/src/main/resources/swift5/Extensions.mustache
@@ -109,24 +109,24 @@ extension String: CodingKey {
extension KeyedEncodingContainerProtocol {
- {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} mutating func encodeArray(_ values: [T], forKey key: Self.Key) throws where T : Encodable {
+ {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} mutating func encodeArray(_ values: [T], forKey key: Self.Key) throws where T: Encodable {
var arrayContainer = nestedUnkeyedContainer(forKey: key)
try arrayContainer.encode(contentsOf: values)
}
- {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} mutating func encodeArrayIfPresent(_ values: [T]?, forKey key: Self.Key) throws where T : Encodable {
+ {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} mutating func encodeArrayIfPresent(_ values: [T]?, forKey key: Self.Key) throws where T: Encodable {
if let values = values {
try encodeArray(values, forKey: key)
}
}
- {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} mutating func encodeMap(_ pairs: [Self.Key: T]) throws where T : Encodable {
+ {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} mutating func encodeMap(_ pairs: [Self.Key: T]) throws where T: Encodable {
for (key, value) in pairs {
try encode(value, forKey: key)
}
}
- {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} mutating func encodeMapIfPresent(_ pairs: [Self.Key: T]?) throws where T : Encodable {
+ {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} mutating func encodeMapIfPresent(_ pairs: [Self.Key: T]?) throws where T: Encodable {
if let pairs = pairs {
try encodeMap(pairs)
}
@@ -136,7 +136,7 @@ extension KeyedEncodingContainerProtocol {
extension KeyedDecodingContainerProtocol {
- {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func decodeArray(_ type: T.Type, forKey key: Self.Key) throws -> [T] where T : Decodable {
+ {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func decodeArray(_ type: T.Type, forKey key: Self.Key) throws -> [T] where T: Decodable {
var tmpArray = [T]()
var nestedContainer = try nestedUnkeyedContainer(forKey: key)
@@ -148,8 +148,8 @@ extension KeyedDecodingContainerProtocol {
return tmpArray
}
- {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func decodeArrayIfPresent(_ type: T.Type, forKey key: Self.Key) throws -> [T]? where T : Decodable {
- var tmpArray: [T]? = nil
+ {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func decodeArrayIfPresent(_ type: T.Type, forKey key: Self.Key) throws -> [T]? where T: Decodable {
+ var tmpArray: [T]?
if contains(key) {
tmpArray = try decodeArray(T.self, forKey: key)
@@ -158,8 +158,8 @@ extension KeyedDecodingContainerProtocol {
return tmpArray
}
- {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func decodeMap(_ type: T.Type, excludedKeys: Set) throws -> [Self.Key: T] where T : Decodable {
- var map: [Self.Key : T] = [:]
+ {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func decodeMap(_ type: T.Type, excludedKeys: Set) throws -> [Self.Key: T] where T: Decodable {
+ var map: [Self.Key: T] = [:]
for key in allKeys {
if !excludedKeys.contains(key) {
@@ -177,10 +177,10 @@ extension HTTPURLResponse {
var isStatusCodeSuccessful: Bool {
return Array(200 ..< 300).contains(statusCode)
}
-}
+}{{#usePromiseKit}}
-{{#usePromiseKit}}extension RequestBuilder {
- {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func execute() -> Promise> {
+extension RequestBuilder {
+ {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} func execute() -> Promise> {
let deferred = Promise>.pending()
self.execute { result in
switch result {
diff --git a/modules/openapi-generator/src/main/resources/swift5/JSONDataEncoding.mustache b/modules/openapi-generator/src/main/resources/swift5/JSONDataEncoding.mustache
index 71cdb1da93c..e227058129b 100644
--- a/modules/openapi-generator/src/main/resources/swift5/JSONDataEncoding.mustache
+++ b/modules/openapi-generator/src/main/resources/swift5/JSONDataEncoding.mustache
@@ -41,7 +41,7 @@ import Foundation
}
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} static func encodingParameters(jsonData: Data?) -> [String: Any]? {
- var returnedParams: [String: Any]? = nil
+ var returnedParams: [String: Any]?
if let jsonData = jsonData, !jsonData.isEmpty {
var params: [String: Any] = [:]
params[jsonDataKey] = jsonData
diff --git a/modules/openapi-generator/src/main/resources/swift5/JSONEncodingHelper.mustache b/modules/openapi-generator/src/main/resources/swift5/JSONEncodingHelper.mustache
index 0191a8fd84a..0eae73e1b39 100644
--- a/modules/openapi-generator/src/main/resources/swift5/JSONEncodingHelper.mustache
+++ b/modules/openapi-generator/src/main/resources/swift5/JSONEncodingHelper.mustache
@@ -9,8 +9,8 @@ import Foundation
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class JSONEncodingHelper {
- {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func encodingParameters(forEncodableObject encodableObj: T?) -> [String: Any]? {
- var params: [String: Any]? = nil
+ {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func encodingParameters(forEncodableObject encodableObj: T?) -> [String: Any]? {
+ var params: [String: Any]?
// Encode the Encodable object
if let encodableObj = encodableObj {
@@ -27,7 +27,7 @@ import Foundation
}
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func encodingParameters(forEncodableObject encodableObj: Any?) -> [String: Any]? {
- var params: [String: Any]? = nil
+ var params: [String: Any]?
if let encodableObj = encodableObj {
do {
@@ -41,5 +41,5 @@ import Foundation
return params
}
-
+
}
diff --git a/modules/openapi-generator/src/main/resources/swift5/Models.mustache b/modules/openapi-generator/src/main/resources/swift5/Models.mustache
index 9a3f50d5d8e..74ce973b287 100644
--- a/modules/openapi-generator/src/main/resources/swift5/Models.mustache
+++ b/modules/openapi-generator/src/main/resources/swift5/Models.mustache
@@ -10,11 +10,11 @@ protocol JSONEncodable {
func encodeToJSON() -> Any
}
-{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum ErrorResponse : Error {
- case error(Int, Data?, Error)
+{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum ErrorResponse: Error {
+ case error(Int, Data?, URLResponse?, Error)
}
-{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum DownloadException : Error {
+{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum DownloadException: Error {
case responseDataMissing
case responseFailed
case requestMissing
@@ -30,7 +30,6 @@ protocol JSONEncodable {
case generalError(Error)
}
-
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class Response {
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let statusCode: Int
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} let header: [String: String]
@@ -44,7 +43,7 @@ protocol JSONEncodable {
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} convenience init(response: HTTPURLResponse, body: T?) {
let rawHeader = response.allHeaderFields
- var header = [String:String]()
+ var header = [String: String]()
for (key, value) in rawHeader {
if let key = key.base as? String, let value = value as? String {
header[key] = value
diff --git a/modules/openapi-generator/src/main/resources/swift5/Podspec.mustache b/modules/openapi-generator/src/main/resources/swift5/Podspec.mustache
index e25eab49911..c0539662ce7 100644
--- a/modules/openapi-generator/src/main/resources/swift5/Podspec.mustache
+++ b/modules/openapi-generator/src/main/resources/swift5/Podspec.mustache
@@ -27,10 +27,10 @@ Pod::Spec.new do |s|
{{/podDocumentationURL}}
s.source_files = '{{projectName}}/Classes/**/*.swift'
{{#usePromiseKit}}
- s.dependency 'PromiseKit/CorePromise', '~> 6.12.0'
+ s.dependency 'PromiseKit/CorePromise', '~> 6.13.1'
{{/usePromiseKit}}
{{#useRxSwift}}
- s.dependency 'RxSwift', '~> 5.0.0'
+ s.dependency 'RxSwift', '~> 5.1.1'
{{/useRxSwift}}
{{#useAlamofire}}
s.dependency 'Alamofire', '~> 4.9.1'
diff --git a/modules/openapi-generator/src/main/resources/swift5/XcodeGen.mustache b/modules/openapi-generator/src/main/resources/swift5/XcodeGen.mustache
index 3b57c69e104..a81137ae138 100644
--- a/modules/openapi-generator/src/main/resources/swift5/XcodeGen.mustache
+++ b/modules/openapi-generator/src/main/resources/swift5/XcodeGen.mustache
@@ -3,7 +3,7 @@ targets:
{{projectName}}:
type: framework
platform: iOS
- deploymentTarget: "10.0"
+ deploymentTarget: "9.0"
sources: [{{projectName}}]
info:
path: ./Info.plist
diff --git a/modules/openapi-generator/src/main/resources/swift5/api.mustache b/modules/openapi-generator/src/main/resources/swift5/api.mustache
index b5aedeb389b..18d3e6b3045 100644
--- a/modules/openapi-generator/src/main/resources/swift5/api.mustache
+++ b/modules/openapi-generator/src/main/resources/swift5/api.mustache
@@ -8,9 +8,8 @@
import Foundation{{#usePromiseKit}}
import PromiseKit{{/usePromiseKit}}{{#useRxSwift}}
import RxSwift{{/useRxSwift}}{{#useCombine}}
-import Combine{{/useCombine}}
+import Combine{{/useCombine}}{{#swiftUseApiNamespace}}
-{{#swiftUseApiNamespace}}
extension {{projectName}}API {
{{/swiftUseApiNamespace}}
@@ -48,7 +47,7 @@ extension {{projectName}}API {
{{#isDeprecated}}
@available(*, deprecated, message: "This operation is deprecated.")
{{/isDeprecated}}
- {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func {{operationId}}({{#allParams}}{{paramName}}: {{#isEnum}}{{#isContainer}}{{{dataType}}}{{/isContainer}}{{^isContainer}}{{{datatypeWithEnum}}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}apiResponseQueue: DispatchQueue = {{projectName}}API.apiResponseQueue, completion: @escaping ((_ data: {{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Void{{/returnType}}?,_ error: Error?) -> Void)) {
+ {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class func {{operationId}}({{#allParams}}{{paramName}}: {{#isEnum}}{{#isContainer}}{{{dataType}}}{{/isContainer}}{{^isContainer}}{{{datatypeWithEnum}}}_{{operationId}}{{/isContainer}}{{/isEnum}}{{^isEnum}}{{{dataType}}}{{/isEnum}}{{^required}}? = nil{{/required}}{{^-last}}, {{/-last}}{{/allParams}}{{#hasParams}}, {{/hasParams}}apiResponseQueue: DispatchQueue = {{projectName}}API.apiResponseQueue, completion: @escaping ((_ data: {{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Void{{/returnType}}?, _ error: Error?) -> Void)) {
{{operationId}}WithRequestBuilder({{#allParams}}{{paramName}}: {{paramName}}{{^-last}}, {{/-last}}{{/allParams}}).execute(apiResponseQueue) { result -> Void in
switch result {
{{#returnType}}
@@ -232,9 +231,9 @@ extension {{projectName}}API {
{{/bodyParam}}
{{^bodyParam}}
{{#hasFormParams}}
- let formParams: [String:Any?] = [
+ let formParams: [String: Any?] = [
{{#formParams}}
- {{> _param}}{{^-last}},{{/-last}}
+ {{> _param}},
{{/formParams}}
]
@@ -242,24 +241,28 @@ extension {{projectName}}API {
let parameters = APIHelper.convertBoolToString(nonNullParameters)
{{/hasFormParams}}
{{^hasFormParams}}
- let parameters: [String:Any]? = nil
+ let parameters: [String: Any]? = nil
{{/hasFormParams}}
- {{/bodyParam}}{{#hasQueryParams}}
+{{/bodyParam}}{{#hasQueryParams}}
var url = URLComponents(string: URLString)
url?.queryItems = APIHelper.mapValuesToQueryItems([{{^queryParams}}:{{/queryParams}}
{{#queryParams}}
- {{> _param}}{{^-last}}, {{/-last}}
+ {{> _param}},
{{/queryParams}}
]){{/hasQueryParams}}{{^hasQueryParams}}
- let url = URLComponents(string: URLString){{/hasQueryParams}}{{#headerParams}}{{#-first}}
- let nillableHeaders: [String: Any?] = [{{/-first}}
- {{> _param}}{{^-last}},{{/-last}}{{#-last}}
+ let url = URLComponents(string: URLString){{/hasQueryParams}}
+
+ let nillableHeaders: [String: Any?] = [{{^headerParams}}{{^hasFormParams}}
+ :{{/hasFormParams}}{{/headerParams}}{{#hasFormParams}}
+ "Content-Type": {{^consumes}}"multipart/form-data"{{/consumes}}{{#consumes.0}}"{{{mediaType}}}"{{/consumes.0}},{{/hasFormParams}}{{#headerParams}}
+ {{> _param}},{{/headerParams}}
]
- let headerParameters = APIHelper.rejectNilHeaders(nillableHeaders){{/-last}}{{/headerParams}}
+
+ let headerParameters = APIHelper.rejectNilHeaders(nillableHeaders)
let requestBuilder: RequestBuilder<{{#returnType}}{{{returnType}}}{{/returnType}}{{^returnType}}Void{{/returnType}}>.Type = {{projectName}}API.requestBuilderFactory.{{#returnType}}getBuilder(){{/returnType}}{{^returnType}}getNonDecodableBuilder(){{/returnType}}
- return requestBuilder.init(method: "{{httpMethod}}", URLString: (url?.string ?? URLString), parameters: parameters, isBody: {{hasBodyParam}}{{#headerParams}}{{#-first}}, headers: headerParameters{{/-first}}{{/headerParams}})
+ return requestBuilder.init(method: "{{httpMethod}}", URLString: (url?.string ?? URLString), parameters: parameters, headers: headerParameters)
}
{{/operation}}
diff --git a/modules/openapi-generator/src/main/resources/swift5/libraries/alamofire/AlamofireImplementations.mustache b/modules/openapi-generator/src/main/resources/swift5/libraries/alamofire/AlamofireImplementations.mustache
index 9b5389031e9..ef593fe7597 100644
--- a/modules/openapi-generator/src/main/resources/swift5/libraries/alamofire/AlamofireImplementations.mustache
+++ b/modules/openapi-generator/src/main/resources/swift5/libraries/alamofire/AlamofireImplementations.mustache
@@ -12,7 +12,7 @@ class AlamofireRequestBuilderFactory: RequestBuilderFactory {
return AlamofireRequestBuilder.self
}
- func getBuilder() -> RequestBuilder.Type {
+ func getBuilder() -> RequestBuilder.Type {
return AlamofireDecodableRequestBuilder.self
}
}
@@ -21,8 +21,8 @@ class AlamofireRequestBuilderFactory: RequestBuilderFactory {
private var managerStore = SynchronizedDictionary()
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class AlamofireRequestBuilder: RequestBuilder {
- required {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(method: String, URLString: String, parameters: [String : Any]?, isBody: Bool, headers: [String : String] = [:]) {
- super.init(method: method, URLString: URLString, parameters: parameters, isBody: isBody, headers: headers)
+ required {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(method: String, URLString: String, parameters: [String: Any]?, headers: [String: String] = [:]) {
+ super.init(method: method, URLString: URLString, parameters: parameters, headers: headers)
}
/**
@@ -39,7 +39,20 @@ private var managerStore = SynchronizedDictionary URLRequest? {
- let encoding: ParameterEncoding = isBody ? JSONDataEncoding() : URLEncoding()
+ guard let xMethod = Alamofire.HTTPMethod(rawValue: method) else {
+ fatalError("Unsuported Http method - \(method)")
+ }
+
+ let encoding: ParameterEncoding
+
+ switch xMethod {
+ case .get, .head:
+ encoding = URLEncoding()
+
+ case .options, .post, .put, .patch, .delete, .trace, .connect:
+ encoding = JSONDataEncoding()
+ }
+
guard let originalRequest = try? URLRequest(url: URLString, method: HTTPMethod(rawValue: method)!, headers: buildHeaders()) else { return nil }
return try? encoding.encode(originalRequest, with: parameters)
}
@@ -59,62 +72,78 @@ private var managerStore = SynchronizedDictionary DataRequest {
+ {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func makeRequest(manager: SessionManager, method: HTTPMethod, encoding: ParameterEncoding, headers: [String: String]) -> DataRequest {
return manager.request(URLString, method: method, parameters: parameters, encoding: encoding, headers: headers)
}
override {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func execute(_ apiResponseQueue: DispatchQueue = {{projectName}}API.apiResponseQueue, _ completion: @escaping (_ result: Swift.Result, Error>) -> Void) {
- let managerId:String = UUID().uuidString
+ let managerId = UUID().uuidString
// Create a new manager for each request to customize its request header
let manager = createSessionManager()
managerStore[managerId] = manager
- let encoding:ParameterEncoding = isBody ? JSONDataEncoding() : URLEncoding()
+ guard let xMethod = Alamofire.HTTPMethod(rawValue: method) else {
+ fatalError("Unsuported Http method - \(method)")
+ }
- let xMethod = Alamofire.HTTPMethod(rawValue: method)
- let fileKeys = parameters == nil ? [] : parameters!.filter { $1 is NSURL }
- .map { $0.0 }
+ let encoding: ParameterEncoding?
- if fileKeys.count > 0 {
- manager.upload(multipartFormData: { mpForm in
- for (k, v) in self.parameters! {
- switch v {
- case let fileURL as URL:
- if let mimeType = self.contentTypeForFormPart(fileURL: fileURL) {
- mpForm.append(fileURL, withName: k, fileName: fileURL.lastPathComponent, mimeType: mimeType)
+ switch xMethod {
+ case .get, .head:
+ encoding = URLEncoding()
+
+ case .options, .post, .put, .patch, .delete, .trace, .connect:
+ let contentType = headers["Content-Type"] ?? "application/json"
+
+ if contentType == "application/json" {
+ encoding = JSONDataEncoding()
+ } else if contentType == "multipart/form-data" {
+ encoding = nil
+
+ manager.upload(multipartFormData: { mpForm in
+ for (k, v) in self.parameters! {
+ switch v {
+ case let fileURL as URL:
+ if let mimeType = self.contentTypeForFormPart(fileURL: fileURL) {
+ mpForm.append(fileURL, withName: k, fileName: fileURL.lastPathComponent, mimeType: mimeType)
+ } else {
+ mpForm.append(fileURL, withName: k)
+ }
+ case let string as String:
+ mpForm.append(string.data(using: String.Encoding.utf8)!, withName: k)
+ case let number as NSNumber:
+ mpForm.append(number.stringValue.data(using: String.Encoding.utf8)!, withName: k)
+ default:
+ fatalError("Unprocessable value \(v) with key \(k)")
}
- else {
- mpForm.append(fileURL, withName: k)
+ }
+ }, to: URLString, method: xMethod, headers: nil, encodingCompletion: { encodingResult in
+ switch encodingResult {
+ case .success(let upload, _, _):
+ if let onProgressReady = self.onProgressReady {
+ onProgressReady(upload.uploadProgress)
+ }
+ self.processRequest(request: upload, managerId, apiResponseQueue, completion)
+ case .failure(let encodingError):
+ apiResponseQueue.async {
+ completion(.failure(ErrorResponse.error(415, nil, nil, encodingError)))
}
- case let string as String:
- mpForm.append(string.data(using: String.Encoding.utf8)!, withName: k)
- case let number as NSNumber:
- mpForm.append(number.stringValue.data(using: String.Encoding.utf8)!, withName: k)
- default:
- fatalError("Unprocessable value \(v) with key \(k)")
}
- }
- }, to: URLString, method: xMethod!, headers: nil, encodingCompletion: { encodingResult in
- switch encodingResult {
- case .success(let upload, _, _):
- if let onProgressReady = self.onProgressReady {
- onProgressReady(upload.uploadProgress)
- }
- self.processRequest(request: upload, managerId, apiResponseQueue, completion)
- case .failure(let encodingError):
- apiResponseQueue.async{
- completion(.failure(ErrorResponse.error(415, nil, encodingError)))
- }
- }
- })
- } else {
- let request = makeRequest(manager: manager, method: xMethod!, encoding: encoding, headers: headers)
+ })
+ } else if contentType == "application/x-www-form-urlencoded" {
+ encoding = URLEncoding(destination: .httpBody)
+ } else {
+ fatalError("Unsuported Media Type - \(contentType)")
+ }
+ }
+
+ if let encoding = encoding {
+ let request = makeRequest(manager: manager, method: xMethod, encoding: encoding, headers: headers)
if let onProgressReady = self.onProgressReady {
onProgressReady(request.progress)
}
processRequest(request: request, managerId, apiResponseQueue, completion)
}
-
}
fileprivate func processRequest(request: DataRequest, _ managerId: String, _ apiResponseQueue: DispatchQueue, _ completion: @escaping (_ result: Swift.Result, Error>) -> Void) {
@@ -132,14 +161,14 @@ private var managerStore = SynchronizedDictionary String? {
+ fileprivate func getFileName(fromContentDisposition contentDisposition: String?) -> String? {
guard let contentDisposition = contentDisposition else {
return nil
@@ -228,7 +257,7 @@ private var managerStore = SynchronizedDictionary String {
+ fileprivate func getPath(from url: URL) throws -> String {
guard var path = URLComponents(url: url, resolvingAgainstBaseURL: true)?.path else {
throw DownloadException.requestMissingPath
@@ -262,7 +291,7 @@ private var managerStore = SynchronizedDictionary URL {
+ fileprivate func getURL(from urlRequest: URLRequest) throws -> URL {
guard let url = urlRequest.url else {
throw DownloadException.requestMissingURL
@@ -273,7 +302,7 @@ private var managerStore = SynchronizedDictionary: AlamofireRequestBuilder {
+{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class AlamofireDecodableRequestBuilder: AlamofireRequestBuilder {
override fileprivate func processRequest(request: DataRequest, _ managerId: String, _ apiResponseQueue: DispatchQueue, _ completion: @escaping (_ result: Swift.Result, Error>) -> Void) {
if let credential = self.credential {
@@ -290,36 +319,36 @@ private var managerStore = SynchronizedDictionary URLRequest {
let urlRequest = try urlRequest.asURLRequest()
-
+
return self.encode(urlRequest, with: parameters)
}
}
diff --git a/modules/openapi-generator/src/main/resources/swift5/libraries/urlsession/URLSessionImplementations.mustache b/modules/openapi-generator/src/main/resources/swift5/libraries/urlsession/URLSessionImplementations.mustache
index 0f19cdfbcf9..594e9c7c1cd 100644
--- a/modules/openapi-generator/src/main/resources/swift5/libraries/urlsession/URLSessionImplementations.mustache
+++ b/modules/openapi-generator/src/main/resources/swift5/libraries/urlsession/URLSessionImplementations.mustache
@@ -14,7 +14,7 @@ class URLSessionRequestBuilderFactory: RequestBuilderFactory {
return URLSessionRequestBuilder.self
}
- func getBuilder() -> RequestBuilder.Type {
+ func getBuilder() -> RequestBuilder.Type {
return URLSessionDecodableRequestBuilder.self
}
}
@@ -23,16 +23,10 @@ class URLSessionRequestBuilderFactory: RequestBuilderFactory {
private var urlSessionStore = SynchronizedDictionary()
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class URLSessionRequestBuilder: RequestBuilder {
-
- private var observation: NSKeyValueObservation?
-
- deinit {
- observation?.invalidate()
- }
-
+
// swiftlint:disable:next weak_delegate
fileprivate let sessionDelegate = SessionDelegate()
-
+
/**
May be assigned if you want to control the authentication challenges.
*/
@@ -44,12 +38,13 @@ private var urlSessionStore = SynchronizedDictionary()
- intercept and handle errors like authorization
- retry the request.
*/
+ @available(*, deprecated, message: "Please override execute() method to intercept and handle errors like authorization or retry the request. Check the Wiki for more info. https://github.com/OpenAPITools/openapi-generator/wiki/FAQ#how-do-i-implement-bearer-token-authentication-with-urlsession-on-the-swift-api-client")
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} var taskCompletionShouldRetry: ((Data?, URLResponse?, Error?, @escaping (Bool) -> Void) -> Void)?
-
- required {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(method: String, URLString: String, parameters: [String : Any]?, isBody: Bool, headers: [String : String] = [:]) {
- super.init(method: method, URLString: URLString, parameters: parameters, isBody: isBody, headers: headers)
+
+ required {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init(method: String, URLString: String, parameters: [String: Any]?, headers: [String: String] = [:]) {
+ super.init(method: method, URLString: URLString, parameters: parameters, headers: headers)
}
-
+
/**
May be overridden by a subclass if you want to control the URLSession
configuration.
@@ -77,71 +72,76 @@ private var urlSessionStore = SynchronizedDictionary()
May be overridden by a subclass if you want to control the URLRequest
configuration (e.g. to override the cache policy).
*/
- {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func createURLRequest(urlSession: URLSession, method: HTTPMethod, encoding: ParameterEncoding, headers: [String:String]) throws -> URLRequest {
-
+ {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func createURLRequest(urlSession: URLSession, method: HTTPMethod, encoding: ParameterEncoding, headers: [String: String]) throws -> URLRequest {
+
guard let url = URL(string: URLString) else {
throw DownloadException.requestMissingURL
}
-
+
var originalRequest = URLRequest(url: url)
-
+
originalRequest.httpMethod = method.rawValue
-
+
headers.forEach { key, value in
originalRequest.setValue(value, forHTTPHeaderField: key)
}
-
+
buildHeaders().forEach { key, value in
originalRequest.setValue(value, forHTTPHeaderField: key)
}
-
+
let modifiedRequest = try encoding.encode(originalRequest, with: parameters)
-
+
return modifiedRequest
}
override {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func execute(_ apiResponseQueue: DispatchQueue = {{projectName}}API.apiResponseQueue, _ completion: @escaping (_ result: Swift.Result, Error>) -> Void) {
- let urlSessionId:String = UUID().uuidString
+ let urlSessionId = UUID().uuidString
// Create a new manager for each request to customize its request header
let urlSession = createURLSession()
urlSessionStore[urlSessionId] = urlSession
-
- let parameters: [String: Any] = self.parameters ?? [:]
-
- let fileKeys = parameters.filter { $1 is URL }
- .map { $0.0 }
-
- let encoding: ParameterEncoding
- if fileKeys.count > 0 {
- encoding = FileUploadEncoding(contentTypeForFormPart: contentTypeForFormPart(fileURL:))
- } else if isBody {
- encoding = JSONDataEncoding()
- } else {
- encoding = URLEncoding()
- }
-
+
guard let xMethod = HTTPMethod(rawValue: method) else {
fatalError("Unsuported Http method - \(method)")
}
-
+
+ let encoding: ParameterEncoding
+
+ switch xMethod {
+ case .get, .head:
+ encoding = URLEncoding()
+
+ case .options, .post, .put, .patch, .delete, .trace, .connect:
+ let contentType = headers["Content-Type"] ?? "application/json"
+
+ if contentType == "application/json" {
+ encoding = JSONDataEncoding()
+ } else if contentType == "multipart/form-data" {
+ encoding = FormDataEncoding(contentTypeForFormPart: contentTypeForFormPart(fileURL:))
+ } else if contentType == "application/x-www-form-urlencoded" {
+ encoding = FormURLEncoding()
+ } else {
+ fatalError("Unsuported Media Type - \(contentType)")
+ }
+ }
+
let cleanupRequest = {
urlSessionStore[urlSessionId] = nil
- self.observation?.invalidate()
}
-
+
do {
let request = try createURLRequest(urlSession: urlSession, method: xMethod, encoding: encoding, headers: headers)
-
+
let dataTask = urlSession.dataTask(with: request) { [weak self] data, response, error in
-
+
guard let self = self else { return }
-
+
if let taskCompletionShouldRetry = self.taskCompletionShouldRetry {
-
+
taskCompletionShouldRetry(data, response, error) { [weak self] shouldRetry in
-
+
guard let self = self else { return }
-
+
if shouldRetry {
cleanupRequest()
self.execute(apiResponseQueue, completion)
@@ -157,94 +157,93 @@ private var urlSessionStore = SynchronizedDictionary()
}
}
}
-
+
if #available(iOS 11.0, macOS 10.13, macCatalyst 13.0, tvOS 11.0, watchOS 4.0, *) {
onProgressReady?(dataTask.progress)
}
-
+
dataTask.resume()
-
+
} catch {
apiResponseQueue.async {
cleanupRequest()
- completion(.failure(ErrorResponse.error(415, nil, error)))
+ completion(.failure(ErrorResponse.error(415, nil, nil, error)))
}
}
-
}
-
+
fileprivate func processRequestResponse(urlRequest: URLRequest, data: Data?, response: URLResponse?, error: Error?, completion: @escaping (_ result: Swift.Result, Error>) -> Void) {
if let error = error {
- completion(.failure(ErrorResponse.error(-1, data, error)))
+ completion(.failure(ErrorResponse.error(-1, data, response, error)))
return
}
guard let httpResponse = response as? HTTPURLResponse else {
- completion(.failure(ErrorResponse.error(-2, data, DecodableRequestBuilderError.nilHTTPResponse)))
+ completion(.failure(ErrorResponse.error(-2, data, response, DecodableRequestBuilderError.nilHTTPResponse)))
return
}
guard httpResponse.isStatusCodeSuccessful else {
- completion(.failure(ErrorResponse.error(httpResponse.statusCode, data, DecodableRequestBuilderError.unsuccessfulHTTPStatusCode)))
+ completion(.failure(ErrorResponse.error(httpResponse.statusCode, data, response, DecodableRequestBuilderError.unsuccessfulHTTPStatusCode)))
return
}
switch T.self {
case is String.Type:
-
+
let body = data.flatMap { String(data: $0, encoding: .utf8) } ?? ""
-
+
completion(.success(Response(response: httpResponse, body: body as? T)))
-
+
case is URL.Type:
do {
-
+
guard error == nil else {
throw DownloadException.responseFailed
}
-
+
guard let data = data else {
throw DownloadException.responseDataMissing
}
-
+
let fileManager = FileManager.default
let documentsDirectory = fileManager.urls(for: .documentDirectory, in: .userDomainMask)[0]
let requestURL = try self.getURL(from: urlRequest)
-
+
var requestPath = try self.getPath(from: requestURL)
-
+
if let headerFileName = self.getFileName(fromContentDisposition: httpResponse.allHeaderFields["Content-Disposition"] as? String) {
requestPath = requestPath.appending("/\(headerFileName)")
}
-
+
let filePath = documentsDirectory.appendingPathComponent(requestPath)
let directoryPath = filePath.deletingLastPathComponent().path
-
+
try fileManager.createDirectory(atPath: directoryPath, withIntermediateDirectories: true, attributes: nil)
try data.write(to: filePath, options: .atomic)
-
+
completion(.success(Response(response: httpResponse, body: filePath as? T)))
-
+
} catch let requestParserError as DownloadException {
- completion(.failure(ErrorResponse.error(400, data, requestParserError)))
+ completion(.failure(ErrorResponse.error(400, data, response, requestParserError)))
} catch let error {
- completion(.failure(ErrorResponse.error(400, data, error)))
+ completion(.failure(ErrorResponse.error(400, data, response, error)))
}
-
+
case is Void.Type:
-
+
completion(.success(Response(response: httpResponse, body: nil)))
-
+
default:
-
+
completion(.success(Response(response: httpResponse, body: data as? T)))
}
}
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} func buildHeaders() -> [String: String] {
- var httpHeaders: [String : String] = [:]
+ var httpHeaders: [String: String] = [:]
for (key, value) in self.headers {
httpHeaders[key] = value
}
@@ -254,7 +253,7 @@ private var urlSessionStore = SynchronizedDictionary()
return httpHeaders
}
- fileprivate func getFileName(fromContentDisposition contentDisposition : String?) -> String? {
+ fileprivate func getFileName(fromContentDisposition contentDisposition: String?) -> String? {
guard let contentDisposition = contentDisposition else {
return nil
@@ -273,7 +272,7 @@ private var urlSessionStore = SynchronizedDictionary()
filename = contentItem
return filename?
- .replacingCharacters(in: range, with:"")
+ .replacingCharacters(in: range, with: "")
.replacingOccurrences(of: "\"", with: "")
.trimmingCharacters(in: .whitespacesAndNewlines)
}
@@ -282,7 +281,7 @@ private var urlSessionStore = SynchronizedDictionary()
}
- fileprivate func getPath(from url : URL) throws -> String {
+ fileprivate func getPath(from url: URL) throws -> String {
guard var path = URLComponents(url: url, resolvingAgainstBaseURL: true)?.path else {
throw DownloadException.requestMissingPath
@@ -296,7 +295,7 @@ private var urlSessionStore = SynchronizedDictionary()
}
- fileprivate func getURL(from urlRequest : URLRequest) throws -> URL {
+ fileprivate func getURL(from urlRequest: URLRequest) throws -> URL {
guard let url = urlRequest.url else {
throw DownloadException.requestMissingURL
@@ -307,62 +306,62 @@ private var urlSessionStore = SynchronizedDictionary()
}
-{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class URLSessionDecodableRequestBuilder: URLSessionRequestBuilder {
+{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}open{{/nonPublicApi}} class URLSessionDecodableRequestBuilder: URLSessionRequestBuilder {
override fileprivate func processRequestResponse(urlRequest: URLRequest, data: Data?, response: URLResponse?, error: Error?, completion: @escaping (_ result: Swift.Result, Error>) -> Void) {
if let error = error {
- completion(.failure(ErrorResponse.error(-1, data, error)))
+ completion(.failure(ErrorResponse.error(-1, data, response, error)))
return
}
guard let httpResponse = response as? HTTPURLResponse else {
- completion(.failure(ErrorResponse.error(-2, data, DecodableRequestBuilderError.nilHTTPResponse)))
+ completion(.failure(ErrorResponse.error(-2, data, response, DecodableRequestBuilderError.nilHTTPResponse)))
return
}
guard httpResponse.isStatusCodeSuccessful else {
- completion(.failure(ErrorResponse.error(httpResponse.statusCode, data, DecodableRequestBuilderError.unsuccessfulHTTPStatusCode)))
+ completion(.failure(ErrorResponse.error(httpResponse.statusCode, data, response, DecodableRequestBuilderError.unsuccessfulHTTPStatusCode)))
return
}
switch T.self {
case is String.Type:
-
+
let body = data.flatMap { String(data: $0, encoding: .utf8) } ?? ""
-
+
completion(.success(Response(response: httpResponse, body: body as? T)))
-
+
case is Void.Type:
-
+
completion(.success(Response(response: httpResponse, body: nil)))
-
+
case is Data.Type:
-
+
completion(.success(Response(response: httpResponse, body: data as? T)))
-
+
default:
-
+
guard let data = data, !data.isEmpty else {
- completion(.failure(ErrorResponse.error(httpResponse.statusCode, nil, DecodableRequestBuilderError.emptyDataResponse)))
+ completion(.failure(ErrorResponse.error(httpResponse.statusCode, nil, response, DecodableRequestBuilderError.emptyDataResponse)))
return
}
-
+
let decodeResult = CodableHelper.decode(T.self, from: data)
-
+
switch decodeResult {
case let .success(decodableObj):
completion(.success(Response(response: httpResponse, body: decodableObj)))
case let .failure(error):
- completion(.failure(ErrorResponse.error(httpResponse.statusCode, data, error)))
+ completion(.failure(ErrorResponse.error(httpResponse.statusCode, data, response, error)))
}
}
}
}
-fileprivate class SessionDelegate: NSObject, URLSessionDelegate, URLSessionDataDelegate {
-
+private class SessionDelegate: NSObject, URLSessionDelegate, URLSessionDataDelegate {
+
var credential: URLCredential?
-
+
var taskDidReceiveChallenge: ((URLSession, URLSessionTask, URLAuthenticationChallenge) -> (URLSession.AuthChallengeDisposition, URLCredential?))?
func urlSession(_ session: URLSession, task: URLSessionTask, didReceive challenge: URLAuthenticationChallenge, completionHandler: @escaping (URLSession.AuthChallengeDisposition, URLCredential?) -> Void) {
@@ -405,13 +404,13 @@ fileprivate class SessionDelegate: NSObject, URLSessionDelegate, URLSessionDataD
func encode(_ urlRequest: URLRequest, with parameters: [String: Any]?) throws -> URLRequest
}
-fileprivate class URLEncoding: ParameterEncoding {
- func encode(_ urlRequest: URLRequest, with parameters: [String : Any]?) throws -> URLRequest {
-
+private class URLEncoding: ParameterEncoding {
+ func encode(_ urlRequest: URLRequest, with parameters: [String: Any]?) throws -> URLRequest {
+
var urlRequest = urlRequest
-
+
guard let parameters = parameters else { return urlRequest }
-
+
guard let url = urlRequest.url else {
throw DownloadException.requestMissingURL
}
@@ -420,12 +419,12 @@ fileprivate class URLEncoding: ParameterEncoding {
urlComponents.queryItems = APIHelper.mapValuesToQueryItems(parameters)
urlRequest.url = urlComponents.url
}
-
+
return urlRequest
}
}
-fileprivate class FileUploadEncoding: ParameterEncoding {
+private class FormDataEncoding: ParameterEncoding {
let contentTypeForFormPart: (_ fileURL: URL) -> String?
@@ -436,13 +435,13 @@ fileprivate class FileUploadEncoding: ParameterEncoding {
func encode(_ urlRequest: URLRequest, with parameters: [String: Any]?) throws -> URLRequest {
var urlRequest = urlRequest
-
+
guard let parameters = parameters, !parameters.isEmpty else {
return urlRequest
}
-
+
let boundary = "Boundary-\(UUID().uuidString)"
-
+
urlRequest.setValue("multipart/form-data; boundary=\(boundary)", forHTTPHeaderField: "Content-Type")
for (key, value) in parameters {
@@ -482,7 +481,7 @@ fileprivate class FileUploadEncoding: ParameterEncoding {
fatalError("Unprocessable value \(value) with key \(key)")
}
}
-
+
var body = urlRequest.httpBody.orEmpty
body.append("\r\n--\(boundary)--\r\n")
@@ -497,7 +496,7 @@ fileprivate class FileUploadEncoding: ParameterEncoding {
var urlRequest = urlRequest
var body = urlRequest.httpBody.orEmpty
-
+
let fileData = try Data(contentsOf: fileURL)
let mimetype = self.contentTypeForFormPart(fileURL) ?? mimeType(for: fileURL)
@@ -505,7 +504,7 @@ fileprivate class FileUploadEncoding: ParameterEncoding {
let fileName = fileURL.lastPathComponent
// If we already added something then we need an additional newline.
- if (body.count > 0) {
+ if body.count > 0 {
body.append("\r\n")
}
@@ -521,20 +520,20 @@ fileprivate class FileUploadEncoding: ParameterEncoding {
// The value data.
body.append(fileData)
-
+
urlRequest.httpBody = body
return urlRequest
}
-
+
private func configureDataUploadRequest(urlRequest: URLRequest, boundary: String, name: String, data: Data) -> URLRequest {
var urlRequest = urlRequest
-
+
var body = urlRequest.httpBody.orEmpty
// If we already added something then we need an additional newline.
- if (body.count > 0) {
+ if body.count > 0 {
body.append("\r\n")
}
@@ -569,7 +568,25 @@ fileprivate class FileUploadEncoding: ParameterEncoding {
}
-fileprivate extension Data {
+private class FormURLEncoding: ParameterEncoding {
+ func encode(_ urlRequest: URLRequest, with parameters: [String: Any]?) throws -> URLRequest {
+
+ var urlRequest = urlRequest
+
+ var requestBodyComponents = URLComponents()
+ requestBodyComponents.queryItems = APIHelper.mapValuesToQueryItems(parameters ?? [:])
+
+ if urlRequest.value(forHTTPHeaderField: "Content-Type") == nil {
+ urlRequest.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
+ }
+
+ urlRequest.httpBody = requestBodyComponents.query?.data(using: .utf8)
+
+ return urlRequest
+ }
+}
+
+private extension Data {
/// Append string to Data
///
/// Rather than littering my code with calls to `dataUsingEncoding` to convert strings to Data, and then add that data to the Data, this wraps it in a nice convenient little extension to Data. This converts using UTF-8.
@@ -583,7 +600,7 @@ fileprivate extension Data {
}
}
-fileprivate extension Optional where Wrapped == Data {
+private extension Optional where Wrapped == Data {
var orEmpty: Data {
self ?? Data()
}
diff --git a/modules/openapi-generator/src/main/resources/swift5/model.mustache b/modules/openapi-generator/src/main/resources/swift5/model.mustache
index 15befbc24b8..a5870644a60 100644
--- a/modules/openapi-generator/src/main/resources/swift5/model.mustache
+++ b/modules/openapi-generator/src/main/resources/swift5/model.mustache
@@ -6,22 +6,10 @@
//
import Foundation
-
{{#description}}
-/** {{description}} */{{/description}}
-{{#isDeprecated}}
-@available(*, deprecated, message: "This schema is deprecated.")
-{{/isDeprecated}}
-{{#isArray}}
-{{> modelArray}}
-{{/isArray}}
-{{^isArray}}
-{{#isEnum}}
-{{> modelEnum}}
-{{/isEnum}}
-{{^isEnum}}
-{{> modelObject}}
-{{/isEnum}}
-{{/isArray}}
-{{/model}}
-{{/models}}
+
+/** {{description}} */{{/description}}{{#isDeprecated}}
+@available(*, deprecated, message: "This schema is deprecated."){{/isDeprecated}}{{#isArray}}
+{{> modelArray}}{{/isArray}}{{^isArray}}{{#isEnum}}
+{{> modelEnum}}{{/isEnum}}{{^isEnum}}
+{{> modelObject}}{{/isEnum}}{{/isArray}}{{/model}}{{/models}}
diff --git a/modules/openapi-generator/src/main/resources/swift5/modelObject.mustache b/modules/openapi-generator/src/main/resources/swift5/modelObject.mustache
index beb0f0be344..36d1eb6e78b 100644
--- a/modules/openapi-generator/src/main/resources/swift5/modelObject.mustache
+++ b/modules/openapi-generator/src/main/resources/swift5/modelObject.mustache
@@ -1,8 +1,5 @@
-{{^objcCompatible}}
-{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct {{classname}}: Codable{{#vendorExtensions.x-swift-hashable}}, Hashable{{/vendorExtensions.x-swift-hashable}} {
-{{/objcCompatible}}
-{{#objcCompatible}}
-@objc {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} class {{classname}}: NSObject, Codable{{#vendorExtensions.x-swift-hashable}}, Hashable{{/vendorExtensions.x-swift-hashable}} {
+{{^objcCompatible}}{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} struct {{classname}}: Codable{{#vendorExtensions.x-swift-hashable}}, Hashable{{/vendorExtensions.x-swift-hashable}} {
+{{/objcCompatible}}{{#objcCompatible}}@objc {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} class {{classname}}: NSObject, Codable{{#vendorExtensions.x-swift-hashable}}, Hashable{{/vendorExtensions.x-swift-hashable}} {
{{/objcCompatible}}
{{#allVars}}
@@ -13,14 +10,12 @@
{{#allVars}}
{{#isEnum}}
{{#description}}/** {{description}} */
- {{/description}}{{#deprecated}}
- @available(*, deprecated, message: "This property is deprecated.")
+ {{/description}}{{#deprecated}}@available(*, deprecated, message: "This property is deprecated.")
{{/deprecated}}{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} {{#readonlyProperties}}private(set) {{/readonlyProperties}}var {{name}}: {{{datatypeWithEnum}}}{{#required}}{{#isNullable}}?{{/isNullable}}{{/required}}{{^required}}?{{/required}}{{#defaultValue}} = {{{defaultValue}}}{{/defaultValue}}
{{/isEnum}}
{{^isEnum}}
{{#description}}/** {{description}} */
- {{/description}}{{#deprecated}}
- @available(*, deprecated, message: "This property is deprecated.")
+ {{/description}}{{#deprecated}}@available(*, deprecated, message: "This property is deprecated.")
{{/deprecated}}{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} {{#readonlyProperties}}private(set) {{/readonlyProperties}}var {{name}}: {{{datatype}}}{{#required}}{{#isNullable}}?{{/isNullable}}{{/required}}{{^required}}?{{/required}}{{#defaultValue}} = {{{defaultValue}}}{{/defaultValue}}
{{#objcCompatible}}
{{#vendorExtensions.x-swift-optional-scalar}}
@@ -33,8 +28,8 @@
{{/objcCompatible}}
{{/isEnum}}
{{/allVars}}
-
{{#hasVars}}
+
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} init({{#allVars}}{{name}}: {{{datatypeWithEnum}}}{{#required}}{{#isNullable}}?{{/isNullable}}{{/required}}{{^required}}?{{/required}}{{#defaultValue}} = {{{defaultValue}}}{{/defaultValue}}{{^defaultValue}}{{^required}} = nil{{/required}}{{/defaultValue}}{{^-last}}, {{/-last}}{{/allVars}}) {
{{#allVars}}
self.{{name}} = {{name}}
@@ -42,7 +37,7 @@
}
{{/hasVars}}
{{#additionalPropertiesType}}
- {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} {{#readonlyProperties}}private(set) {{/readonlyProperties}}var additionalProperties: [String:{{{additionalPropertiesType}}}] = [:]
+ {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} {{#readonlyProperties}}private(set) {{/readonlyProperties}}var additionalProperties: [String: {{{additionalPropertiesType}}}] = [:]
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} subscript(key: String) -> {{{additionalPropertiesType}}}? {
get {
@@ -83,11 +78,12 @@
{{/allVars}}
additionalProperties = try container.decodeMap({{{additionalPropertiesType}}}.self, excludedKeys: nonAdditionalPropertyKeys)
}
-
{{/additionalPropertiesType}}
{{^additionalPropertiesType}}{{#vendorExtensions.x-codegen-has-escaped-property-names}}
- {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum CodingKeys: String, CodingKey, CaseIterable { {{#allVars}}
- case {{name}}{{#vendorExtensions.x-codegen-escaped-property-name}} = "{{{baseName}}}"{{/vendorExtensions.x-codegen-escaped-property-name}}{{/allVars}}
+ {{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} enum CodingKeys: String, CodingKey, CaseIterable {
+ {{#allVars}}
+ case {{name}}{{#vendorExtensions.x-codegen-escaped-property-name}} = "{{{baseName}}}"{{/vendorExtensions.x-codegen-escaped-property-name}}
+ {{/allVars}}
}
{{/vendorExtensions.x-codegen-has-escaped-property-names}}{{/additionalPropertiesType}}
-}
+}
\ No newline at end of file
diff --git a/modules/openapi-generator/src/main/resources/typescript-angular/api.module.mustache b/modules/openapi-generator/src/main/resources/typescript-angular/api.module.mustache
index 05c32156fe6..428af5e48b8 100644
--- a/modules/openapi-generator/src/main/resources/typescript-angular/api.module.mustache
+++ b/modules/openapi-generator/src/main/resources/typescript-angular/api.module.mustache
@@ -12,9 +12,9 @@ import { {{classname}} } from './{{importPath}}';
imports: [],
declarations: [],
exports: [],
- providers: [{{^providedInRoot}}
+ providers: [{{#isProvidedInNone}}
{{#apiInfo}}{{#apis}}{{classname}}{{^-last}},
- {{/-last}}{{/apis}}{{/apiInfo}} {{/providedInRoot}}]
+ {{/-last}}{{/apis}}{{/apiInfo}} {{/isProvidedInNone}}]
})
export class {{apiModuleClassName}} {
public static forRoot(configurationFactory: () => {{configurationClassName}}): ModuleWithProviders{{#enforceGenericModuleWithProviders}}<{{apiModuleClassName}}>{{/enforceGenericModuleWithProviders}} {
diff --git a/modules/openapi-generator/src/main/resources/typescript-angular/api.service.mustache b/modules/openapi-generator/src/main/resources/typescript-angular/api.service.mustache
index 8adae895867..718a8b88549 100644
--- a/modules/openapi-generator/src/main/resources/typescript-angular/api.service.mustache
+++ b/modules/openapi-generator/src/main/resources/typescript-angular/api.service.mustache
@@ -43,14 +43,14 @@ export interface {{#prefixParameterInterfaces}}{{classname}}{{/prefixParameterIn
* {{&description}}
*/
{{/description}}
-{{^providedInRoot}}
+{{#isProvidedInNone}}
@Injectable()
-{{/providedInRoot}}
-{{#providedInRoot}}
+{{/isProvidedInNone}}
+{{^isProvidedInNone}}
@Injectable({
- providedIn: 'root'
+ providedIn: '{{providedIn}}'
})
-{{/providedInRoot}}
+{{/isProvidedInNone}}
{{#withInterfaces}}
export class {{classname}} implements {{classname}}Interface {
{{/withInterfaces}}
diff --git a/modules/openapi-generator/src/main/resources/typescript-fetch/runtime.mustache b/modules/openapi-generator/src/main/resources/typescript-fetch/runtime.mustache
index 767a3488080..04001417a82 100644
--- a/modules/openapi-generator/src/main/resources/typescript-fetch/runtime.mustache
+++ b/modules/openapi-generator/src/main/resources/typescript-fetch/runtime.mustache
@@ -172,7 +172,7 @@ export class Configuration {
return undefined;
}
- get headers(): HTTPHeaders | undefined {
+ get headers(): HTTPHeaders | undefined {
return this.configuration.headers;
}
diff --git a/modules/openapi-generator/src/main/resources/typescript/index.mustache b/modules/openapi-generator/src/main/resources/typescript/index.mustache
index 3e4d0907d0e..b19f7f9348a 100644
--- a/modules/openapi-generator/src/main/resources/typescript/index.mustache
+++ b/modules/openapi-generator/src/main/resources/typescript/index.mustache
@@ -1,7 +1,8 @@
export * from "./http/http{{extensionForDeno}}";
export * from "./auth/auth{{extensionForDeno}}";
export * from "./models/all{{extensionForDeno}}";
-export { createConfiguration, Configuration } from "./configuration{{extensionForDeno}}"
+export { createConfiguration } from "./configuration{{extensionForDeno}}"
+export{{#platforms}}{{#deno}} type{{/deno}}{{/platforms}} { Configuration } from "./configuration{{extensionForDeno}}"
export * from "./apis/exception{{extensionForDeno}}";
export * from "./servers{{extensionForDeno}}";
@@ -9,7 +10,7 @@ export * from "./servers{{extensionForDeno}}";
export { Middleware } from './middleware{{extensionForDeno}}';
{{/useRxJS}}
{{^useRxJS}}
-export { PromiseMiddleware as Middleware } from './middleware{{extensionForDeno}}';
+export{{#platforms}}{{#deno}} type{{/deno}}{{/platforms}} { PromiseMiddleware as Middleware } from './middleware{{extensionForDeno}}';
{{/useRxJS}}
{{#useObjectParameters}}
export { {{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}{{classname}}{{operationIdCamelCase}}Request, {{/operation}}Object{{classname}} as {{classname}}{{^-last}}, {{/-last}} {{/operations}}{{/apis}}{{/apiInfo}}} from './types/ObjectParamAPI{{extensionForDeno}}';
diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/confluencewiki/ConfluenceWikiTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/confluencewiki/ConfluenceWikiTest.java
new file mode 100644
index 00000000000..1711eee45bd
--- /dev/null
+++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/confluencewiki/ConfluenceWikiTest.java
@@ -0,0 +1,198 @@
+/*
+ * 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.confluencewiki;
+
+import io.swagger.v3.oas.models.Components;
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.media.*;
+import org.apache.commons.lang3.StringUtils;
+import org.openapitools.codegen.CodegenModel;
+import org.openapitools.codegen.CodegenProperty;
+import org.openapitools.codegen.DefaultCodegen;
+import org.openapitools.codegen.TestUtils;
+import org.openapitools.codegen.languages.ConfluenceWikiCodegen;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.Map;
+
+public class ConfluenceWikiTest {
+
+ @Test(description = "convert a model with an enum")
+ public void converterTest() {
+ final StringSchema enumSchema = new StringSchema();
+ enumSchema.setEnum(Arrays.asList("VALUE1", "VALUE2", "VALUE3"));
+ final Schema model = new Schema().type("object").addProperties("name", enumSchema);
+
+ final ConfluenceWikiCodegen codegen = new ConfluenceWikiCodegen();
+ OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("sample", model);
+ codegen.setOpenAPI(openAPI);
+ final CodegenModel cm = codegen.fromModel("sample", model);
+
+ Assert.assertEquals(cm.vars.size(), 1);
+
+ final CodegenProperty enumVar = cm.vars.get(0);
+ Assert.assertEquals(enumVar.baseName, "name");
+ Assert.assertEquals(enumVar.dataType, "String");
+ Assert.assertEquals(enumVar.datatypeWithEnum, "NameEnum");
+ Assert.assertEquals(enumVar.name, "name");
+ Assert.assertEquals(enumVar.defaultValue, "null");
+ Assert.assertEquals(enumVar.baseType, "string");
+ Assert.assertTrue(enumVar.isEnum);
+ }
+
+ @Test(description = "convert a model with an enum inside a list")
+ public void converterInArrayTest() {
+ final ArraySchema enumSchema = new ArraySchema().items(
+ new StringSchema().addEnumItem("Aaaa").addEnumItem("Bbbb"));
+ final Schema model = new Schema().type("object").addProperties("name", enumSchema);
+
+ final DefaultCodegen codegen = new ConfluenceWikiCodegen();
+ OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("sample", model);
+ codegen.setOpenAPI(openAPI);
+ final CodegenModel cm = codegen.fromModel("sample", model);
+
+ Assert.assertEquals(cm.vars.size(), 1);
+
+ final CodegenProperty enumVar = cm.vars.get(0);
+ Assert.assertEquals(enumVar.baseName, "name");
+ Assert.assertEquals(enumVar.dataType, "array[String]");
+ Assert.assertEquals(enumVar.datatypeWithEnum, "array[String]");
+ Assert.assertEquals(enumVar.name, "name");
+ Assert.assertEquals(enumVar.defaultValue, "null");
+ Assert.assertEquals(enumVar.baseType, "array");
+ Assert.assertTrue(enumVar.isEnum);
+
+ Assert.assertEquals(enumVar.mostInnerItems.baseName, "name");
+ Assert.assertEquals(enumVar.mostInnerItems.dataType, "String");
+ Assert.assertEquals(enumVar.mostInnerItems.datatypeWithEnum, "NameEnum");
+ Assert.assertEquals(enumVar.mostInnerItems.name, "name");
+ Assert.assertEquals(enumVar.mostInnerItems.defaultValue, "null");
+ Assert.assertEquals(enumVar.mostInnerItems.baseType, "string");
+
+ Assert.assertEquals(enumVar.mostInnerItems.baseType, enumVar.items.baseType);
+ }
+
+ @Test(description = "convert a model with an enum inside a list")
+ public void converterInArrayInArrayTest() {
+ final ArraySchema enumSchema = new ArraySchema().items(
+ new ArraySchema().items(
+ new StringSchema().addEnumItem("Aaaa").addEnumItem("Bbbb")));
+ final Schema model = new Schema().type("object").addProperties("name", enumSchema);
+
+ final DefaultCodegen codegen = new ConfluenceWikiCodegen();
+ OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("sample", model);
+ codegen.setOpenAPI(openAPI);
+ final CodegenModel cm = codegen.fromModel("sample", model);
+
+ Assert.assertEquals(cm.vars.size(), 1);
+
+ final CodegenProperty enumVar = cm.vars.get(0);
+ Assert.assertEquals(enumVar.baseName, "name");
+ Assert.assertEquals(enumVar.dataType, "array[array[String]]");
+ Assert.assertEquals(enumVar.datatypeWithEnum, "array[array[String]]");
+ Assert.assertEquals(enumVar.name, "name");
+ Assert.assertEquals(enumVar.defaultValue, "null");
+ Assert.assertEquals(enumVar.baseType, "array");
+ Assert.assertTrue(enumVar.isEnum);
+
+ Assert.assertEquals(enumVar.mostInnerItems.baseName, "name");
+ Assert.assertEquals(enumVar.mostInnerItems.dataType, "String");
+ Assert.assertEquals(enumVar.mostInnerItems.datatypeWithEnum, "NameEnum");
+ Assert.assertEquals(enumVar.mostInnerItems.name, "name");
+ Assert.assertEquals(enumVar.mostInnerItems.defaultValue, "null");
+ Assert.assertEquals(enumVar.mostInnerItems.baseType, "string");
+
+ Assert.assertEquals(enumVar.mostInnerItems.baseType, enumVar.items.items.baseType);
+ }
+
+ @Test(description = "not override identical parent enums")
+ public void overrideEnumTest() {
+ final StringSchema identicalEnumProperty = new StringSchema();
+ identicalEnumProperty.setEnum(Arrays.asList("VALUE1", "VALUE2", "VALUE3"));
+
+ final StringSchema subEnumProperty = new StringSchema();
+ subEnumProperty.setEnum(Arrays.asList("SUB1", "SUB2", "SUB3"));
+
+ // Add one enum property to the parent
+ final Map parentProperties = new HashMap<>();
+ parentProperties.put("sharedThing", identicalEnumProperty);
+
+ // Add TWO enums to the subType model; one of which is identical to the one in parent class
+ final Map subProperties = new HashMap<>();
+ subProperties.put("unsharedThing", subEnumProperty);
+
+ final Schema parentModel = new Schema();
+ parentModel.setProperties(parentProperties);
+ parentModel.name("parentModel");
+
+ Discriminator discriminator = new Discriminator().mapping("name", StringUtils.EMPTY);
+ discriminator.setPropertyName("model_type");
+ parentModel.setDiscriminator(discriminator);
+
+ final ComposedSchema composedSchema = new ComposedSchema()
+ .addAllOfItem(new Schema().$ref(parentModel.getName()));
+ composedSchema.setName("sample");
+
+ final ConfluenceWikiCodegen codegen = new ConfluenceWikiCodegen();
+ OpenAPI openAPI = TestUtils.createOpenAPI();
+ openAPI.setComponents(new Components()
+ .addSchemas(parentModel.getName(), parentModel)
+ .addSchemas(composedSchema.getName(), composedSchema)
+ );
+
+ codegen.setOpenAPI(openAPI);
+ final CodegenModel cm = codegen.fromModel("sample", composedSchema);
+
+ Assert.assertEquals(cm.name, "sample");
+ Assert.assertEquals(cm.classname, "Sample");
+ Assert.assertEquals(cm.parent, "ParentModel");
+ Assert.assertTrue(cm.imports.contains("ParentModel"));
+ }
+
+ @Test
+ public void testEnumTestSchema() {
+ final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/petstore-with-fake-endpoints-models-for-testing.yaml");
+ ConfluenceWikiCodegen codegen = new ConfluenceWikiCodegen();
+ codegen.setOpenAPI(openAPI);
+
+ Schema enumTest = openAPI.getComponents().getSchemas().get("Enum_Test");
+ Assert.assertNotNull(enumTest);
+ CodegenModel cm = codegen.fromModel("Enum_Test", enumTest);
+
+ Assert.assertEquals(cm.getVars().size(), 8);
+ CodegenProperty cp0 = cm.getVars().get(0);
+ Assert.assertEquals(cp0.dataType, "String");
+ CodegenProperty cp1 = cm.getVars().get(1);
+ Assert.assertEquals(cp1.dataType, "String");
+ CodegenProperty cp2 = cm.getVars().get(2);
+ Assert.assertEquals(cp2.dataType, "Integer");
+ CodegenProperty cp3 = cm.getVars().get(3);
+ Assert.assertEquals(cp3.dataType, "Double");
+ CodegenProperty cp4 = cm.getVars().get(4);
+ Assert.assertEquals(cp4.dataType, "OuterEnum");
+ CodegenProperty cp5 = cm.getVars().get(5);
+ Assert.assertEquals(cp5.dataType, "OuterEnumInteger");
+ CodegenProperty cp6 = cm.getVars().get(6);
+ Assert.assertEquals(cp6.dataType, "OuterEnumDefaultValue");
+ CodegenProperty cp7 = cm.getVars().get(7);
+ Assert.assertEquals(cp7.dataType, "OuterEnumIntegerDefaultValue");
+ }
+}
diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/csharpnetcore/CSharpNetCoreClientCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/csharpnetcore/CSharpNetCoreClientCodegenTest.java
new file mode 100644
index 00000000000..0d28f8f255a
--- /dev/null
+++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/csharpnetcore/CSharpNetCoreClientCodegenTest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2020 OpenAPI-Generator Contributors (https://openapi-generator.tech)
+ *
+ * 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.csharp;
+
+import org.openapitools.codegen.languages.CSharpNetCoreClientCodegen;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+public class CSharpNetCoreClientCodegenTest {
+
+ @Test
+ public void testToEnumVarName() throws Exception {
+ final CSharpNetCoreClientCodegen codegen = new CSharpNetCoreClientCodegen();
+ codegen.processOpts();
+
+ Assert.assertEquals(codegen.toEnumVarName("FooBar", "string"), "FooBar");
+ Assert.assertEquals(codegen.toEnumVarName("fooBar", "string"), "FooBar");
+ Assert.assertEquals(codegen.toEnumVarName("foo-bar", "string"), "FooBar");
+ Assert.assertEquals(codegen.toEnumVarName("foo_bar", "string"), "FooBar");
+ Assert.assertEquals(codegen.toEnumVarName("foo bar", "string"), "FooBar");
+
+ // The below cases do not work currently, camelize doesn't support uppercase
+ // Assert.assertEquals(codegen.toEnumVarName("FOO-BAR", "string"), "FooBar");
+ // Assert.assertEquals(codegen.toEnumVarName("FOO_BAR", "string"), "FooBar");
+ }
+}
diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/dart/DartModelTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/dart/DartModelTest.java
index cab086ffaf1..c8438e80a0f 100644
--- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/dart/DartModelTest.java
+++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/dart/DartModelTest.java
@@ -22,6 +22,7 @@ import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.media.*;
import org.openapitools.codegen.*;
import org.openapitools.codegen.languages.DartClientCodegen;
+import org.openapitools.codegen.languages.DartDioClientCodegen;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
@@ -521,4 +522,29 @@ public class DartModelTest {
Assert.assertEquals(op.returnType, "DateTime");
Assert.assertEquals(op.bodyParam.dataType, "DateTime");
}
+
+ @Test(description = "correctly generate date/datetime default values, currently null")
+ public void dateDefaultValues() {
+ final DateSchema date = new DateSchema();
+ date.setDefault("2021-01-01");
+ final DateTimeSchema dateTime = new DateTimeSchema();
+ dateTime.setDefault("2021-01-01T14:00:00Z");
+ final Schema model = new Schema()
+ .description("a sample model")
+ .addProperties("date", date)
+ .addProperties("dateTime", dateTime)
+ .addProperties("mapNoDefault", new MapSchema());
+ final DefaultCodegen codegen = new DartDioClientCodegen();
+ OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("sample", model);
+ codegen.setOpenAPI(openAPI);
+ final CodegenModel cm = codegen.fromModel("sample", model);
+
+ final CodegenProperty dateDefault = cm.vars.get(0);
+ Assert.assertEquals(dateDefault.name, "date");
+ Assert.assertNull(dateDefault.defaultValue);
+
+ final CodegenProperty dateTimeDefault = cm.vars.get(1);
+ Assert.assertEquals(dateTimeDefault.name, "dateTime");
+ Assert.assertNull(dateTimeDefault.defaultValue);
+ }
}
diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/dartdio/DartDioModelTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/dartdio/DartDioModelTest.java
index ade2bff57c0..3faa4d477a0 100644
--- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/dartdio/DartDioModelTest.java
+++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/dartdio/DartDioModelTest.java
@@ -391,4 +391,56 @@ public class DartDioModelTest {
Assert.assertEquals(cm.name, name);
Assert.assertEquals(cm.classname, expectedName);
}
+
+ @Test(description = "correctly generate collection default values")
+ public void collectionDefaultValues() {
+ final ArraySchema array = new ArraySchema();
+ array.setDefault("[]");
+ final Schema model = new Schema()
+ .description("a sample model")
+ .addProperties("arrayNoDefault", new ArraySchema())
+ .addProperties("arrayEmptyDefault", array)
+ .addProperties("mapNoDefault", new MapSchema());
+ final DefaultCodegen codegen = new DartDioClientCodegen();
+ OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("sample", model);
+ codegen.setOpenAPI(openAPI);
+ final CodegenModel cm = codegen.fromModel("sample", model);
+
+ final CodegenProperty arrayNoDefault = cm.vars.get(0);
+ Assert.assertEquals(arrayNoDefault.name, "arrayNoDefault");
+ Assert.assertNull(arrayNoDefault.defaultValue);
+
+ final CodegenProperty arrayEmptyDefault = cm.vars.get(1);
+ Assert.assertEquals(arrayEmptyDefault.name, "arrayEmptyDefault");
+ Assert.assertEquals(arrayEmptyDefault.defaultValue, "ListBuilder()");
+
+ final CodegenProperty mapNoDefault = cm.vars.get(2);
+ Assert.assertEquals(mapNoDefault.name, "mapNoDefault");
+ Assert.assertNull(mapNoDefault.defaultValue);
+ }
+
+ @Test(description = "correctly generate date/datetime default values, currently null")
+ public void dateDefaultValues() {
+ final DateSchema date = new DateSchema();
+ date.setDefault("2021-01-01");
+ final DateTimeSchema dateTime = new DateTimeSchema();
+ dateTime.setDefault("2021-01-01T14:00:00Z");
+ final Schema model = new Schema()
+ .description("a sample model")
+ .addProperties("date", date)
+ .addProperties("dateTime", dateTime)
+ .addProperties("mapNoDefault", new MapSchema());
+ final DefaultCodegen codegen = new DartDioClientCodegen();
+ OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("sample", model);
+ codegen.setOpenAPI(openAPI);
+ final CodegenModel cm = codegen.fromModel("sample", model);
+
+ final CodegenProperty dateDefault = cm.vars.get(0);
+ Assert.assertEquals(dateDefault.name, "date");
+ Assert.assertNull(dateDefault.defaultValue);
+
+ final CodegenProperty dateTimeDefault = cm.vars.get(1);
+ Assert.assertEquals(dateTimeDefault.name, "dateTime");
+ Assert.assertNull(dateTimeDefault.defaultValue);
+ }
}
diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/ktorm/KtormSchemaCodegenTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/ktorm/KtormSchemaCodegenTest.java
new file mode 100644
index 00000000000..6accc27c9ce
--- /dev/null
+++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/ktorm/KtormSchemaCodegenTest.java
@@ -0,0 +1,316 @@
+/*
+ * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech)
+ *
+ * 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.ktorm;
+
+import org.openapitools.codegen.CodegenModel;
+import org.openapitools.codegen.CodegenProperty;
+import org.openapitools.codegen.DefaultCodegen;
+import org.openapitools.codegen.TestUtils;
+import org.openapitools.codegen.languages.KtormSchemaCodegen;
+import org.testng.Assert;
+import org.testng.annotations.Test;
+
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.media.*;
+import io.swagger.v3.parser.util.SchemaTypeUtil;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+public class KtormSchemaCodegenTest {
+
+ private Map toObjs(CodegenModel cm) {
+ Map objs = new HashMap();
+ List models = new ArrayList();
+ Map model = new HashMap<>();
+ model.put("model", cm);
+ models.add(model);
+ objs.put("models", models);
+ return objs;
+ }
+
+ private CodegenModel getModel(Schema schema, String pkName, Boolean surrogateKey) {
+ final KtormSchemaCodegen codegen = new KtormSchemaCodegen();
+ OpenAPI openAPI = TestUtils.createOpenAPIWithOneSchema("sample", schema);
+ codegen.setAddSurrogateKey(surrogateKey);
+ codegen.setPrimaryKeyConvention(pkName);
+ codegen.setOpenAPI(openAPI);
+ CodegenModel cm = codegen.fromModel("sample", schema);
+ codegen.postProcessModels(toObjs(cm));
+ return cm;
+ }
+
+ private Map getExtension(CodegenProperty property) {
+ return (Map)
+ property.vendorExtensions.get(KtormSchemaCodegen.VENDOR_EXTENSION_SCHEMA);
+ }
+
+ private Map getColumnDefinition(Map schema) {
+ return (Map)
+ schema.get("columnDefinition");
+ }
+
+ private Map getRelationDefinition(Map schema) {
+ return (Map)
+ schema.get("relationDefinition");
+ }
+
+ private Map getKtormSchema(Schema propertySchema) {
+ final Schema schema = new Schema()
+ .description("a sample model")
+ .addProperties("key", propertySchema)
+ .addRequiredItem("key");
+ final CodegenModel cm = getModel(schema, "id", false);
+ final CodegenProperty prop = cm.vars.get(0);
+ return getExtension(prop);
+ }
+
+ private String getMatchedColType(Schema propertySchema) {
+ Map ktormSchema = getColumnDefinition(getKtormSchema(propertySchema));
+ return (String) ktormSchema.get("colType");
+ }
+
+ private String getMatchedKotlinType(Schema propertySchema) {
+ Map ktormSchema = getColumnDefinition(getKtormSchema(propertySchema));
+ return (String) ktormSchema.get("colKotlinType");
+ }
+
+ private String getMatchedRelation(Schema propertySchema) {
+ Map ktormSchema = getRelationDefinition(getKtormSchema(propertySchema));
+ if (ktormSchema == null) return null;
+ return (String) ktormSchema.get("fkName");
+ }
+
+ @Test
+ public void testMatchedColType() {
+ Assert.assertEquals(getMatchedColType(new StringSchema()), "text");
+ Assert.assertEquals(getMatchedColType(new StringSchema().type("char")), "text");
+ Assert.assertEquals(getMatchedColType(new StringSchema().format("char")), "text");
+ Assert.assertEquals(getMatchedColType(new BooleanSchema()), "boolean");
+ Assert.assertEquals(getMatchedColType(new IntegerSchema().type(SchemaTypeUtil.BYTE_FORMAT)), "int");
+ Assert.assertEquals(getMatchedColType(new IntegerSchema().format(SchemaTypeUtil.BYTE_FORMAT)), "int");
+ Assert.assertEquals(getMatchedColType(new IntegerSchema().type("short")), "int");
+ Assert.assertEquals(getMatchedColType(new IntegerSchema().format("short")), "int");
+ Assert.assertEquals(getMatchedColType(new IntegerSchema()), "int");
+ Assert.assertEquals(getMatchedColType(new IntegerSchema().type("integer")), "int");
+ Assert.assertEquals(getMatchedColType(new IntegerSchema().format("integer")), "int");
+ Assert.assertEquals(getMatchedColType(new IntegerSchema().format(SchemaTypeUtil.INTEGER32_FORMAT)), "int");
+ Assert.assertEquals(getMatchedColType(new IntegerSchema().type("long")), "long");
+ Assert.assertEquals(getMatchedColType(new IntegerSchema().format(SchemaTypeUtil.INTEGER64_FORMAT)), "long");
+ Assert.assertEquals(getMatchedColType(new ObjectSchema().type(SchemaTypeUtil.FLOAT_FORMAT)), "float");
+ Assert.assertEquals(getMatchedColType(new NumberSchema().format(SchemaTypeUtil.FLOAT_FORMAT)), "float");
+ Assert.assertEquals(getMatchedColType(new ObjectSchema().type(SchemaTypeUtil.DOUBLE_FORMAT)), "double");
+ Assert.assertEquals(getMatchedColType(new NumberSchema().format(SchemaTypeUtil.DOUBLE_FORMAT)), "double");
+ Assert.assertEquals(getMatchedColType(new ObjectSchema().type(SchemaTypeUtil.FLOAT_FORMAT).format(SchemaTypeUtil.DOUBLE_FORMAT)), "float");
+ Assert.assertEquals(getMatchedColType(new ObjectSchema().type(SchemaTypeUtil.DOUBLE_FORMAT).format(SchemaTypeUtil.FLOAT_FORMAT)), "double");
+ Assert.assertEquals(getMatchedColType(new ObjectSchema().type("real")), "double");
+ Assert.assertEquals(getMatchedColType(new NumberSchema().format("real")), "decimal");
+ Assert.assertEquals(getMatchedColType(new NumberSchema().type(SchemaTypeUtil.NUMBER_TYPE)), "decimal");
+ Assert.assertEquals(getMatchedColType(new NumberSchema().type("decimal")), "decimal");
+ Assert.assertEquals(getMatchedColType(new NumberSchema().type("BigDecimal")), "decimal");
+ Assert.assertEquals(getMatchedColType(new ByteArraySchema()), "blob");
+ Assert.assertEquals(getMatchedColType(new ArraySchema().items(new IntegerSchema().type(SchemaTypeUtil.BYTE_FORMAT))), "blob");
+ Assert.assertEquals(getMatchedColType(new ArraySchema().items(new IntegerSchema().format(SchemaTypeUtil.BYTE_FORMAT))), "blob");
+ Assert.assertEquals(getMatchedColType(new ArraySchema()), "blob");
+ Assert.assertEquals(getMatchedColType(new ObjectSchema().type("list")), "blob");
+ Assert.assertEquals(getMatchedColType(new ObjectSchema().type("set")), "blob");
+ Assert.assertEquals(getMatchedColType(new ObjectSchema().type("map")), "blob");
+ Assert.assertEquals(getMatchedColType(new ObjectSchema()), "blob");
+ Assert.assertEquals(getMatchedColType(new ObjectSchema().type("binary")), "blob");
+ Assert.assertEquals(getMatchedColType(new ObjectSchema().type("AnyType")), "blob");
+ Assert.assertEquals(getMatchedColType(new BinarySchema()), "blob");
+ Assert.assertEquals(getMatchedColType(new FileSchema()), "blob");
+ Assert.assertEquals(getMatchedColType(new DateSchema()), "date");
+ Assert.assertEquals(getMatchedColType(new DateTimeSchema()), "datetime");
+ Assert.assertEquals(getMatchedColType(new UUIDSchema()), "text");
+ Assert.assertEquals(getMatchedColType(new ObjectSchema().type("UUID")), "text");
+ Assert.assertEquals(getMatchedColType(new StringSchema().format("URI")), "text");
+ Assert.assertEquals(getMatchedColType(new ObjectSchema().type("URI")), "text");
+ Assert.assertEquals(getMatchedColType(new StringSchema().format("password")), "text");
+ Assert.assertEquals(getMatchedColType(new StringSchema().type("password")), "text");
+ }
+
+ @Test
+ public void testMatchedColKotlinType() {
+ // *1 - format specifiers aren't used
+ Assert.assertEquals(getMatchedKotlinType(new StringSchema()), "kotlin.String");
+ Assert.assertEquals(getMatchedKotlinType(new StringSchema().type("char")), "kotlin.String");
+ Assert.assertEquals(getMatchedKotlinType(new StringSchema().format("char")), "kotlin.String");
+ Assert.assertEquals(getMatchedKotlinType(new BooleanSchema()), "kotlin.Boolean");
+ Assert.assertEquals(getMatchedKotlinType(new IntegerSchema().type(SchemaTypeUtil.BYTE_FORMAT)), "kotlin.Byte");
+ Assert.assertEquals(getMatchedKotlinType(new IntegerSchema().format(SchemaTypeUtil.BYTE_FORMAT)), "kotlin.Int"); //*1
+ Assert.assertEquals(getMatchedKotlinType(new IntegerSchema().type("short")), "kotlin.Short");
+ Assert.assertEquals(getMatchedKotlinType(new IntegerSchema().format("short")), "kotlin.Int"); //*1
+ Assert.assertEquals(getMatchedKotlinType(new IntegerSchema()), "kotlin.Int");
+ Assert.assertEquals(getMatchedKotlinType(new IntegerSchema().type("integer")), "kotlin.Int");
+ Assert.assertEquals(getMatchedKotlinType(new IntegerSchema().format("integer")), "kotlin.Int"); //*1
+ Assert.assertEquals(getMatchedKotlinType(new IntegerSchema().format(SchemaTypeUtil.INTEGER32_FORMAT)), "kotlin.Int");
+ Assert.assertEquals(getMatchedKotlinType(new IntegerSchema().type("long")), "kotlin.Long");
+ Assert.assertEquals(getMatchedKotlinType(new IntegerSchema().format(SchemaTypeUtil.INTEGER64_FORMAT)), "kotlin.Long");
+ Assert.assertEquals(getMatchedKotlinType(new ObjectSchema().type(SchemaTypeUtil.FLOAT_FORMAT)), "kotlin.Float");
+ Assert.assertEquals(getMatchedKotlinType(new NumberSchema().format(SchemaTypeUtil.FLOAT_FORMAT)), "kotlin.Float");
+ Assert.assertEquals(getMatchedKotlinType(new ObjectSchema().type(SchemaTypeUtil.DOUBLE_FORMAT)), "kotlin.Double");
+ Assert.assertEquals(getMatchedKotlinType(new NumberSchema().format(SchemaTypeUtil.DOUBLE_FORMAT)), "kotlin.Double");
+ Assert.assertEquals(getMatchedKotlinType(new ObjectSchema().type(SchemaTypeUtil.FLOAT_FORMAT).format(SchemaTypeUtil.DOUBLE_FORMAT)), "kotlin.Float"); //*1
+ Assert.assertEquals(getMatchedKotlinType(new ObjectSchema().type(SchemaTypeUtil.DOUBLE_FORMAT).format(SchemaTypeUtil.FLOAT_FORMAT)), "kotlin.Double"); //*1
+ Assert.assertEquals(getMatchedKotlinType(new ObjectSchema().type("real")), "kotlin.Double");
+ Assert.assertEquals(getMatchedKotlinType(new NumberSchema().format("real")), "java.math.BigDecimal"); //*1
+ Assert.assertEquals(getMatchedKotlinType(new NumberSchema().type(SchemaTypeUtil.NUMBER_TYPE)), "java.math.BigDecimal");
+ Assert.assertEquals(getMatchedKotlinType(new NumberSchema().type("decimal")), "java.math.BigDecimal");
+ Assert.assertEquals(getMatchedKotlinType(new NumberSchema().type("BigDecimal")), "java.math.BigDecimal");
+ Assert.assertEquals(getMatchedKotlinType(new ByteArraySchema()), "kotlin.ByteArray");
+ Assert.assertEquals(getMatchedKotlinType(new ArraySchema().items(new IntegerSchema().type(SchemaTypeUtil.BYTE_FORMAT))), "kotlin.Array");
+ Assert.assertEquals(getMatchedKotlinType(new ArraySchema().items(new IntegerSchema().format(SchemaTypeUtil.BYTE_FORMAT))), "kotlin.Array"); //*1
+ Assert.assertEquals(getMatchedKotlinType(new ArraySchema()), "kotlin.Array");
+ Assert.assertEquals(getMatchedKotlinType(new ObjectSchema().type("list")), "kotlin.collections.List");
+ Assert.assertEquals(getMatchedKotlinType(new ObjectSchema().type("set")), "kotlin.collections.Set");
+ Assert.assertEquals(getMatchedKotlinType(new ObjectSchema().type("map")), "kotlin.collections.Map");
+ Assert.assertEquals(getMatchedKotlinType(new ObjectSchema()), "kotlin.Any");
+ Assert.assertEquals(getMatchedKotlinType(new ObjectSchema().type("binary")), "kotlin.ByteArray");
+ Assert.assertEquals(getMatchedKotlinType(new ObjectSchema().type("AnyType")), "kotlin.Any");
+ Assert.assertEquals(getMatchedKotlinType(new BinarySchema()), "java.io.File"); //looks like a bug
+ Assert.assertEquals(getMatchedKotlinType(new FileSchema()), "java.io.File");
+ Assert.assertEquals(getMatchedKotlinType(new DateSchema()), "java.time.LocalDate");
+ Assert.assertEquals(getMatchedKotlinType(new DateTimeSchema()), "java.time.LocalDateTime");
+ Assert.assertEquals(getMatchedKotlinType(new UUIDSchema()), "java.util.UUID");
+ Assert.assertEquals(getMatchedKotlinType(new ObjectSchema().type("UUID")), "java.util.UUID");
+ Assert.assertEquals(getMatchedKotlinType(new StringSchema().format("URI")), "java.net.URI");
+ Assert.assertEquals(getMatchedKotlinType(new ObjectSchema().type("URI")), "java.net.URI");
+ Assert.assertEquals(getMatchedKotlinType(new StringSchema().format("password")), "kotlin.String");
+ Assert.assertEquals(getMatchedKotlinType(new StringSchema().type("password")), "kotlin.String");
+ }
+
+ @Test
+ public void testNonMatchedRelation() {
+ Assert.assertEquals(getMatchedRelation(new StringSchema()), null);
+ Assert.assertEquals(getMatchedRelation(new StringSchema().type("char")), null);
+ Assert.assertEquals(getMatchedRelation(new StringSchema().format("char")), null);
+ Assert.assertEquals(getMatchedRelation(new BooleanSchema()), null);
+ Assert.assertEquals(getMatchedRelation(new IntegerSchema().type(SchemaTypeUtil.BYTE_FORMAT)), null);
+ Assert.assertEquals(getMatchedRelation(new IntegerSchema().format(SchemaTypeUtil.BYTE_FORMAT)), null);
+ Assert.assertEquals(getMatchedRelation(new IntegerSchema().type("short")), null);
+ Assert.assertEquals(getMatchedRelation(new IntegerSchema().format("short")), null);
+ Assert.assertEquals(getMatchedRelation(new IntegerSchema()), null);
+ Assert.assertEquals(getMatchedRelation(new IntegerSchema().type("integer")), null);
+ Assert.assertEquals(getMatchedRelation(new IntegerSchema().format("integer")), null);
+ Assert.assertEquals(getMatchedRelation(new IntegerSchema().format(SchemaTypeUtil.INTEGER32_FORMAT)), null);
+ Assert.assertEquals(getMatchedRelation(new IntegerSchema().type("long")), null);
+ Assert.assertEquals(getMatchedRelation(new IntegerSchema().format(SchemaTypeUtil.INTEGER64_FORMAT)), null);
+ Assert.assertEquals(getMatchedRelation(new ObjectSchema().type(SchemaTypeUtil.FLOAT_FORMAT)), null);
+ Assert.assertEquals(getMatchedRelation(new NumberSchema().format(SchemaTypeUtil.FLOAT_FORMAT)), null);
+ Assert.assertEquals(getMatchedRelation(new ObjectSchema().type(SchemaTypeUtil.DOUBLE_FORMAT)), null);
+ Assert.assertEquals(getMatchedRelation(new NumberSchema().format(SchemaTypeUtil.DOUBLE_FORMAT)), null);
+ Assert.assertEquals(getMatchedRelation(new ObjectSchema().type(SchemaTypeUtil.FLOAT_FORMAT).format(SchemaTypeUtil.DOUBLE_FORMAT)), null);
+ Assert.assertEquals(getMatchedRelation(new ObjectSchema().type(SchemaTypeUtil.DOUBLE_FORMAT).format(SchemaTypeUtil.FLOAT_FORMAT)), null);
+ Assert.assertEquals(getMatchedRelation(new ObjectSchema().type("real")), null);
+ Assert.assertEquals(getMatchedRelation(new NumberSchema().format("real")), null);
+ Assert.assertEquals(getMatchedRelation(new NumberSchema().type(SchemaTypeUtil.NUMBER_TYPE)), null);
+ Assert.assertEquals(getMatchedRelation(new NumberSchema().type("decimal")), null);
+ Assert.assertEquals(getMatchedRelation(new NumberSchema().type("BigDecimal")), null);
+ Assert.assertEquals(getMatchedRelation(new ByteArraySchema()), null);
+ Assert.assertEquals(getMatchedRelation(new ObjectSchema().type("list")), null);
+ Assert.assertEquals(getMatchedRelation(new ObjectSchema().type("set")), null);
+ Assert.assertEquals(getMatchedRelation(new ObjectSchema().type("map")), null);
+ Assert.assertEquals(getMatchedRelation(new ObjectSchema()), null);
+ Assert.assertEquals(getMatchedRelation(new ObjectSchema().type("binary")), null);
+ Assert.assertEquals(getMatchedRelation(new ObjectSchema().type("AnyType")), null);
+ Assert.assertEquals(getMatchedRelation(new BinarySchema()), null);
+ Assert.assertEquals(getMatchedRelation(new FileSchema()), null);
+ Assert.assertEquals(getMatchedRelation(new DateSchema()), null);
+ Assert.assertEquals(getMatchedRelation(new DateTimeSchema()), null);
+ Assert.assertEquals(getMatchedRelation(new UUIDSchema()), null);
+ Assert.assertEquals(getMatchedRelation(new ObjectSchema().type("UUID")), null);
+ Assert.assertEquals(getMatchedRelation(new StringSchema().format("URI")), null);
+ Assert.assertEquals(getMatchedRelation(new ObjectSchema().type("URI")), null);
+ Assert.assertEquals(getMatchedRelation(new StringSchema().format("password")), null);
+ Assert.assertEquals(getMatchedRelation(new StringSchema().type("password")), null);
+ }
+
+ @Test
+ public void testMatchedRelation() {
+ //foreign keys
+ Assert.assertEquals(getMatchedRelation(new ObjectSchema().type("Something")), "something");
+ Assert.assertEquals(getMatchedColType(new ObjectSchema().type("Something")), "long");
+ Assert.assertEquals(getMatchedRelation(new ObjectSchema().type("UserNamespace.UserClass")), "userNamespaceUserClass");
+ Assert.assertEquals(getMatchedColType(new ObjectSchema().type("UserNamespace.UserClass")), "long");
+ //arrays are special case, we convert them to 1:N relations
+ Assert.assertEquals(getMatchedRelation(new ArraySchema()), "key");
+ Assert.assertEquals(getMatchedRelation(new ArraySchema().items(new ObjectSchema().type("Something"))), "something");
+ Assert.assertEquals(getMatchedRelation(new ArraySchema().items(new ObjectSchema().type("UserNamespace.UserClass"))), "userNamespaceUserClass");
+ Assert.assertEquals(getMatchedRelation(new ArraySchema().items(new IntegerSchema().type(SchemaTypeUtil.BYTE_FORMAT))), "key");
+ Assert.assertEquals(getMatchedRelation(new ArraySchema().items(new StringSchema())), "key");
+ //blob will be the default type, the template shouldn't include those fields
+ Assert.assertEquals(getMatchedColType(new ArraySchema()), "blob");
+ Assert.assertEquals(getMatchedColType(new ArraySchema().items(new ObjectSchema().type("Something"))), "blob");
+ Assert.assertEquals(getMatchedColType(new ArraySchema().items(new ObjectSchema().type("UserNamespace.UserClass"))), "blob");
+ Assert.assertEquals(getMatchedColType(new ArraySchema().items(new IntegerSchema().type(SchemaTypeUtil.BYTE_FORMAT))), "blob");
+ Assert.assertEquals(getMatchedColType(new ArraySchema().items(new StringSchema())), "blob");
+ }
+
+ @Test
+ public void testDefinePrimaryKey() {
+ final Schema schema = new Schema()
+ .description("a sample model")
+ .addProperties("key" , new IntegerSchema())
+ .addRequiredItem("key");
+ CodegenModel cm = getModel(schema, "key", false);
+ Assert.assertEquals(cm.vars.size(), 1);
+ CodegenProperty prop = cm.vars.get(0);
+ Map propSchema = getColumnDefinition(getExtension(prop));
+ Assert.assertEquals(propSchema.get("colPrimaryKey"), true);
+ Assert.assertEquals(propSchema.get("colType"), "int");
+ }
+
+ @Test
+ public void testDontAddSorrogateKey() {
+ final Schema schema = new Schema()
+ .description("a sample model")
+ .addProperties("key" , new IntegerSchema())
+ .addRequiredItem("key");
+ CodegenModel cm = getModel(schema, "id", false);
+ Assert.assertEquals(cm.vars.size(), 1);
+ CodegenProperty prop = cm.vars.get(0);
+ Map propSchema = getColumnDefinition(getExtension(prop));
+ Assert.assertEquals(propSchema.get("colPrimaryKey"), false);
+ Assert.assertEquals(propSchema.get("colType"), "int");
+ }
+
+ @Test
+ public void testAddSorrogateKey() {
+ final Schema schema = new Schema()
+ .description("a sample model")
+ .addProperties("key", new IntegerSchema());
+ CodegenModel cm = getModel(schema, "id", true);
+ Assert.assertEquals(cm.vars.size(), 2);
+ CodegenProperty prop = cm.vars.get(0);
+ Map propSchema = getColumnDefinition(getExtension(prop));
+ Assert.assertEquals(propSchema.get("colNotNull"), true);
+ Assert.assertEquals(propSchema.get("colPrimaryKey"), true);
+ Assert.assertEquals(propSchema.get("colName"), "id");
+ Assert.assertEquals(propSchema.get("colType"), "long"); //by default
+ CodegenProperty prop2 = cm.vars.get(1);
+ Map propSchema2 = getColumnDefinition(getExtension(prop2));
+ Assert.assertEquals(propSchema2.get("colNotNull"), false);
+ Assert.assertEquals(propSchema2.get("colPrimaryKey"), false);
+ Assert.assertEquals(propSchema2.get("colName"), "key");
+ Assert.assertEquals(propSchema2.get("colType"), "int");
+
+ }
+
+}
diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/options/TypeScriptAngularClientOptionsProvider.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/options/TypeScriptAngularClientOptionsProvider.java
index 51049743954..eb8d90a468b 100644
--- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/options/TypeScriptAngularClientOptionsProvider.java
+++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/options/TypeScriptAngularClientOptionsProvider.java
@@ -34,8 +34,8 @@ public class TypeScriptAngularClientOptionsProvider implements OptionsProvider {
public static final String ENSURE_UNIQUE_PARAMS_VALUE = "true";
public static final String ENUM_PROPERTY_NAMING_VALUE = "PascalCase";
public static final String MODEL_PROPERTY_NAMING_VALUE = "camelCase";
- private static final String NMP_NAME = "npmName";
- private static final String NMP_VERSION = "1.1.2";
+ private static final String NPM_NAME = "npmName";
+ private static final String NPM_VERSION = "1.1.2";
private static final String NPM_REPOSITORY = "https://registry.npmjs.org";
public static final String ALLOW_UNICODE_IDENTIFIERS_VALUE = "false";
public static final String NG_VERSION = "2";
@@ -44,6 +44,7 @@ public class TypeScriptAngularClientOptionsProvider implements OptionsProvider {
public static final String API_MODULE_PREFIX = "";
public static final String CONFIGURATION_PREFIX = "";
public static final String QUERY_PARAM_OBJECT_FORMAT_VALUE = "dot";
+ public static final String PROVIDED_IN_LEVEL = "root";
public static String SERVICE_SUFFIX = "Service";
public static String SERVICE_FILE_SUFFIX = ".service";
public static String MODEL_SUFFIX = "";
@@ -66,12 +67,13 @@ public class TypeScriptAngularClientOptionsProvider implements OptionsProvider {
.put(AbstractTypeScriptClientCodegen.NULL_SAFE_ADDITIONAL_PROPS, NULL_SAFE_ADDITIONAL_PROPS_VALUE)
.put(CodegenConstants.ENUM_NAME_SUFFIX, ENUM_NAME_SUFFIX)
.put(TypeScriptAngularClientCodegen.STRING_ENUMS, STRING_ENUMS_VALUE)
- .put(TypeScriptAngularClientCodegen.NPM_NAME, NMP_NAME)
- .put(TypeScriptAngularClientCodegen.NPM_VERSION, NMP_VERSION)
+ .put(TypeScriptAngularClientCodegen.NPM_NAME, NPM_NAME)
+ .put(TypeScriptAngularClientCodegen.NPM_VERSION, NPM_VERSION)
.put(TypeScriptAngularClientCodegen.SNAPSHOT, Boolean.FALSE.toString())
.put(TypeScriptAngularClientCodegen.WITH_INTERFACES, Boolean.FALSE.toString())
.put(TypeScriptAngularClientCodegen.USE_SINGLE_REQUEST_PARAMETER, Boolean.FALSE.toString())
.put(TypeScriptAngularClientCodegen.PROVIDED_IN_ROOT, Boolean.FALSE.toString())
+ .put(TypeScriptAngularClientCodegen.PROVIDED_IN, PROVIDED_IN_LEVEL)
.put(TypeScriptAngularClientCodegen.TAGGED_UNIONS, Boolean.FALSE.toString())
.put(TypeScriptAngularClientCodegen.NPM_REPOSITORY, NPM_REPOSITORY)
.put(TypeScriptAngularClientCodegen.NG_VERSION, NG_VERSION)
diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonClientTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonClientTest.java
index 683d4aa4b02..98f06e7b889 100644
--- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonClientTest.java
+++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/python/PythonClientTest.java
@@ -15,6 +15,18 @@
*/
package org.openapitools.codegen.python;
+import com.google.common.io.Resources;
+import io.swagger.v3.oas.models.PathItem;
+import io.swagger.v3.oas.models.Paths;
+import io.swagger.v3.oas.models.parameters.RequestBody;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PrintWriter;
+import java.nio.charset.StandardCharsets;
+import javax.validation.constraints.AssertTrue;
+import org.apache.commons.io.IOUtils;
import org.openapitools.codegen.config.CodegenConfigurator;
import com.google.common.collect.Sets;
@@ -33,6 +45,7 @@ import org.openapitools.codegen.*;
import org.openapitools.codegen.languages.PythonClientCodegen;
import org.openapitools.codegen.utils.ModelUtils;
import org.testng.Assert;
+import org.testng.TestNGAntTask.Mode;
import org.testng.annotations.Test;
@SuppressWarnings("static-method")
@@ -425,4 +438,31 @@ public class PythonClientTest {
final CodegenModel model = codegen.fromModel(modelName, modelSchema);
Assert.assertEquals((int) model.getMinProperties(), 1);
}
+
+ @Test(description = "tests RecursiveToExample")
+ public void testRecursiveToExample() throws IOException {
+ final OpenAPI openAPI = TestUtils.parseFlattenSpec("src/test/resources/3_0/issue_8052_recursive_model.yaml");
+ final PythonClientCodegen codegen = new PythonClientCodegen();
+ codegen.setOpenAPI(openAPI);
+
+ final Operation operation = openAPI.getPaths().get("/geojson").getPost();
+ Schema schema = ModelUtils.getSchemaFromRequestBody(operation.getRequestBody());
+ String exampleValue = codegen.toExampleValue(schema, null);
+
+ // uncomment if you need to regenerate the expected value
+ // PrintWriter printWriter = new PrintWriter("src/test/resources/3_0/issue_8052_recursive_model_expected_value.txt");
+ // printWriter.write(exampleValue);
+ // printWriter.close();
+ // org.junit.Assert.assertTrue(false);
+
+ String expectedValue = Resources.toString(
+ Resources.getResource("3_0/issue_8052_recursive_model_expected_value.txt"),
+ StandardCharsets.UTF_8);
+ expectedValue = expectedValue.replaceAll("\\r\\n", "\n");
+
+
+ Assert.assertEquals(expectedValue.trim(), exampleValue.trim());
+
+ }
+
}
diff --git a/modules/openapi-generator/src/test/java/org/openapitools/codegen/utils/StringUtilsTest.java b/modules/openapi-generator/src/test/java/org/openapitools/codegen/utils/StringUtilsTest.java
index d4a8efebae5..2001a9844c5 100644
--- a/modules/openapi-generator/src/test/java/org/openapitools/codegen/utils/StringUtilsTest.java
+++ b/modules/openapi-generator/src/test/java/org/openapitools/codegen/utils/StringUtilsTest.java
@@ -11,6 +11,7 @@ public class StringUtilsTest {
public void testUnderscore() {
Assert.assertEquals(underscore("abcd"), "abcd");
Assert.assertEquals(underscore("abCd"), "ab_cd");
+ Assert.assertEquals(underscore("ListABCs"), "list_abcs");
}
@Test
diff --git a/modules/openapi-generator/src/test/resources/2_0/swift/petstore-with-fake-endpoints-models-for-testing.yaml b/modules/openapi-generator/src/test/resources/2_0/swift/petstore-with-fake-endpoints-models-for-testing.yaml
index 505aa0dd631..c37f4329e4e 100644
--- a/modules/openapi-generator/src/test/resources/2_0/swift/petstore-with-fake-endpoints-models-for-testing.yaml
+++ b/modules/openapi-generator/src/test/resources/2_0/swift/petstore-with-fake-endpoints-models-for-testing.yaml
@@ -1086,6 +1086,7 @@ definitions:
xml:
name: Order
Category:
+ x-swift-hashable: true
type: object
required:
- name
@@ -1124,6 +1125,7 @@ definitions:
xml:
name: User
Tag:
+ x-swift-hashable: true
type: object
properties:
id:
diff --git a/modules/openapi-generator/src/test/resources/3_0/issue_8052_recursive_model.yaml b/modules/openapi-generator/src/test/resources/3_0/issue_8052_recursive_model.yaml
new file mode 100644
index 00000000000..6d833b29211
--- /dev/null
+++ b/modules/openapi-generator/src/test/resources/3_0/issue_8052_recursive_model.yaml
@@ -0,0 +1,83 @@
+openapi: 3.0.0
+info:
+ version: 01.01.00
+ title: APITest API documentation.
+ termsOfService: http://api.apitest.com/party/tos/
+servers:
+ - url: https://api.apitest.com/v1
+paths:
+ /geojson:
+ post:
+ summary: Add a GeoJson Object
+ operationId: post-geojson
+ responses:
+ '201':
+ description: Created
+ content:
+ application/json:
+ schema:
+ type: string
+ description: GeoJson ID
+ '400':
+ description: Bad Request
+ requestBody:
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/GeoJsonGeometry'
+ parameters: []
+components:
+ schemas:
+ GeoJsonGeometry:
+ title: GeoJsonGeometry
+ description: GeoJSON geometry
+ oneOf:
+ - $ref: '#/components/schemas/Point'
+ - $ref: '#/components/schemas/GeometryCollection'
+ discriminator:
+ propertyName: type
+ mapping:
+ Point: '#/components/schemas/Point'
+ GeometryCollection: '#/components/schemas/GeometryCollection'
+ externalDocs:
+ url: http://geojson.org/geojson-spec.html#geometry-objects
+ Point:
+ title: Point
+ type: object
+ description: GeoJSON geometry
+ externalDocs:
+ url: http://geojson.org/geojson-spec.html#id2
+ properties:
+ coordinates:
+ title: Point3D
+ type: array
+ description: Point in 3D space
+ externalDocs:
+ url: http://geojson.org/geojson-spec.html#id2
+ minItems: 2
+ maxItems: 3
+ items:
+ type: number
+ format: double
+ type:
+ type: string
+ default: Point
+ required:
+ - type
+ GeometryCollection:
+ title: GeometryCollection
+ type: object
+ description: GeoJSon geometry collection
+ required:
+ - type
+ - geometries
+ externalDocs:
+ url: http://geojson.org/geojson-spec.html#geometrycollection
+ properties:
+ type:
+ type: string
+ default: GeometryCollection
+ geometries:
+ type: array
+ items:
+ $ref: '#/components/schemas/GeoJsonGeometry'
diff --git a/modules/openapi-generator/src/test/resources/3_0/issue_8052_recursive_model_expected_value.txt b/modules/openapi-generator/src/test/resources/3_0/issue_8052_recursive_model_expected_value.txt
new file mode 100644
index 00000000000..98ef62651fb
--- /dev/null
+++ b/modules/openapi-generator/src/test/resources/3_0/issue_8052_recursive_model_expected_value.txt
@@ -0,0 +1,9 @@
+GeoJsonGeometry(
+ type="GeometryCollection",
+ geometries=[
+ GeoJsonGeometry(
+ type="GeometryCollection",
+ geometries=[],
+ ),
+ ],
+ )
\ No newline at end of file
diff --git a/mvnw b/mvnw
index 4e574d9a02d..41c0f0c23db 100755
--- a/mvnw
+++ b/mvnw
@@ -8,7 +8,7 @@
# "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
+# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
@@ -19,7 +19,7 @@
# ----------------------------------------------------------------------------
# ----------------------------------------------------------------------------
-# Maven2 Start Up Batch script
+# Maven Start Up Batch script
#
# Required ENV vars:
# ------------------
@@ -114,7 +114,6 @@ if $mingw ; then
M2_HOME="`(cd "$M2_HOME"; pwd)`"
[ -n "$JAVA_HOME" ] &&
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
- # TODO classpath?
fi
if [ -z "$JAVA_HOME" ]; then
@@ -200,6 +199,85 @@ if [ -z "$BASE_DIR" ]; then
exit 1;
fi
+##########################################################################################
+# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+# This allows using the maven wrapper in projects that prohibit checking in binary data.
+##########################################################################################
+if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found .mvn/wrapper/maven-wrapper.jar"
+ fi
+else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
+ fi
+ if [ -n "$MVNW_REPOURL" ]; then
+ jarUrl="$MVNW_REPOURL/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ else
+ jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ fi
+ while IFS="=" read key value; do
+ case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
+ esac
+ done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Downloading from: $jarUrl"
+ fi
+ wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
+ if $cygwin; then
+ wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"`
+ fi
+
+ if command -v wget > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found wget ... using wget"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ wget "$jarUrl" -O "$wrapperJarPath"
+ else
+ wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath"
+ fi
+ elif command -v curl > /dev/null; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Found curl ... using curl"
+ fi
+ if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then
+ curl -o "$wrapperJarPath" "$jarUrl" -f
+ else
+ curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f
+ fi
+
+ else
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo "Falling back to using Java to download"
+ fi
+ javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
+ # For Cygwin, switch paths to Windows format before running javac
+ if $cygwin; then
+ javaClass=`cygpath --path --windows "$javaClass"`
+ fi
+ if [ -e "$javaClass" ]; then
+ if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Compiling MavenWrapperDownloader.java ..."
+ fi
+ # Compiling the Java class
+ ("$JAVA_HOME/bin/javac" "$javaClass")
+ fi
+ if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
+ # Running the downloader
+ if [ "$MVNW_VERBOSE" = true ]; then
+ echo " - Running MavenWrapperDownloader.java ..."
+ fi
+ ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
+ fi
+ fi
+ fi
+fi
+##########################################################################################
+# End of extension
+##########################################################################################
+
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
if [ "$MVNW_VERBOSE" = true ]; then
echo $MAVEN_PROJECTBASEDIR
@@ -218,6 +296,11 @@ if $cygwin; then
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
fi
+# Provide a "standardized" way to retrieve the CLI args that will
+# work with both Windows and non-Windows executions.
+MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@"
+export MAVEN_CMD_LINE_ARGS
+
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
exec "$JAVACMD" \
diff --git a/mvnw.cmd b/mvnw.cmd
index e506408e362..86115719e53 100755
--- a/mvnw.cmd
+++ b/mvnw.cmd
@@ -7,7 +7,7 @@
@REM "License"); you may not use this file except in compliance
@REM with the License. You may obtain a copy of the License at
@REM
-@REM https://www.apache.org/licenses/LICENSE-2.0
+@REM http://www.apache.org/licenses/LICENSE-2.0
@REM
@REM Unless required by applicable law or agreed to in writing,
@REM software distributed under the License is distributed on an
@@ -18,7 +18,7 @@
@REM ----------------------------------------------------------------------------
@REM ----------------------------------------------------------------------------
-@REM Maven2 Start Up Batch script
+@REM Maven Start Up Batch script
@REM
@REM Required ENV vars:
@REM JAVA_HOME - location of a JDK home dir
@@ -26,7 +26,7 @@
@REM Optional ENV vars
@REM M2_HOME - location of maven2's installed home dir
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
-@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
+@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
@REM e.g. to debug Maven itself, use
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
@@ -37,7 +37,7 @@
@echo off
@REM set title of command window
title %0
-@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
+@REM enable echoing by setting MAVEN_BATCH_ECHO to 'on'
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
@REM set %HOME% to equivalent of $HOME
@@ -117,10 +117,47 @@ for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do s
:endReadAdditionalConfig
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
-
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
+set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+
+FOR /F "tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO (
+ IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
+)
+
+@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
+@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
+if exist %WRAPPER_JAR% (
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Found %WRAPPER_JAR%
+ )
+) else (
+ if not "%MVNW_REPOURL%" == "" (
+ SET DOWNLOAD_URL="%MVNW_REPOURL%/io/takari/maven-wrapper/0.5.6/maven-wrapper-0.5.6.jar"
+ )
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Couldn't find %WRAPPER_JAR%, downloading it ...
+ echo Downloading from: %DOWNLOAD_URL%
+ )
+
+ powershell -Command "&{"^
+ "$webclient = new-object System.Net.WebClient;"^
+ "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^
+ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^
+ "}"^
+ "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^
+ "}"
+ if "%MVNW_VERBOSE%" == "true" (
+ echo Finished downloading %WRAPPER_JAR%
+ )
+)
+@REM End of extension
+
+@REM Provide a "standardized" way to retrieve the CLI args that will
+@REM work with both Windows and non-Windows executions.
+set MAVEN_CMD_LINE_ARGS=%*
+
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
if ERRORLEVEL 1 goto error
goto end
diff --git a/pom.xml b/pom.xml
index f2a7a0800a5..9f2b516426a 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1181,6 +1181,7 @@
+ samples/client/petstore/crystal
samples/server/petstore/python-aiohttp
samples/server/petstore/python-aiohttp-srclayout
@@ -1429,9 +1430,26 @@
+
+ samples/client/petstore/swift5/alamofireLibrary
+ samples/client/petstore/swift5/combineLibrary
+ samples/client/petstore/swift5/default
+ samples/client/petstore/swift5/deprecated
+ samples/client/petstore/swift5/nonPublicApi
+ samples/client/petstore/swift5/objcCompatible
+ samples/client/petstore/swift5/promisekitLibrary
+ samples/client/petstore/swift5/readonlyProperties
+ samples/client/petstore/swift5/resultLibrary
+ samples/client/petstore/swift5/rxswiftLibrary
+ samples/client/petstore/swift5/urlsessionLibrary
+
+ samples/client/petstore/swift5/alamofireLibrary/SwaggerClientTests
+ samples/client/petstore/swift5/combineLibrary/SwaggerClientTests
samples/client/petstore/swift5/default/SwaggerClientTests
- samples/client/petstore/swift5/promisekit/SwaggerClientTests
- samples/client/petstore/swift5/rxswift/SwaggerClientTests
+ samples/client/petstore/swift5/promisekitLibrary/SwaggerClientTests
+ samples/client/petstore/swift5/rxswiftLibrary/SwaggerClientTests
+ samples/client/petstore/swift5/urlsessionLibrary/SwaggerClientTests
+
samples/client/petstore/swift4/default/SwaggerClientTests
samples/client/petstore/swift4/promisekit/SwaggerClientTests
samples/client/petstore/swift4/rxswift/SwaggerClientTests
diff --git a/samples/client/petstore/crystal/.gitignore b/samples/client/petstore/crystal/.gitignore
new file mode 100644
index 00000000000..05a17cb8f0a
--- /dev/null
+++ b/samples/client/petstore/crystal/.gitignore
@@ -0,0 +1,39 @@
+# Generated by: https://openapi-generator.tech
+#
+
+*.gem
+*.rbc
+/.config
+/coverage/
+/InstalledFiles
+/pkg/
+/spec/reports/
+/spec/examples.txt
+/test/tmp/
+/test/version_tmp/
+/tmp/
+
+## Specific to RubyMotion:
+.dat*
+.repl_history
+build/
+
+## Documentation cache and generated files:
+/.yardoc/
+/_yardoc/
+/doc/
+/rdoc/
+
+## Environment normalization:
+/.bundle/
+/vendor/bundle
+/lib/bundler/man/
+
+# for a library or gem, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# Gemfile.lock
+# .ruby-version
+# .ruby-gemset
+
+# unless supporting rvm < 1.11.0 or doing something fancy, ignore this:
+.rvmrc
diff --git a/samples/client/petstore/crystal/.openapi-generator-ignore b/samples/client/petstore/crystal/.openapi-generator-ignore
new file mode 100644
index 00000000000..7484ee590a3
--- /dev/null
+++ b/samples/client/petstore/crystal/.openapi-generator-ignore
@@ -0,0 +1,23 @@
+# OpenAPI Generator Ignore
+# Generated by openapi-generator https://github.com/openapitools/openapi-generator
+
+# Use this file to prevent files from being overwritten by the generator.
+# The patterns follow closely to .gitignore or .dockerignore.
+
+# As an example, the C# client generator defines ApiClient.cs.
+# You can make changes and tell OpenAPI Generator to ignore just this file by uncommenting the following line:
+#ApiClient.cs
+
+# You can match any string of characters against a directory, file or extension with a single asterisk (*):
+#foo/*/qux
+# The above matches foo/bar/qux and foo/baz/qux, but not foo/bar/baz/qux
+
+# You can recursively match patterns against a directory, file or extension with a double asterisk (**):
+#foo/**/qux
+# This matches foo/bar/qux, foo/baz/qux, and foo/bar/baz/qux
+
+# You can also negate patterns with an exclamation (!).
+# For example, you can ignore all files in a docs folder with the file extension .md:
+#docs/*.md
+# Then explicitly reverse the ignore rule for a single file:
+#!docs/README.md
diff --git a/samples/client/petstore/crystal/.openapi-generator/FILES b/samples/client/petstore/crystal/.openapi-generator/FILES
new file mode 100644
index 00000000000..aa17a1e4b98
--- /dev/null
+++ b/samples/client/petstore/crystal/.openapi-generator/FILES
@@ -0,0 +1,19 @@
+.gitignore
+.travis.yml
+README.md
+git_push.sh
+shard.yml
+spec/spec_helper.cr
+src/petstore.cr
+src/petstore/api/pet_api.cr
+src/petstore/api/store_api.cr
+src/petstore/api/user_api.cr
+src/petstore/api_client.cr
+src/petstore/api_error.cr
+src/petstore/configuration.cr
+src/petstore/models/api_response.cr
+src/petstore/models/category.cr
+src/petstore/models/order.cr
+src/petstore/models/pet.cr
+src/petstore/models/tag.cr
+src/petstore/models/user.cr
diff --git a/samples/client/petstore/crystal/.openapi-generator/VERSION b/samples/client/petstore/crystal/.openapi-generator/VERSION
new file mode 100644
index 00000000000..c30f0ec2be7
--- /dev/null
+++ b/samples/client/petstore/crystal/.openapi-generator/VERSION
@@ -0,0 +1 @@
+5.1.0-SNAPSHOT
\ No newline at end of file
diff --git a/samples/client/petstore/crystal/.rspec b/samples/client/petstore/crystal/.rspec
new file mode 100644
index 00000000000..83e16f80447
--- /dev/null
+++ b/samples/client/petstore/crystal/.rspec
@@ -0,0 +1,2 @@
+--color
+--require spec_helper
diff --git a/samples/client/petstore/crystal/.rubocop.yml b/samples/client/petstore/crystal/.rubocop.yml
new file mode 100644
index 00000000000..d32b2b1cdab
--- /dev/null
+++ b/samples/client/petstore/crystal/.rubocop.yml
@@ -0,0 +1,148 @@
+# This file is based on https://github.com/rails/rails/blob/master/.rubocop.yml (MIT license)
+# Automatically generated by OpenAPI Generator (https://openapi-generator.tech)
+AllCops:
+ TargetRubyVersion: 2.4
+ # RuboCop has a bunch of cops enabled by default. This setting tells RuboCop
+ # to ignore them, so only the ones explicitly set in this file are enabled.
+ DisabledByDefault: true
+ Exclude:
+ - '**/templates/**/*'
+ - '**/vendor/**/*'
+ - 'actionpack/lib/action_dispatch/journey/parser.rb'
+
+# Prefer &&/|| over and/or.
+Style/AndOr:
+ Enabled: true
+
+# Align `when` with `case`.
+Layout/CaseIndentation:
+ Enabled: true
+
+# Align comments with method definitions.
+Layout/CommentIndentation:
+ Enabled: true
+
+Layout/ElseAlignment:
+ Enabled: true
+
+Layout/EmptyLineAfterMagicComment:
+ Enabled: true
+
+# In a regular class definition, no empty lines around the body.
+Layout/EmptyLinesAroundClassBody:
+ Enabled: true
+
+# In a regular method definition, no empty lines around the body.
+Layout/EmptyLinesAroundMethodBody:
+ Enabled: true
+
+# In a regular module definition, no empty lines around the body.
+Layout/EmptyLinesAroundModuleBody:
+ Enabled: true
+
+Layout/FirstArgumentIndentation:
+ Enabled: true
+
+# Use Ruby >= 1.9 syntax for hashes. Prefer { a: :b } over { :a => :b }.
+Style/HashSyntax:
+ Enabled: false
+
+# Method definitions after `private` or `protected` isolated calls need one
+# extra level of indentation.
+Layout/IndentationConsistency:
+ Enabled: true
+ EnforcedStyle: indented_internal_methods
+
+# Two spaces, no tabs (for indentation).
+Layout/IndentationWidth:
+ Enabled: true
+
+Layout/LeadingCommentSpace:
+ Enabled: true
+
+Layout/SpaceAfterColon:
+ Enabled: true
+
+Layout/SpaceAfterComma:
+ Enabled: true
+
+Layout/SpaceAroundEqualsInParameterDefault:
+ Enabled: true
+
+Layout/SpaceAroundKeyword:
+ Enabled: true
+
+Layout/SpaceAroundOperators:
+ Enabled: true
+
+Layout/SpaceBeforeComma:
+ Enabled: true
+
+Layout/SpaceBeforeFirstArg:
+ Enabled: true
+
+Style/DefWithParentheses:
+ Enabled: true
+
+# Defining a method with parameters needs parentheses.
+Style/MethodDefParentheses:
+ Enabled: true
+
+Style/FrozenStringLiteralComment:
+ Enabled: false
+ EnforcedStyle: always
+
+# Use `foo {}` not `foo{}`.
+Layout/SpaceBeforeBlockBraces:
+ Enabled: true
+
+# Use `foo { bar }` not `foo {bar}`.
+Layout/SpaceInsideBlockBraces:
+ Enabled: true
+
+# Use `{ a: 1 }` not `{a:1}`.
+Layout/SpaceInsideHashLiteralBraces:
+ Enabled: true
+
+Layout/SpaceInsideParens:
+ Enabled: true
+
+# Check quotes usage according to lint rule below.
+#Style/StringLiterals:
+# Enabled: true
+# EnforcedStyle: single_quotes
+
+# Detect hard tabs, no hard tabs.
+Layout/IndentationStyle:
+ Enabled: true
+
+# Blank lines should not have any spaces.
+Layout/TrailingEmptyLines:
+ Enabled: true
+
+# No trailing whitespace.
+Layout/TrailingWhitespace:
+ Enabled: false
+
+# Use quotes for string literals when they are enough.
+Style/RedundantPercentQ:
+ Enabled: true
+
+# Align `end` with the matching keyword or starting expression except for
+# assignments, where it should be aligned with the LHS.
+Layout/EndAlignment:
+ Enabled: true
+ EnforcedStyleAlignWith: variable
+ AutoCorrect: true
+
+# Use my_method(my_arg) not my_method( my_arg ) or my_method my_arg.
+Lint/RequireParentheses:
+ Enabled: true
+
+Style/RedundantReturn:
+ Enabled: true
+ AllowMultipleReturnValues: true
+
+Style/Semicolon:
+ Enabled: true
+ AllowAsExpressionSeparator: true
diff --git a/samples/client/petstore/crystal/.travis.yml b/samples/client/petstore/crystal/.travis.yml
new file mode 100644
index 00000000000..b66a6b55820
--- /dev/null
+++ b/samples/client/petstore/crystal/.travis.yml
@@ -0,0 +1,16 @@
+# #OpenAPI Petstore
+#
+##This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.
+#
+#The version of the OpenAPI document: 1.0.0
+#
+#Generated by: https://openapi-generator.tech
+#OpenAPI Generator version: 5.1.0-SNAPSHOT
+#
+
+language: crystal
+
+script:
+ - crystal spec
+# uncomment below to check the code format
+# - crystal tool format --check
diff --git a/samples/client/petstore/crystal/README.md b/samples/client/petstore/crystal/README.md
new file mode 100644
index 00000000000..fc7fa03548c
--- /dev/null
+++ b/samples/client/petstore/crystal/README.md
@@ -0,0 +1,38 @@
+# petstore
+
+The Crystsal module for the OpenAPI Petstore
+
+This is a sample server Petstore server. For this sample, you can use the api key `special-key` to test the authorization filters.
+
+This SDK is automatically generated by the [OpenAPI Generator](https://openapi-generator.tech) project:
+
+- API version: 1.0.0
+- Package version: 1.0.0
+- Build package: org.openapitools.codegen.languages.CrystalClientCodegen
+
+## Installation
+
+### Install from Git
+
+Add the following to shard.yaml
+
+```yaml
+dependencies:
+ petstore:
+ github: GIT_USER_ID/GIT_REPO_ID
+ version: ~> 1.0.0
+```
+
+## Development
+
+Install dependencies
+
+```shell
+shards
+```
+
+Run the tests:
+
+```shell
+crystal spec
+```
diff --git a/samples/client/petstore/crystal/bin/ameba b/samples/client/petstore/crystal/bin/ameba
new file mode 100755
index 00000000000..332b8c750a4
Binary files /dev/null and b/samples/client/petstore/crystal/bin/ameba differ
diff --git a/samples/client/petstore/crystal/bin/ameba.cr b/samples/client/petstore/crystal/bin/ameba.cr
new file mode 100644
index 00000000000..d61d0ff11a5
--- /dev/null
+++ b/samples/client/petstore/crystal/bin/ameba.cr
@@ -0,0 +1,7 @@
+# Require ameba cli which starts the inspection.
+require "ameba/cli"
+
+# Require ameba extensions here which are added as project dependencies.
+# Example:
+#
+# require "ameba-performance"
diff --git a/samples/client/petstore/crystal/git_push.sh b/samples/client/petstore/crystal/git_push.sh
new file mode 100644
index 00000000000..ced3be2b0c7
--- /dev/null
+++ b/samples/client/petstore/crystal/git_push.sh
@@ -0,0 +1,58 @@
+#!/bin/sh
+# ref: https://help.github.com/articles/adding-an-existing-project-to-github-using-the-command-line/
+#
+# Usage example: /bin/sh ./git_push.sh wing328 openapi-pestore-perl "minor update" "gitlab.com"
+
+git_user_id=$1
+git_repo_id=$2
+release_note=$3
+git_host=$4
+
+if [ "$git_host" = "" ]; then
+ git_host="github.com"
+ echo "[INFO] No command line input provided. Set \$git_host to $git_host"
+fi
+
+if [ "$git_user_id" = "" ]; then
+ git_user_id="GIT_USER_ID"
+ echo "[INFO] No command line input provided. Set \$git_user_id to $git_user_id"
+fi
+
+if [ "$git_repo_id" = "" ]; then
+ git_repo_id="GIT_REPO_ID"
+ echo "[INFO] No command line input provided. Set \$git_repo_id to $git_repo_id"
+fi
+
+if [ "$release_note" = "" ]; then
+ release_note="Minor update"
+ echo "[INFO] No command line input provided. Set \$release_note to $release_note"
+fi
+
+# Initialize the local directory as a Git repository
+git init
+
+# Adds the files in the local repository and stages them for commit.
+git add .
+
+# Commits the tracked changes and prepares them to be pushed to a remote repository.
+git commit -m "$release_note"
+
+# Sets the new remote
+git_remote=`git remote`
+if [ "$git_remote" = "" ]; then # git remote not defined
+
+ if [ "$GIT_TOKEN" = "" ]; then
+ echo "[INFO] \$GIT_TOKEN (environment variable) is not set. Using the git credential in your environment."
+ git remote add origin https://${git_host}/${git_user_id}/${git_repo_id}.git
+ else
+ git remote add origin https://${git_user_id}:${GIT_TOKEN}@${git_host}/${git_user_id}/${git_repo_id}.git
+ fi
+
+fi
+
+git pull origin master
+
+# Pushes (Forces) the changes in the local repository up to the remote repository
+echo "Git pushing to https://${git_host}/${git_user_id}/${git_repo_id}.git"
+git push origin master 2>&1 | grep -v 'To https'
+
diff --git a/samples/client/petstore/crystal/pom.xml b/samples/client/petstore/crystal/pom.xml
new file mode 100644
index 00000000000..f4b890f7d80
--- /dev/null
+++ b/samples/client/petstore/crystal/pom.xml
@@ -0,0 +1,56 @@
+
+ 4.0.0
+ org.openapitools
+ CrystalPetstoreClientTests
+ pom
+ 1.0-SNAPSHOT
+ Crystal OpenAPI Petstore Client
+
+
+
+ maven-dependency-plugin
+
+
+ package
+
+