Compare commits

..

1 Commits

Author SHA1 Message Date
devhl
da9ef216a0 version bump 2024-09-06 19:08:32 -04:00
788 changed files with 45709 additions and 16658 deletions

View File

@@ -17,5 +17,5 @@
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*`.
IMPORTANT: Do **NOT** purge/delete any folders/files (e.g. tests) when regenerating the samples as manually written tests may be removed.
- [ ] File the PR against the [correct branch](https://github.com/OpenAPITools/openapi-generator/wiki/Git-Branches): `master` (upcoming `7.x.0` minor release - breaking changes with fallbacks), `8.0.x` (breaking changes without fallbacks)
- [ ] File the PR against the [correct branch](https://github.com/OpenAPITools/openapi-generator/wiki/Git-Branches): `master` (upcoming 7.6.0 minor release - breaking changes with fallbacks), `8.0.x` (breaking changes without fallbacks)
- [ ] If your PR is targeting a particular programming language, @mention the [technical committee](https://github.com/openapitools/openapi-generator/#62---openapi-generator-technical-committee) members, so they are more likely to review the pull request.

View File

@@ -3,33 +3,33 @@ name: Samples Erlang
on:
push:
paths:
- samples/server/echo_api/erlang-server/**
- samples/server/petstore/erlang-server/**
# comment out due to errors
# ===> Compiling src/openapi_pet_handler.erl failed
# src/openapi_pet_handler.erl:278: function is_authorized/2 already defined
#- samples/server/petstore/erlang-server/**
- samples/client/petstore/erlang-client/**
- samples/client/petstore/erlang-proper/**
pull_request:
paths:
- samples/server/echo_api/erlang-server/**
- samples/server/petstore/erlang-server/**
#- samples/server/petstore/erlang-server/**
- samples/client/petstore/erlang-client/**
- samples/client/petstore/erlang-proper/**
jobs:
build:
name: Build Erlang projects
runs-on: ubuntu-24.04
runs-on: ubuntu-20.04
strategy:
fail-fast: false
matrix:
sample:
- samples/server/echo_api/erlang-server/
- samples/server/petstore/erlang-server/
#- samples/server/petstore/erlang-server/
- samples/client/petstore/erlang-client/
- samples/client/petstore/erlang-proper/
steps:
- uses: actions/checkout@v4
- uses: erlef/setup-beam@v1
with:
otp-version: '27'
rebar3-version: '3.23.0'
otp-version: '22.2'
rebar3-version: '3.14.3'
- run: rebar3 compile
working-directory: ${{ matrix.sample }}

View File

@@ -35,27 +35,3 @@ jobs:
- name: Run test
working-directory: ${{ matrix.sample }}
run: go test -mod=mod -v
verify:
name: Verify generated Go files with Go tests
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
sample:
- samples/server/petstore/go-api-server/
go-version:
- "1.18"
steps:
- uses: actions/checkout@v4
- name: Set up Go
uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go-version }}
- name: Install Dependencies
working-directory: ${{ matrix.sample }}
run: |
go mod tidy
- name: Run tests
working-directory: ${{ matrix.sample }}
run: go test ./samples_tests -v

View File

@@ -17,7 +17,7 @@ jobs:
# schema
- samples/schema/postman-collection
python-version:
- "3.12"
- "3.11"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5

View File

@@ -19,7 +19,6 @@ jobs:
- "3.9"
- "3.10"
- "3.11"
- "3.12"
sample:
- samples/openapi3/client/petstore/python-aiohttp
- samples/openapi3/client/petstore/python

View File

@@ -17,11 +17,11 @@ jobs:
# clients
- samples/client/echo_api/python-pydantic-v1/
python-version:
- "3.7"
- "3.8"
- "3.9"
- "3.10"
- "3.11"
- "3.12"
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5

View File

@@ -19,7 +19,6 @@ jobs:
- "3.9"
- "3.10"
- "3.11"
- "3.12"
sample:
- samples/openapi3/client/petstore/python-pydantic-v1-aiohttp
- samples/openapi3/client/petstore/python-pydantic-v1

View File

@@ -21,7 +21,7 @@ jobs:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
with:
python-version: '3.9'
python-version: '3.7'
- name: Test
working-directory: ${{ matrix.sample }}
run: make test-all

View File

@@ -72,7 +72,6 @@ If you find OpenAPI Generator useful for work, please consider asking your compa
[<img src="https://openapi-generator.tech/img/companies/itm.png" width="128" height="128">](https://opensource.muenchen.de?utm_source=openapi-generator&utm_medium=sponsorship&utm_campaign=oss-sponsorship)
[<img src="https://openapi-generator.tech/img/companies/kong.png" width="128" height="128">](https://konghq.com/products/kong-konnect?utm_medium=referral&utm_source=github&utm_campaign=platform&utm_content=openapi-generator)
[<img src="https://openapi-generator.tech/img/companies/route4me.png" width="128" height="128">](https://route4me.com/?utm_source=openapi-generator&utm_medium=sponsorship&utm_campaign=oss-sponsorship)
[<img src="https://openapi-generator.tech/img/companies/dm.png" width="128" height="128">](https://www.dotcom-monitor.com/sponsoring-open-source-projects/?utm_source=openapi-generator&utm_medium=sponsorship&utm_campaign=oss-sponsorship)
#### Thank you GoDaddy for sponsoring the domain names, Linode for sponsoring the VPS, Checkly for sponsoring the API monitoring and Gradle for sponsoring Develocity
@@ -447,7 +446,41 @@ npm install @openapitools/openapi-generator-cli -D
```
<!-- /RELEASE_VERSION -->
You can use [locally built JARs](https://github.com/OpenAPITools/openapi-generator-cli?tab=readme-ov-file#use-locally-built-jar) or [`SNAPSHOT` versions](https://github.com/OpenAPITools/openapi-generator-cli?tab=readme-ov-file#use-nightly-snapshot-build) as well.
#### Use locally built JAR
In order to use a locally built jar of the generator CLI, you can copy the jar from your local build (i.e. if you were to `build` this repository it would be in `~/openapi-generator/modules/openapi-generator-cli/target/openapi-generator-cli.jar`) into `./node_modules/@openapitools/openapi-generator-cli/versions/` and change the `version` in the `openapitools.json` file to the base name of the jar file.
E.g.:
```sh
cd openapi-generator
./mvnw clean package
cp ./modules/openapi-generator-cli/target/openapi-generator-cli.jar /your/project/node_modules/@openapitools/openapi-generator-cli/versions/my-local-snapshot.jar
```
and then:
```json
{
"$schema": "./node_modules/@openapitools/openapi-generator-cli/config.schema.json",
"spaces": 2,
"generator-cli": {
"version": "my-local-snapshot",
}
}
```
#### Use nightly `SNAPSHOT` build
Change your `openapitools.json` to:
```json
{
"$schema": "node_modules/@openapitools/openapi-generator-cli/config.schema.json",
"spaces": 2,
"generator-cli": {
"version": "7.9.0-20240829.123431-22",
"repository": {
"downloadUrl": "https://oss.sonatype.org/content/repositories/snapshots/org/openapitools/openapi-generator-cli/7.9.0-SNAPSHOT/openapi-generator-cli-${versionName}.jar"
}
}
}
```
(example is with a snapshot of `7.9.0`, please change the `version` and `downloadUrl` accordingly)
## [2 - Getting Started](#table-of-contents)
@@ -1074,7 +1107,7 @@ Here is a list of template creators:
* C++ (Qt5 QHttpEngine): @etherealjoy
* C++ Pistache: @sebymiano
* C++ Restbed: @stkrwork
* Erlang Server: @galaxie @nelsonvides
* Erlang Server: @galaxie
* F# (Giraffe) Server: @nmfisher
* Go Server: @guohuang
* Go Server (refactored in 7.0.0): @lwj5
@@ -1185,7 +1218,7 @@ If you want to join the committee, please kindly apply by sending an email to te
| Eiffel | @jvelilla (2017/09) |
| Elixir | @mrmstn (2018/12) |
| Elm | @eriktim (2018/09) |
| Erlang | @tsloughter (2017/11) @jfacorro (2018/10) @robertoaloi (2018/10) @nelsonvides (2024/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) @jirikuncar (2021/01) @ph4r5h4d (2021/04) @lwj5 (2023/04) |
| GraphQL | @renepardon (2018/12) |

View File

@@ -1,4 +0,0 @@
generatorName: avro-schema
outputDir: samples/openapi3/schema/valid-enums/avro-schema-enum
inputSpec: modules/openapi-generator/src/test/resources/3_0/avro-schema/valid-enums.yaml
templateDir: modules/openapi-generator/src/main/resources/avro-schema

View File

@@ -2,6 +2,3 @@ generatorName: avro-schema
outputDir: samples/openapi3/schema/petstore/avro-schema
inputSpec: modules/openapi-generator/src/test/resources/3_0/avro-schema/petstore.yaml
templateDir: modules/openapi-generator/src/main/resources/avro-schema
additionalProperties:
useLogicalTypes: true
logicalTypeTimeQuantifier: nanos

View File

@@ -1,4 +0,0 @@
generatorName: erlang-server
outputDir: samples/server/echo_api/erlang-server
inputSpec: modules/openapi-generator/src/test/resources/3_0/echo_api.yaml
templateDir: modules/openapi-generator/src/main/resources/erlang-server

View File

@@ -1,4 +0,0 @@
generatorName: typescript
outputDir: samples/client/others/typescript/builds/array-of-lists
inputSpec: modules/openapi-generator/src/test/resources/3_0/typescript/array_list.yaml
templateDir: modules/openapi-generator/src/main/resources/typescript

View File

@@ -201,23 +201,6 @@ The steps are shown here for a specific version of the generator, but apply the
```
* Set breakpoints in code, and then attach your remote debugger from your IDE (see above). The generator will automatically unblock once the remote debugger is attached. You can now step through the code.
For VSCode you can use the following launch configuration (`launch.json`):
```json
{
"version": "0.2.0",
"configurations": [
{
"type": "java",
"name": "Debug (Attach)",
"request": "attach",
"hostName": "localhost",
"port": 5005
}
]
}
```
to attach the the suspended process above.
## Logs
You can try to enable debugging log with `-Dlog.level=debug` option to the `JAVA_OPTS` environment variable to see more information:

View File

@@ -89,7 +89,6 @@ The following generators are available:
* [cpp-restbed-server-deprecated](generators/cpp-restbed-server-deprecated.md)
* [csharp-functions](generators/csharp-functions.md)
* [erlang-server](generators/erlang-server.md)
* [erlang-server-deprecated (deprecated)](generators/erlang-server-deprecated.md)
* [fsharp-functions (beta)](generators/fsharp-functions.md)
* [fsharp-giraffe-server (beta)](generators/fsharp-giraffe-server.md)
* [go-echo-server (beta)](generators/go-echo-server.md)

View File

@@ -23,12 +23,10 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|ensureUniqueParams|Whether to ensure parameter names are unique in an operation (rename parameters that are not).| |true|
|enumUnknownDefaultCase|If the server adds new enum cases, that are unknown by an old spec/client, the client will fail to parse the network response.With this option enabled, each enum will have a new case, 'unknown_default_open_api', so that when the server sends an enum case that is not known by the client/spec, they can safely fallback to this case.|<dl><dt>**false**</dt><dd>No changes to the enum's are made, this is the default option.</dd><dt>**true**</dt><dd>With this option enabled, each enum will have a new case, 'unknown_default_open_api', so that when the enum case sent by the server is not known by the client/spec, can safely be decoded to this case.</dd></dl>|false|
|legacyDiscriminatorBehavior|Set to false for generators with better support for discriminators. (Python, Java, Go, PowerShell, C# have this enabled by default).|<dl><dt>**true**</dt><dd>The mapping in the discriminator includes descendent schemas that allOf inherit from self and the discriminator mapping schemas in the OAS document.</dd><dt>**false**</dt><dd>The mapping in the discriminator includes any descendent schemas that allOf inherit from self, any oneOf schemas, any anyOf schemas, any x-discriminator-values, and the discriminator mapping schemas in the OAS document AND Codegen validates that oneOf and anyOf schemas contain the required discriminator and throws an error if the discriminator is missing.</dd></dl>|true|
|logicalTypeTimeQuantifier|The quantifier for time-related logical types (`timestamp` and `local-timestamp`).|<dl><dt>**nanos**</dt><dd>nanoseconds</dd><dt>**micros**</dt><dd>microseconds</dd><dt>**millis**</dt><dd>milliseconds</dd></dl>|millis|
|packageName|package for generated classes (where supported)| |null|
|prependFormOrBodyParameters|Add form or body parameters to the beginning of the parameter list.| |false|
|sortModelPropertiesByRequiredFlag|Sort model properties to place required parameters before optional parameters.| |true|
|sortParamsByRequiredFlag|Sort method arguments to place required parameters before optional parameters.| |true|
|useLogicalTypes|Use logical types for fields, when matching OpenAPI types. Currently supported: `date-time`, `date`.| |false|
## IMPORT MAPPING

View File

@@ -1,190 +0,0 @@
---
title: Documentation for the erlang-server-deprecated Generator
---
## METADATA
| Property | Value | Notes |
| -------- | ----- | ----- |
| generator name | erlang-server-deprecated | pass this to the generate command after -g |
| generator stability | DEPRECATED | |
| generator type | SERVER | |
| generator language | Erlang | |
| generator default templating engine | mustache | |
| helpTxt | Generates an Erlang server library (deprecated) using OpenAPI Generator (https://openapi-generator.tech). By default, it will also generate service classes, which can be disabled with the `-Dnoservice` environment variable. | |
## CONFIG OPTIONS
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 |
| ------ | ----------- | ------ | ------- |
|openAPISpecName|Openapi Spec Name.| |openapi|
|packageName|Erlang package name (convention: lowercase).| |openapi|
## IMPORT MAPPING
| Type/Alias | Imports |
| ---------- | ------- |
## INSTANTIATION TYPES
| Type/Alias | Instantiated By |
| ---------- | --------------- |
## LANGUAGE PRIMITIVES
<ul class="column-ul">
</ul>
## RESERVED WORDS
<ul class="column-ul">
<li>after</li>
<li>and</li>
<li>andalso</li>
<li>band</li>
<li>begin</li>
<li>bnot</li>
<li>bor</li>
<li>bsl</li>
<li>bsr</li>
<li>bxor</li>
<li>case</li>
<li>catch</li>
<li>cond</li>
<li>div</li>
<li>end</li>
<li>fun</li>
<li>if</li>
<li>let</li>
<li>not</li>
<li>of</li>
<li>or</li>
<li>orelse</li>
<li>receive</li>
<li>rem</li>
<li>try</li>
<li>when</li>
<li>xor</li>
</ul>
## FEATURE SET
### Client Modification Feature
| Name | Supported | Defined By |
| ---- | --------- | ---------- |
|BasePath|✗|ToolingExtension
|Authorizations|✗|ToolingExtension
|UserAgent|✗|ToolingExtension
|MockServer|✗|ToolingExtension
### Data Type Feature
| Name | Supported | Defined By |
| ---- | --------- | ---------- |
|Custom|✗|OAS2,OAS3
|Int32|✓|OAS2,OAS3
|Int64|✓|OAS2,OAS3
|Float|✓|OAS2,OAS3
|Double|✓|OAS2,OAS3
|Decimal|✓|ToolingExtension
|String|✓|OAS2,OAS3
|Byte|✓|OAS2,OAS3
|Binary|✓|OAS2,OAS3
|Boolean|✓|OAS2,OAS3
|Date|✓|OAS2,OAS3
|DateTime|✓|OAS2,OAS3
|Password|✓|OAS2,OAS3
|File|✓|OAS2
|Uuid|✗|
|Array|✓|OAS2,OAS3
|Null|✗|OAS3
|AnyType|✗|OAS2,OAS3
|Object|✓|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
|allOf|✗|OAS2,OAS3
|anyOf|✗|OAS3
|oneOf|✗|OAS3
|not|✗|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
|SignatureAuth|✗|OAS3
|AWSV4Signature|✗|ToolingExtension
### Wire Format Feature
| Name | Supported | Defined By |
| ---- | --------- | ---------- |
|JSON|✓|OAS2,OAS3
|XML|✗|OAS2,OAS3
|PROTOBUF|✗|ToolingExtension
|Custom|✗|OAS2,OAS3

View File

@@ -59,7 +59,6 @@ These options may be applied as additional-properties (cli) or configOptions (pl
<li>fun</li>
<li>if</li>
<li>let</li>
<li>maybe</li>
<li>not</li>
<li>of</li>
<li>or</li>

View File

@@ -25,7 +25,6 @@ These options may be applied as additional-properties (cli) or configOptions (pl
|collectionType|Option. Collection type to use|<dl><dt>**array**</dt><dd>kotlin.Array</dd><dt>**list**</dt><dd>kotlin.collections.List</dd></dl>|list|
|dateLibrary|Option. Date library to use|<dl><dt>**threetenbp-localdatetime**</dt><dd>Threetenbp - Backport of JSR310 (jvm only, for legacy app only)</dd><dt>**kotlinx-datetime**</dt><dd>kotlinx-datetime (preferred for multiplatform)</dd><dt>**string**</dt><dd>String</dd><dt>**java8-localdatetime**</dt><dd>Java 8 native JSR310 (jvm only, for legacy app only)</dd><dt>**java8**</dt><dd>Java 8 native JSR310 (jvm only, preferred for jdk 1.8+)</dd><dt>**threetenbp**</dt><dd>Threetenbp - Backport of JSR310 (jvm only, preferred for jdk &lt; 1.8)</dd></dl>|java8|
|enumPropertyNaming|Naming convention for enum properties: 'camelCase', 'PascalCase', 'snake_case', 'UPPERCASE', and 'original'| |original|
|failOnUnknownProperties|Fail Jackson de-serialization on unknown properties| |false|
|generateOneOfAnyOfWrappers|Generate oneOf, anyOf schemas as wrappers.| |false|
|generateRoomModels|Generate Android Room database models in addition to API models (JVM Volley library only)| |false|
|groupId|Generated artifact package's organization (i.e. maven groupId).| |org.openapitools|

View File

@@ -256,10 +256,6 @@ public interface CodegenConfig {
void setSkipOperationExample(boolean skipOperationExample);
boolean isSkipSortingOperations();
void setSkipSortingOperations(boolean skipSortingOperations);
public boolean isHideGenerationTimestamp();
public void setHideGenerationTimestamp(boolean hideGenerationTimestamp);

View File

@@ -224,8 +224,6 @@ public class DefaultCodegen implements CodegenConfig {
@Getter @Setter
protected int removeOperationIdPrefixCount = 1;
protected boolean skipOperationExample;
// sort operations by default
protected boolean skipSortingOperations = false;
protected final static Pattern XML_MIME_PATTERN = Pattern.compile("(?i)application\\/(.*)[+]?xml(;.*)?");
protected final static Pattern JSON_MIME_PATTERN = Pattern.compile("(?i)application\\/json(;.*)?");
@@ -6156,16 +6154,6 @@ public class DefaultCodegen implements CodegenConfig {
this.skipOperationExample = skipOperationExample;
}
@Override
public boolean isSkipSortingOperations() {
return this.skipSortingOperations;
}
@Override
public void setSkipSortingOperations(boolean skipSortingOperations) {
this.skipSortingOperations = skipSortingOperations;
}
@Override
public boolean isHideGenerationTimestamp() {
return hideGenerationTimestamp;

View File

@@ -121,7 +121,6 @@ public class DefaultGenerator implements Generator {
this.opts = opts;
this.openAPI = opts.getOpenAPI();
this.config = opts.getConfig();
List<TemplateDefinition> userFiles = opts.getUserDefinedTemplates();
if (userFiles != null) {
this.userDefinedTemplates = Collections.unmodifiableList(userFiles);
@@ -681,10 +680,7 @@ public class DefaultGenerator implements Generator {
for (String tag : paths.keySet()) {
try {
List<CodegenOperation> ops = paths.get(tag);
if(!this.config.isSkipSortingOperations()) {
// sort operations by operationId
ops.sort((one, another) -> ObjectUtils.compare(one.operationId, another.operationId));
}
ops.sort((one, another) -> ObjectUtils.compare(one.operationId, another.operationId));
OperationsMap operation = processOperations(config, tag, ops, allModels);
URL url = URLPathUtils.getServerURL(openAPI, config.serverVariableOverrides());
operation.put("basePath", basePath);

View File

@@ -20,7 +20,6 @@ package org.openapitools.codegen;
import io.swagger.v3.oas.models.*;
import io.swagger.v3.oas.models.callbacks.Callback;
import io.swagger.v3.oas.models.headers.Header;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.media.*;
import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.parameters.RequestBody;
@@ -284,24 +283,10 @@ public class OpenAPINormalizer {
this.openAPI.getComponents().setSchemas(new HashMap<String, Schema>());
}
normalizeInfo();
normalizePaths();
normalizeComponentsSchemas();
}
/**
* Pre-populate info if it's not defined.
*/
private void normalizeInfo() {
if (this.openAPI.getInfo() == null) {
Info info = new Info();
info.setTitle("OpenAPI");
info.setVersion("0.0.1");
info.setDescription("OpenAPI");
this.openAPI.setInfo(info);
}
}
/**
* Normalizes inline models in Paths
*/

View File

@@ -684,28 +684,6 @@ public abstract class AbstractPhpCodegen extends DefaultCodegen implements Codeg
p.example = example;
}
@Override
protected void updateEnumVarsWithExtensions(List<Map<String, Object>> enumVars, Map<String, Object> vendorExtensions, String dataType) {
if (vendorExtensions != null) {
if (vendorExtensions.containsKey("x-enum-varnames")) {
List<String> values = (List<String>) vendorExtensions.get("x-enum-varnames");
int size = Math.min(enumVars.size(), values.size());
for (int i = 0; i < size; i++) {
enumVars.get(i).put("name", toEnumVarName(values.get(i), dataType));
}
}
if (vendorExtensions.containsKey("x-enum-descriptions")) {
List<String> values = (List<String>) vendorExtensions.get("x-enum-descriptions");
int size = Math.min(enumVars.size(), values.size());
for (int i = 0; i < size; i++) {
enumVars.get(i).put("enumDescription", values.get(i));
}
}
}
}
@Override
public String toEnumValue(String value, String datatype) {
if ("int".equals(datatype) || "float".equals(datatype)) {
@@ -726,11 +704,11 @@ public abstract class AbstractPhpCodegen extends DefaultCodegen implements Codeg
return enumNameMapping.get(name);
}
if (name.isEmpty()) {
if (name.length() == 0) {
return "EMPTY";
}
if (name.trim().isEmpty()) {
if (name.trim().length() == 0) {
return "SPACE_" + name.length();
}
@@ -741,12 +719,11 @@ public abstract class AbstractPhpCodegen extends DefaultCodegen implements Codeg
// number
if ("int".equals(datatype) || "float".equals(datatype)) {
if (name.matches("\\d.*")) { // starts with number
name = "NUMBER_" + name;
}
name = name.replaceAll("-", "MINUS_");
name = name.replaceAll("\\+", "PLUS_");
name = name.replaceAll("\\.", "_DOT_");
String varName = name;
varName = varName.replaceAll("-", "MINUS_");
varName = varName.replaceAll("\\+", "PLUS_");
varName = varName.replaceAll("\\.", "_DOT_");
return varName;
}
// string

View File

@@ -25,45 +25,15 @@ import org.openapitools.codegen.model.ModelsMap;
import java.io.File;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import static org.openapitools.codegen.utils.StringUtils.camelize;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import lombok.Getter;
import lombok.Setter;
public class AvroSchemaCodegen extends DefaultCodegen implements CodegenConfig {
private final Logger LOGGER = LoggerFactory.getLogger(AvroSchemaCodegen.class);
private static final String AVRO = "avro-schema";
/**
* See https://avro.apache.org/docs/++version++/specification/#logical-types
*/
public static final String USE_LOGICAL_TYPES = "useLogicalTypes";
public static final String USE_LOGICAL_TYPES_DESC = "Use logical types for fields, when matching OpenAPI types. Currently supported: `date-time`, `date`.";
/**
* See https://avro.apache.org/docs/++version++/specification/#timestamps
*/
public static final String LOGICAL_TYPES_TIME_QUANTIFIER = "logicalTypeTimeQuantifier";
public static final String LOGICAL_TYPES_TIME_QUANTIFIER_DESC = "The quantifier for time-related logical types (`timestamp` and `local-timestamp`).";
protected String packageName = "model";
@Getter @Setter
protected boolean useLogicalTypes = false; // this defaults to false for backwards compatibility
@Getter @Setter
protected String logicalTypeTimeQuantifier = "millis";
public AvroSchemaCodegen() {
super();
@@ -118,15 +88,6 @@ public class AvroSchemaCodegen extends DefaultCodegen implements CodegenConfig {
typeMapping.put("BigDecimal", "string");
cliOptions.add(new CliOption(CodegenConstants.PACKAGE_NAME, CodegenConstants.PACKAGE_NAME_DESC));
cliOptions.add(CliOption.newBoolean(USE_LOGICAL_TYPES, USE_LOGICAL_TYPES_DESC).defaultValue(Boolean.FALSE.toString()));
CliOption logicalTimeQuantifier = new CliOption(LOGICAL_TYPES_TIME_QUANTIFIER, LOGICAL_TYPES_TIME_QUANTIFIER_DESC).defaultValue(this.getLogicalTypeTimeQuantifier());
Map<String, String> timeQuantifierOptions = new HashMap<>();
timeQuantifierOptions.put("nanos", "nanoseconds");
timeQuantifierOptions.put("micros", "microseconds");
timeQuantifierOptions.put("millis", "milliseconds");
logicalTimeQuantifier.setEnum(timeQuantifierOptions);
cliOptions.add(logicalTimeQuantifier);
}
@Override
@@ -140,16 +101,6 @@ public class AvroSchemaCodegen extends DefaultCodegen implements CodegenConfig {
}
additionalProperties.put("packageName", packageName);
if (!convertPropertyToBooleanAndWriteBack(USE_LOGICAL_TYPES, this::setUseLogicalTypes)) {
// This sets the default if the option was not specified.
additionalProperties.put(USE_LOGICAL_TYPES, useLogicalTypes);
}
if (convertPropertyToStringAndWriteBack(LOGICAL_TYPES_TIME_QUANTIFIER, this::setLogicalTypeTimeQuantifier) == null) {
// This sets the default if the option was not specified.
additionalProperties.put(LOGICAL_TYPES_TIME_QUANTIFIER, logicalTypeTimeQuantifier);
}
}
@Override
@@ -197,44 +148,4 @@ public class AvroSchemaCodegen extends DefaultCodegen implements CodegenConfig {
return input;
}
@Override
protected List<Map<String, Object>> buildEnumVars(List<Object> values, String dataType) {
List<Object> sanitizedValues = values.stream().map(Object::toString).map(this::sanitizeEnumValue)
.collect(Collectors.toList());
removeEnumValueCollisions(sanitizedValues);
return super.buildEnumVars(sanitizedValues, dataType);
}
/**
* Valid enums in Avro need to adhere to [A-Za-z_][A-Za-z0-9_]*
* See https://avro.apache.org/docs/1.12.0/specification/#enums
*/
private String sanitizeEnumValue(String value) {
// Replace any non-alphanumeric characters with an underscore
String sanitizedValue = value.replaceAll("[^A-Za-z0-9_]", "_");
// If the enum starts with a number, prefix it with an underscore
sanitizedValue = sanitizedValue.replaceAll("^([0-9])", "_$1");
return sanitizedValue;
}
private void removeEnumValueCollisions(List<Object> values) {
Collections.reverse(values);
for (int i = 0; i < values.size(); i++) {
final String value = values.get(i).toString();
long count = values.stream().filter(v1 -> v1.equals(value)).count();
if (count > 1) {
String uniqueEnumValue = getUniqueEnumValue(value.toString(), values);
LOGGER.debug("Changing duplicate enumeration value from " + value + " to " + uniqueEnumValue);
values.set(i, uniqueEnumValue);
}
}
Collections.reverse(values);
}
private String getUniqueEnumValue(String value, List<Object> values) {
long count = values.stream().filter(v -> v.equals(value)).count();
return count > 1
? getUniqueEnumValue(value + count, values)
: value;
}
}

View File

@@ -99,9 +99,9 @@ public class ErlangServerCodegen extends DefaultCodegen implements CodegenConfig
*/
setReservedWordsLowerCase(
Arrays.asList(
"after", "and", "andalso", "band", "begin", "bnot", "bor", "bsl", "bsr", "bxor",
"case", "catch", "cond", "div", "end", "fun", "if", "let", "maybe", "not",
"of", "or", "orelse", "receive", "rem", "try", "when", "xor"
"after", "and", "andalso", "band", "begin", "bnot", "bor", "bsl", "bsr", "bxor", "case",
"catch", "cond", "div", "end", "fun", "if", "let", "not", "of", "or", "orelse", "receive",
"rem", "try", "when", "xor"
)
);
@@ -172,8 +172,10 @@ public class ErlangServerCodegen extends DefaultCodegen implements CodegenConfig
supportingFiles.add(new SupportingFile("router.mustache", "", toSourceFilePath("router", "erl")));
supportingFiles.add(new SupportingFile("api.mustache", "", toSourceFilePath("api", "erl")));
supportingFiles.add(new SupportingFile("server.mustache", "", toSourceFilePath("server", "erl")));
supportingFiles.add(new SupportingFile("utils.mustache", "", toSourceFilePath("utils", "erl")));
supportingFiles.add(new SupportingFile("auth.mustache", "", toSourceFilePath("auth", "erl")));
supportingFiles.add(new SupportingFile("openapi.mustache", "", toPrivFilePath(this.openApiSpecName, "json")));
supportingFiles.add(new SupportingFile("default_logic_handler.mustache", "", toSourceFilePath("default_logic_handler", "erl")));
supportingFiles.add(new SupportingFile("logic_handler.mustache", "", toSourceFilePath("logic_handler", "erl")));
supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")
.doNotOverwrite());
@@ -221,7 +223,7 @@ public class ErlangServerCodegen extends DefaultCodegen implements CodegenConfig
@Override
public String toApiName(String name) {
if (name.length() == 0) {
return this.packageName + "_handler";
return this.packageName + "_default_handler";
}
return this.packageName + "_" + underscore(name) + "_handler";
}

View File

@@ -1,337 +0,0 @@
/*
* Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech)
* Copyright 2018 SmartBear Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openapitools.codegen.languages;
import lombok.Setter;
import org.openapitools.codegen.*;
import org.openapitools.codegen.meta.features.*;
import org.openapitools.codegen.model.ModelMap;
import org.openapitools.codegen.model.OperationMap;
import org.openapitools.codegen.model.OperationsMap;
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 java.io.File;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import static org.openapitools.codegen.utils.StringUtils.camelize;
import static org.openapitools.codegen.utils.StringUtils.underscore;
public class ErlangServerDeprecatedCodegen extends DefaultCodegen implements CodegenConfig {
private final Logger LOGGER = LoggerFactory.getLogger(ErlangServerDeprecatedCodegen.class);
protected String apiVersion = "1.0.0";
protected String apiPath = "src";
@Setter protected String packageName = "openapi";
@Setter protected String openApiSpecName = "openapi";
public ErlangServerDeprecatedCodegen() {
super();
generatorMetadata = GeneratorMetadata.newBuilder(generatorMetadata).stability(Stability.DEPRECATED).build();
modifyFeatureSet(features -> features
.includeDocumentationFeatures(DocumentationFeature.Readme)
.wireFormatFeatures(EnumSet.of(WireFormatFeature.JSON))
.securityFeatures(EnumSet.of(
SecurityFeature.ApiKey,
SecurityFeature.OAuth2_Implicit
))
.excludeGlobalFeatures(
GlobalFeature.XMLStructureDefinitions,
GlobalFeature.Callbacks,
GlobalFeature.LinkObjects,
GlobalFeature.ParameterStyling
)
.excludeSchemaSupportFeatures(
SchemaSupportFeature.Polymorphism
)
.excludeParameterFeatures(
ParameterFeature.Cookie
)
);
// set the output folder here
outputFolder = "generated-code/erlang-server";
/**
* Models. You can write model files using the modelTemplateFiles map.
* if you want to create one template for file, you can do so here.
* for multiple files for model, just put another entry in the `modelTemplateFiles` with
* a different extension
*/
modelTemplateFiles.clear();
/**
* Api classes. You can write classes for each Api file with the apiTemplateFiles map.
* as with models, add multiple entries with different extensions for multiple files per
* class
*/
apiTemplateFiles.put(
"handler.mustache", // the template to use
".erl"); // the extension for each file to write
/**
* Template Location. This is the location which templates will be read from. The generator
* will use the resource stream to attempt to read the templates.
*/
embeddedTemplateDir = templateDir = "erlang-server-deprecated";
/**
* Reserved words. Override this with reserved words specific to your language
*/
setReservedWordsLowerCase(
Arrays.asList(
"after", "and", "andalso", "band", "begin", "bnot", "bor", "bsl", "bsr", "bxor", "case",
"catch", "cond", "div", "end", "fun", "if", "let", "not", "of", "or", "orelse", "receive",
"rem", "try", "when", "xor"
)
);
instantiationTypes.clear();
typeMapping.clear();
typeMapping.put("enum", "binary");
typeMapping.put("date", "date");
typeMapping.put("datetime", "datetime");
typeMapping.put("boolean", "boolean");
typeMapping.put("string", "binary");
typeMapping.put("integer", "integer");
typeMapping.put("int", "integer");
typeMapping.put("float", "integer");
typeMapping.put("long", "integer");
typeMapping.put("double", "float");
typeMapping.put("array", "list");
typeMapping.put("map", "map");
typeMapping.put("number", "integer");
typeMapping.put("bigdecimal", "float");
typeMapping.put("List", "list");
typeMapping.put("object", "object");
typeMapping.put("file", "file");
typeMapping.put("binary", "binary");
typeMapping.put("bytearray", "binary");
typeMapping.put("byte", "binary");
typeMapping.put("uuid", "binary");
typeMapping.put("uri", "binary");
typeMapping.put("password", "binary");
cliOptions.clear();
cliOptions.add(new CliOption(CodegenConstants.PACKAGE_NAME, "Erlang package name (convention: lowercase).")
.defaultValue(this.packageName));
cliOptions.add(new CliOption(CodegenConstants.OPEN_API_SPEC_NAME, "Openapi Spec Name.")
.defaultValue(this.openApiSpecName));
}
@Override
public void processOpts() {
super.processOpts();
if (additionalProperties.containsKey(CodegenConstants.PACKAGE_NAME)) {
setPackageName((String) additionalProperties.get(CodegenConstants.PACKAGE_NAME));
} else {
additionalProperties.put(CodegenConstants.PACKAGE_NAME, packageName);
}
if (additionalProperties.containsKey(CodegenConstants.OPEN_API_SPEC_NAME)) {
setOpenApiSpecName((String) additionalProperties.get(CodegenConstants.OPEN_API_SPEC_NAME));
} else {
additionalProperties.put(CodegenConstants.OPEN_API_SPEC_NAME, openApiSpecName);
}
/**
* Additional Properties. These values can be passed to the templates and
* are available in models, apis, and supporting files
*/
additionalProperties.put("apiVersion", apiVersion);
additionalProperties.put("apiPath", apiPath);
/**
* Supporting Files. You can write single files for the generator with the
* entire object tree available. If the input file has a suffix of `.mustache
* it will be processed by the template engine. Otherwise, it will be copied
*/
supportingFiles.add(new SupportingFile("rebar.config.mustache", "", "rebar.config"));
supportingFiles.add(new SupportingFile("app.src.mustache", "", "src" + File.separator + this.packageName + ".app.src"));
supportingFiles.add(new SupportingFile("router.mustache", "", toSourceFilePath("router", "erl")));
supportingFiles.add(new SupportingFile("api.mustache", "", toSourceFilePath("api", "erl")));
supportingFiles.add(new SupportingFile("server.mustache", "", toSourceFilePath("server", "erl")));
supportingFiles.add(new SupportingFile("utils.mustache", "", toSourceFilePath("utils", "erl")));
supportingFiles.add(new SupportingFile("auth.mustache", "", toSourceFilePath("auth", "erl")));
supportingFiles.add(new SupportingFile("openapi.mustache", "", toPrivFilePath(this.openApiSpecName, "json")));
supportingFiles.add(new SupportingFile("default_logic_handler.mustache", "", toSourceFilePath("default_logic_handler", "erl")));
supportingFiles.add(new SupportingFile("logic_handler.mustache", "", toSourceFilePath("logic_handler", "erl")));
supportingFiles.add(new SupportingFile("README.mustache", "", "README.md")
.doNotOverwrite());
}
@Override
public String apiPackage() {
return apiPath;
}
/**
* Configures the type of generator.
*
* @return the CodegenType for this generator
* @see org.openapitools.codegen.CodegenType
*/
@Override
public CodegenType getTag() {
return CodegenType.SERVER;
}
/**
* Configures a friendly name for the generator. This will be used by the generator
* to select the library with the -g flag.
*
* @return the friendly name for the generator
*/
@Override
public String getName() {
return "erlang-server-deprecated";
}
/**
* Returns human-friendly help for the generator. Provide the consumer with help
* tips, parameters here
*
* @return A string value for the help message
*/
@Override
public String getHelp() {
return "Generates an Erlang server library (deprecated) using OpenAPI Generator (https://openapi-generator.tech). By default, " +
"it will also generate service classes, which can be disabled with the `-Dnoservice` environment variable.";
}
@Override
public String toApiName(String name) {
if (name.length() == 0) {
return this.packageName + "_default_handler";
}
return this.packageName + "_" + underscore(name) + "_handler";
}
/**
* Escapes a reserved word as defined in the `reservedWords` array. Handle escaping
* those terms here. This logic is only called if a variable matches the reserved words
*
* @return the escaped term
*/
@Override
public String escapeReservedWord(String name) {
if (this.reservedWordsMappings().containsKey(name)) {
return this.reservedWordsMappings().get(name);
}
return "_" + name;
}
/**
* Location to write api files. You can use the apiPackage() as defined when the class is
* instantiated
*/
@Override
public String apiFileFolder() {
return outputFolder + File.separator + apiPackage().replace('.', File.separatorChar);
}
@Override
public String toModelName(String name) {
return camelize(toModelFilename(name));
}
@Override
public String toOperationId(String operationId) {
// method name cannot use reserved keyword, e.g. return
if (isReservedWord(operationId)) {
LOGGER.warn("{} (reserved word) cannot be used as method name. Renamed to {}", operationId, camelize(sanitizeName("call_" + operationId)));
operationId = "call_" + operationId;
}
return camelize(operationId);
}
@Override
public String toApiFilename(String name) {
return toHandlerName(name);
}
@Override
public OperationsMap postProcessOperationsWithModels(OperationsMap objs, List<ModelMap> allModels) {
OperationMap operations = objs.getOperations();
List<CodegenOperation> operationList = operations.getOperation();
for (CodegenOperation op : operationList) {
if (op.path != null) {
op.path = op.path.replaceAll("\\{(.*?)\\}", ":$1");
}
}
return objs;
}
@Override
public Map<String, Object> postProcessSupportingFileData(Map<String, Object> objs) {
generateJSONSpecFile(objs);
return super.postProcessSupportingFileData(objs);
}
protected String toHandlerName(String name) {
return toModuleName(name) + "_handler";
}
protected String toModuleName(String name) {
return this.packageName + "_" + underscore(name.replaceAll("-", "_"));
}
protected String toSourceFilePath(String name, String extension) {
return "src" + File.separator + toModuleName(name) + "." + extension;
}
protected String toIncludeFilePath(String name, String extension) {
return "include" + File.separator + toModuleName(name) + "." + extension;
}
protected String toPrivFilePath(String name, String extension) {
return "priv" + File.separator + name + "." + extension;
}
@Override
public String escapeQuotationMark(String input) {
// remove ' to avoid code injection
return input.replace("'", "");
}
@Override
public String escapeUnsafeCharacters(String input) {
// ref: http://stackoverflow.com/a/30421295/677735
return input.replace("-ifdef", "- if def").replace("-endif", "- end if");
}
@Override
public String addRegularExpressionDelimiter(String pattern) {
return pattern;
}
@Override
public GeneratorLanguage generatorLanguage() { return GeneratorLanguage.ERLANG; }
}

View File

@@ -66,9 +66,6 @@ public class GoServerCodegen extends AbstractGoCodegen {
public GoServerCodegen() {
super();
// skip sorting of operations to preserve the order found in the OpenAPI spec file
super.setSkipSortingOperations(true);
modifyFeatureSet(features -> features
.includeDocumentationFeatures(DocumentationFeature.Readme)
.wireFormatFeatures(EnumSet.of(WireFormatFeature.JSON, WireFormatFeature.XML))

View File

@@ -85,7 +85,6 @@ public class KotlinClientCodegen extends AbstractKotlinCodegen {
public static final String DATE_LIBRARY = "dateLibrary";
public static final String REQUEST_DATE_CONVERTER = "requestDateConverter";
public static final String COLLECTION_TYPE = "collectionType";
public static final String FAIL_ON_UNKNOWN_PROPERTIES = "failOnUnknownProperties";
public static final String MOSHI_CODE_GEN = "moshiCodeGen";
@@ -109,7 +108,6 @@ public class KotlinClientCodegen extends AbstractKotlinCodegen {
@Setter protected String roomModelPackage = "";
@Setter protected boolean omitGradleWrapper = false;
@Setter protected boolean generateOneOfAnyOfWrappers = true;
@Getter @Setter protected boolean failOnUnknownProperties = false;
protected String authFolder;
@@ -261,7 +259,6 @@ public class KotlinClientCodegen extends AbstractKotlinCodegen {
cliOptions.add(CliOption.newBoolean(IDEA, "Add IntellJ Idea plugin and mark Kotlin main and test folders as source folders."));
cliOptions.add(CliOption.newBoolean(MOSHI_CODE_GEN, "Whether to enable codegen with the Moshi library. Refer to the [official Moshi doc](https://github.com/square/moshi#codegen) for more info."));
cliOptions.add(CliOption.newBoolean(FAIL_ON_UNKNOWN_PROPERTIES, "Fail Jackson de-serialization on unknown properties", false));
cliOptions.add(CliOption.newBoolean(NULLABLE_RETURN_TYPE, "Nullable return type"));
@@ -441,13 +438,6 @@ public class KotlinClientCodegen extends AbstractKotlinCodegen {
setGenerateOneOfAnyOfWrappers(Boolean.parseBoolean(additionalProperties.get(GENERATE_ONEOF_ANYOF_WRAPPERS).toString()));
}
if (additionalProperties.containsKey(FAIL_ON_UNKNOWN_PROPERTIES)) {
setFailOnUnknownProperties(Boolean.parseBoolean(additionalProperties.get(FAIL_ON_UNKNOWN_PROPERTIES).toString()));
} else {
additionalProperties.put(FAIL_ON_UNKNOWN_PROPERTIES, false);
setFailOnUnknownProperties(false);
}
commonSupportingFiles();
switch (getLibrary()) {
@@ -593,15 +583,12 @@ public class KotlinClientCodegen extends AbstractKotlinCodegen {
typeMapping.put("date-time", "Instant");
typeMapping.put("date", "LocalDate");
typeMapping.put("time", "LocalTime");
typeMapping.put("DateTime", "Instant");
typeMapping.put("Date", "LocalDate");
typeMapping.put("Time", "LocalTime");
importMapping.put("Instant", "kotlinx.datetime.Instant");
importMapping.put("LocalDate", "kotlinx.datetime.LocalDate");
importMapping.put("LocalTime", "kotlinx.datetime.LocalTime");
}
private void processJVMRetrofit2Library(String infrastructureFolder) {
@@ -667,7 +654,7 @@ public class KotlinClientCodegen extends AbstractKotlinCodegen {
supportingFiles.add(new SupportingFile("jvm-common/infrastructure/LocalDateAdapter.kt.mustache", infrastructureFolder, "LocalDateAdapter.kt"));
supportingFiles.add(new SupportingFile("jvm-common/infrastructure/LocalDateTimeAdapter.kt.mustache", infrastructureFolder, "LocalDateTimeAdapter.kt"));
supportingFiles.add(new SupportingFile("jvm-common/infrastructure/OffsetDateTimeAdapter.kt.mustache", infrastructureFolder, "OffsetDateTimeAdapter.kt"));
addKotlinxDateTimeAdapters(infrastructureFolder);
addKotlinxDateTimeInstantAdapter(infrastructureFolder);
supportingFiles.add(new SupportingFile("jvm-common/infrastructure/BigDecimalAdapter.kt.mustache", infrastructureFolder, "BigDecimalAdapter.kt"));
supportingFiles.add(new SupportingFile("jvm-common/infrastructure/BigIntegerAdapter.kt.mustache", infrastructureFolder, "BigIntegerAdapter.kt"));
supportingFiles.add(new SupportingFile("jvm-common/infrastructure/URIAdapter.kt.mustache", infrastructureFolder, "URIAdapter.kt"));
@@ -678,7 +665,7 @@ public class KotlinClientCodegen extends AbstractKotlinCodegen {
supportingFiles.add(new SupportingFile("jvm-common/infrastructure/LocalDateAdapter.kt.mustache", infrastructureFolder, "LocalDateAdapter.kt"));
supportingFiles.add(new SupportingFile("jvm-common/infrastructure/LocalDateTimeAdapter.kt.mustache", infrastructureFolder, "LocalDateTimeAdapter.kt"));
supportingFiles.add(new SupportingFile("jvm-common/infrastructure/OffsetDateTimeAdapter.kt.mustache", infrastructureFolder, "OffsetDateTimeAdapter.kt"));
addKotlinxDateTimeAdapters(infrastructureFolder);
addKotlinxDateTimeInstantAdapter(infrastructureFolder);
break;
case jackson:
@@ -702,10 +689,9 @@ public class KotlinClientCodegen extends AbstractKotlinCodegen {
}
}
private void addKotlinxDateTimeAdapters(final String infrastructureFolder) {
private void addKotlinxDateTimeInstantAdapter(final String infrastructureFolder) {
if (DateLibrary.KOTLINX_DATETIME.value.equals(dateLibrary)) {
supportingFiles.add(new SupportingFile("jvm-common/infrastructure/InstantAdapter.kt.mustache", infrastructureFolder, "InstantAdapter.kt"));
supportingFiles.add(new SupportingFile("jvm-common/infrastructure/LocalTimeAdapter.kt.mustache", infrastructureFolder, "LocalTimeAdapter.kt"));
}
}

View File

@@ -374,4 +374,22 @@ public class PhpLaravelServerCodegen extends AbstractPhpCodegen {
public String toEnumDefaultValue(String value, String datatype) {
return datatype + "::" + value;
}
@Override
public String toEnumVarName(String value, String datatype) {
if (value.length() == 0) {
return super.toEnumVarName(value, datatype);
}
// number
if ("int".equals(datatype) || "float".equals(datatype)) {
String varName = "NUMBER_" + value;
varName = varName.replaceAll("-", "MINUS_");
varName = varName.replaceAll("\\+", "PLUS_");
varName = varName.replaceAll("\\.", "_DOT_");
return varName;
}
return super.toEnumVarName(value, datatype);
}
}

View File

@@ -1063,48 +1063,39 @@ public class RustServerCodegen extends AbstractRustCodegen implements CodegenCon
public String getTypeDeclaration(Schema p) {
LOGGER.trace("Getting type declaration for schema");
String type;
if (ModelUtils.isArraySchema(p)) {
ArraySchema ap = (ArraySchema) p;
Schema inner = ap.getItems();
String innerType = getTypeDeclaration(inner);
type = typeMapping.get("array") + "<" + innerType + ">";
return typeMapping.get("array") + "<" + innerType + ">";
} else if (ModelUtils.isMapSchema(p)) {
Schema inner = ModelUtils.getAdditionalProperties(p);
String innerType = getTypeDeclaration(inner);
StringBuilder typeDeclaration = new StringBuilder(typeMapping.get("map")).append("<").append(typeMapping.get("string")).append(", ");
typeDeclaration.append(innerType).append(">");
type = typeDeclaration.toString();
return typeDeclaration.toString();
} else if (!StringUtils.isEmpty(p.get$ref())) {
String dataType;
try {
type = modelFromSchema(p);
dataType = modelFromSchema(p);
if (type != null) {
type = "models::" + type;
LOGGER.debug("Returning " + type + " from ref");
if (dataType != null) {
dataType = "models::" + dataType;
LOGGER.debug("Returning " + dataType + " from ref");
}
} catch (Exception e) {
type = null;
dataType = null;
LOGGER.error("Error obtaining the datatype from schema (model): " + p + ". Error was: " + e.getMessage(), e);
}
LOGGER.debug("Returning " + dataType);
return dataType;
} else if (p instanceof FileSchema) {
type = typeMapping.get("File").toString();
} else {
type = super.getTypeDeclaration(p);
return typeMapping.get("File").toString();
}
// We are using extrinsic nullability, rather than intrinsic, so we need to dig into the inner
// layer of the referenced schema.
Schema rp = ModelUtils.getReferencedSchema(openAPI, p);
if (rp.getNullable() == Boolean.TRUE) {
type = "swagger::Nullable<" + type + ">";
}
LOGGER.debug("Returning " + type + " for type declaration");
return type;
return super.getTypeDeclaration(p);
}
@Override
@@ -1409,19 +1400,6 @@ public class RustServerCodegen extends AbstractRustCodegen implements CodegenCon
return "swagger::AnyOf" + types.size() + "<" + String.join(",", types) + ">";
}
/**
* Strip a swagger::Nullable wrapper on a datatype
*
* @deprecated Avoid using this - use a different mechanism instead.
*/
private static String stripNullable(String type) {
if (type.startsWith("swagger::Nullable<") && type.endsWith(">")) {
return type.substring("swagger::Nullable<".length(), type.length() - 1);
} else {
return type;
}
}
@Override
public String toAllOfName(List<String> names, Schema composedSchema) {
// Handle all of objects as freeform
@@ -1431,9 +1409,7 @@ public class RustServerCodegen extends AbstractRustCodegen implements CodegenCon
@Override
public void postProcessModelProperty(CodegenModel model, CodegenProperty property) {
super.postProcessModelProperty(model, property);
// TODO: We should avoid reverse engineering primitive type status from the data type
if (!languageSpecificPrimitives.contains(stripNullable(property.dataType))) {
if (!languageSpecificPrimitives.contains(property.dataType)) {
// If we use a more qualified model name, then only camelize the actual type, not the qualifier.
if (property.dataType.contains(":")) {
int position = property.dataType.lastIndexOf(":");
@@ -1441,7 +1417,7 @@ public class RustServerCodegen extends AbstractRustCodegen implements CodegenCon
} else {
property.dataType = camelize(property.dataType);
}
property.isPrimitiveType = property.isContainer && languageSpecificPrimitives.contains(typeMapping.get(stripNullable(property.complexType)));
property.isPrimitiveType = property.isContainer && languageSpecificPrimitives.contains(typeMapping.get(property.complexType));
} else {
property.isPrimitiveType = true;
}

View File

@@ -111,6 +111,7 @@ public class TypeScriptClientCodegen extends AbstractTypeScriptClientCodegen imp
// Typescript reserved words
"constructor"));
typeMapping.put("List", "Array");
typeMapping.put("object", "any");
typeMapping.put("DateTime", "Date");

View File

@@ -99,7 +99,7 @@ public class {{classname}} extends BaseApi {
{{/required}}{{/allParams}}
// create path and map variables
String localVarPath = "{{{path}}}"{{#pathParams}}
.replaceAll("\\{" + "{{baseName}}" + "\\}", apiClient.escapeString(apiClient.parameterToString({{{paramName}}}))){{/pathParams}};
.replaceAll("\\{" + "{{baseName}}" + "\\}", apiClient.escapeString({{{paramName}}}.toString())){{/pathParams}};
StringJoiner localVarQueryStringJoiner = new StringJoiner("&");
String localVarQueryParameterBaseName;

View File

@@ -1356,11 +1356,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} {
} else if ("PUT".equals(method)) {
response = invocationBuilder.put(entity);
} else if ("DELETE".equals(method)) {
if ("".equals(entity.getEntity())) {
response = invocationBuilder.method("DELETE");
} else {
response = invocationBuilder.method("DELETE", entity);
}
response = invocationBuilder.method("DELETE", entity);
} else if ("PATCH".equals(method)) {
response = invocationBuilder.method("PATCH", entity);
} else {

View File

@@ -1356,11 +1356,7 @@ public class ApiClient{{#jsr310}} extends JavaTimeFormatter{{/jsr310}} {
} else if ("PUT".equals(method)) {
response = invocationBuilder.put(entity);
} else if ("DELETE".equals(method)) {
if ("".equals(entity.getEntity())) {
response = invocationBuilder.method("DELETE");
} else {
response = invocationBuilder.method("DELETE", entity);
}
response = invocationBuilder.method("DELETE", entity);
} else if ("PATCH".equals(method)) {
response = invocationBuilder.method("PATCH", entity);
} else {

View File

@@ -90,7 +90,7 @@ import {{{invokerPackage}}}.ApiClient;
import {{{invokerPackage}}}.ApiException;
import {{{invokerPackage}}}.Configuration;{{#hasAuthMethods}}
import {{{invokerPackage}}}.auth.*;{{/hasAuthMethods}}
import {{{modelPackage}}}.*;
import {{{invokerPackage}}}.models.*;
import {{{package}}}.{{{classname}}};
public class Example {

View File

@@ -11,18 +11,18 @@
<maven.compiler.target>${java.version}</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
{{#springDocDocumentationProvider}}
<springdoc.version>2.6.0</springdoc.version>
<springdoc.version>2.2.0</springdoc.version>
{{/springDocDocumentationProvider}}
{{^springDocDocumentationProvider}}
{{#swagger2AnnotationLibrary}}
<swagger-annotations.version>2.2.22</swagger-annotations.version>
<swagger-annotations.version>2.2.15</swagger-annotations.version>
{{/swagger2AnnotationLibrary}}
{{/springDocDocumentationProvider}}
{{#useSwaggerUI}}
<swagger-ui.version>5.17.14</swagger-ui.version>
<swagger-ui.version>5.3.1</swagger-ui.version>
{{/useSwaggerUI}}
{{#virtualService}}
<virtualan.version>2.5.5</virtualan.version>
<virtualan.version>2.5.2</virtualan.version>
{{/virtualService}}
</properties>
{{#parentOverridden}}

View File

@@ -30,7 +30,6 @@ org.openapitools.codegen.languages.ElmClientCodegen
org.openapitools.codegen.languages.ErlangClientCodegen
org.openapitools.codegen.languages.ErlangProperCodegen
org.openapitools.codegen.languages.ErlangServerCodegen
org.openapitools.codegen.languages.ErlangServerDeprecatedCodegen
org.openapitools.codegen.languages.FsharpFunctionsServerCodegen
org.openapitools.codegen.languages.FsharpGiraffeServerCodegen
org.openapitools.codegen.languages.GoClientCodegen

View File

@@ -1 +0,0 @@
{{#useLogicalTypes}}{{#isDate}}{ "type": "int", "logicalType": "date" }{{/isDate}}{{#isDateTime}}{ "type": "long", "logicalType": "timestamp-{{{logicalTypeTimeQuantifier}}}" }{{/isDateTime}}{{^isDate}}{{^isDateTime}}"{{{dataType}}}"{{/isDateTime}}{{/isDate}}{{/useLogicalTypes}}{{^useLogicalTypes}}"{{{dataType}}}"{{/useLogicalTypes}}

View File

@@ -1 +1 @@
{{^isEnum}}{{^isContainer}}{{#isPrimitiveType}}{{>dataType}}{{/isPrimitiveType}}{{^isPrimitiveType}}"{{package}}.{{dataType}}"{{/isPrimitiveType}}{{/isContainer}}{{#isContainer}}{{>typeArray}}{{/isContainer}}{{/isEnum}}{{#isEnum}}{{>typeEnum}}{{/isEnum}}
{{^isEnum}}{{^isContainer}}{{#isPrimitiveType}}"{{dataType}}"{{/isPrimitiveType}}{{^isPrimitiveType}}"{{package}}.{{dataType}}"{{/isPrimitiveType}}{{/isContainer}}{{#isContainer}}{{>typeArray}}{{/isContainer}}{{/isEnum}}{{#isEnum}}{{>typeEnum}}{{/isEnum}}

View File

@@ -46,12 +46,7 @@ class {{{classname}}} {
{{#defaultValue}}defaultValue: {{{defaultValue}}},{{/defaultValue}}
name: r'{{{baseName}}}',
required: {{#required}}true{{/required}}{{^required}}false{{/required}},
includeIfNull: {{#required}}{{#isNullable}}true{{/isNullable}}{{^isNullable}}false{{/isNullable}}{{/required}}{{^required}}false{{/required}},
{{#isEnumOrRef}}
{{#enumUnknownDefaultCase}}
unknownEnumValue: {{{datatypeWithEnum}}}.unknownDefaultOpenApi,
{{/enumUnknownDefaultCase}}
{{/isEnumOrRef}}
includeIfNull: {{#required}}{{#isNullable}}true{{/isNullable}}{{^isNullable}}false{{/isNullable}}{{/required}}{{^required}}false{{/required}}
)
{{/isBinary}}
{{#isBinary}}

View File

@@ -52,13 +52,13 @@ final _regMap = RegExp(r'^Map<String,(.*)>$');
.toSet() as ReturnType;
}
if (value is Map && (match = _regMap.firstMatch(targetType)) != null) {
targetType = match![1]!.trim(); // ignore: parameter_assignments
return Map<String, BaseType>.fromIterables(
value.keys as Iterable<String>,
targetType = match![1]!; // ignore: parameter_assignments
return Map<dynamic, BaseType>.fromIterables(
value.keys,
value.values.map((dynamic v) => deserialize<BaseType, BaseType>(v, targetType, growable: growable)),
) as ReturnType;
}
break;
}
}
throw Exception('Cannot deserialize');
}

View File

@@ -6,21 +6,9 @@
enum {{{ enumName }}} {
{{#allowableValues}}
{{#enumVars}}
{{^isNull}}
{{#description}}
/// {{{.}}}
{{/description}}
@JsonValue({{#isString}}r{{/isString}}{{{value}}})
{{{name}}}({{^isString}}'{{/isString}}{{#isString}}r{{/isString}}{{{value}}}{{^isString}}'{{/isString}}){{^-last}},{{/-last}}{{#-last}};{{/-last}}
{{/isNull}}
@JsonValue({{#isString}}r{{/isString}}{{{value}}})
{{{name}}},
{{/enumVars}}
{{/allowableValues}}
const {{{enumName}}}(this.value);
final String value;
@override
String toString() => value;
}
{{/enumName}}

View File

@@ -1,57 +0,0 @@
# OpenAPI server library for Erlang
## Overview
An Erlang server stub generated by [OpenAPI Generator](https://openapi-generator.tech) given an OpenAPI spec.
Dependency: [Cowboy](https://github.com/ninenines/cowboy)
## Prerequisites
TODO
## Getting started
Use erlang-server with erlang.mk
1, Create an application by using erlang.mk
$ mkdir http_server
$ cd http_server
$ wget https://erlang.mk/erlang.mk
$ make -f erlang.mk bootstrap bootstrap-rel
$ make run
2, Modify the Makefile in the http_server directory to the following to introduce the dependency library:
PROJECT = http_server
PROJECT_DESCRIPTION = New project
PROJECT_VERSION = 0.1.0
DEPS = cowboy jesse jsx
dep_cowboy_commit = 2.5.0
dep_jesse_commit = 1.5.2
dep_jsx_commit = 2.9.0
DEP_PLUGINS = cowboy jesse jsx
PACKAGES += rfc3339
pkg_rfc3339_name = rfc3339
pkg_rfc3339_description = an erlang/elixir rfc3339 lib
pkg_rfc3339_homepage = https://github.com/talentdeficit/rfc3339
pkg_rfc3339_fetch = git
pkg_rfc3339_repo = https://github.com/talentdeficit/rfc3339
pkg_rfc3339_commit = master
include erlang.mk
3, Generate erlang-server project using openapi-generator
https://github.com/OpenAPITools/openapi-generator#2---getting-started
4, Copy erlang-server file to http_server project,Don't forget the 'priv' folder.
5, Start in the http_server project:
1, Introduce the following line in the http_server_app:start(_Type, _Args) function
openapi_server:start(http_server, #{ip=>{127,0,0,1}, port=>8080, net_opts=>[]})
2, Compilation http_server project
$ make
3, Start erlang virtual machine
$erl -pa ./deps/cowboy/ebin -pa ./deps/cowlib/ebin -pa ./deps/ranch/ebin -pa ./deps/jsx/ebin -pa ./deps/jesse/ebin -pa ./deps/rfc3339/ebin -pa ./ebin
4, Start project
application:ensure_all_started(http_server).

View File

@@ -1,363 +0,0 @@
-module({{packageName}}_api).
-export([request_params/1]).
-export([request_param_info/2]).
-export([populate_request/3]).
-export([validate_response/4]).
%% exported to silence openapi complains
-export([get_value/3, validate_response_body/4]).
-type operation_id() :: atom().
-type request_param() :: atom().
-export_type([operation_id/0]).
-spec request_params(OperationID :: operation_id()) -> [Param :: request_param()].
{{#apiInfo}}{{#apis}}
{{#operations}}{{#operation}}
request_params('{{operationId}}') ->
[{{#allParams}}{{^isBodyParam}}
'{{baseName}}'{{/isBodyParam}}{{#isBodyParam}}
'{{dataType}}'{{/isBodyParam}}{{^-last}},{{/-last}}{{/allParams}}
];
{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
request_params(_) ->
error(unknown_operation).
-type rule() ::
{type, 'binary'} |
{type, 'integer'} |
{type, 'float'} |
{type, 'binary'} |
{type, 'boolean'} |
{type, 'date'} |
{type, 'datetime'} |
{enum, [atom()]} |
{max, Max :: number()} |
{exclusive_max, Max :: number()} |
{min, Min :: number()} |
{exclusive_min, Min :: number()} |
{max_length, MaxLength :: integer()} |
{min_length, MaxLength :: integer()} |
{pattern, Pattern :: string()} |
schema |
required |
not_required.
-spec request_param_info(OperationID :: operation_id(), Name :: request_param()) -> #{
source => qs_val | binding | header | body,
rules => [rule()]
}.
{{#apiInfo}}{{#apis}}
{{#operations}}{{#operation}}{{#allParams}}
request_param_info('{{operationId}}', {{^isBodyParam}}'{{baseName}}'{{/isBodyParam}}{{#isBodyParam}}'{{dataType}}'{{/isBodyParam}}) ->
#{
source => {{#isQueryParam}}qs_val{{/isQueryParam}} {{#isPathParam}}binding{{/isPathParam}} {{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}{{#isFormParam}}body{{/isFormParam}},
rules => [{{#isString}}
{type, 'binary'},{{/isString}}{{#isInteger}}
{type, 'integer'},{{/isInteger}}{{#isLong}}
{type, 'integer'},{{/isLong}}{{#isFloat}}
{type, 'float'},{{/isFloat}}{{#isDouble}}
{type, 'float'},{{/isDouble}}{{#isByteArray}}
{type, 'binary'},{{/isByteArray}}{{#isBinary}}
{type, 'binary'},{{/isBinary}}{{#isBoolean}}
{type, 'boolean'},{{/isBoolean}}{{#isDate}}
{type, 'date'},{{/isDate}}{{#isDateTime}}
{type, 'datetime'},{{/isDateTime}}{{#isEnum}}
{enum, [{{#allowableValues}}{{#values}}'{{.}}'{{^-last}}, {{/-last}}{{/values}}{{/allowableValues}}] },{{/isEnum}}{{#maximum}}
{max, {{maximum}} }, {{/maximum}}{{#exclusiveMaximum}}
{exclusive_max, {{exclusiveMaximum}} },{{/exclusiveMaximum}}{{#minimum}}
{min, {{minimum}} },{{/minimum}}{{#exclusiveMinimum}}
{exclusive_min, {{exclusiveMinimum}} },{{/exclusiveMinimum}}{{#maxLength}}
{max_length, {{maxLength}} },{{/maxLength}}{{#minLength}}
{min_length, {{minLength}} },{{/minLength}}{{#pattern}}
{pattern, "{{{pattern}}}" },{{/pattern}}{{#isBodyParam}}
schema,{{/isBodyParam}}{{#required}}
required{{/required}}{{^required}}
not_required{{/required}}
]
};
{{/allParams}}{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
request_param_info(OperationID, Name) ->
error({unknown_param, OperationID, Name}).
-spec populate_request(
OperationID :: operation_id(),
Req :: cowboy_req:req(),
ValidatorState :: jesse_state:state()
) ->
{ok, Model :: #{}, Req :: cowboy_req:req()} |
{error, Reason :: any(), Req :: cowboy_req:req()}.
populate_request(OperationID, Req, ValidatorState) ->
Params = request_params(OperationID),
populate_request_params(OperationID, Params, Req, ValidatorState, #{}).
populate_request_params(_, [], Req, _, Model) ->
{ok, Model, Req};
populate_request_params(OperationID, [FieldParams | T], Req0, ValidatorState, Model) ->
case populate_request_param(OperationID, FieldParams, Req0, ValidatorState) of
{ok, K, V, Req} ->
populate_request_params(OperationID, T, Req, ValidatorState, maps:put(K, V, Model));
Error ->
Error
end.
populate_request_param(OperationID, Name, Req0, ValidatorState) ->
#{rules := Rules, source := Source} = request_param_info(OperationID, Name),
case get_value(Source, Name, Req0) of
{error, Reason, Req} ->
{error, Reason, Req};
{Value, Req} ->
case prepare_param(Rules, Name, Value, ValidatorState) of
{ok, Result} -> {ok, Name, Result, Req};
{error, Reason} ->
{error, Reason, Req}
end
end.
-spec validate_response(
OperationID :: operation_id(),
Code :: 200..599,
Body :: jesse:json_term(),
ValidatorState :: jesse_state:state()
) -> ok | no_return().
{{#apiInfo}}{{#apis}}
{{#operations}}{{#operation}}
{{#responses}}
validate_response('{{operationId}}', {{code}}, Body, ValidatorState) ->
validate_response_body('{{dataType}}', '{{baseType}}', Body, ValidatorState);
{{/responses}}
{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
validate_response(_OperationID, _Code, _Body, _ValidatorState) ->
ok.
validate_response_body('list', ReturnBaseType, Body, ValidatorState) ->
[
validate(schema, ReturnBaseType, Item, ValidatorState)
|| Item <- Body];
validate_response_body(_, ReturnBaseType, Body, ValidatorState) ->
validate(schema, ReturnBaseType, Body, ValidatorState).
%%%
validate(Rule = required, Name, Value, _ValidatorState) ->
case Value of
undefined -> validation_error(Rule, Name);
_ -> ok
end;
validate(not_required, _Name, _Value, _ValidatorState) ->
ok;
validate(_, _Name, undefined, _ValidatorState) ->
ok;
validate(Rule = {type, 'integer'}, Name, Value, _ValidatorState) ->
try
{ok, {{packageName}}_utils:to_int(Value)}
catch
error:badarg ->
validation_error(Rule, Name)
end;
validate(Rule = {type, 'float'}, Name, Value, _ValidatorState) ->
try
{ok, {{packageName}}_utils:to_float(Value)}
catch
error:badarg ->
validation_error(Rule, Name)
end;
validate(Rule = {type, 'binary'}, Name, Value, _ValidatorState) ->
case is_binary(Value) of
true -> ok;
false -> validation_error(Rule, Name)
end;
validate(_Rule = {type, 'boolean'}, _Name, Value, _ValidatorState) when is_boolean(Value) ->
{ok, Value};
validate(Rule = {type, 'boolean'}, Name, Value, _ValidatorState) ->
V = binary_to_lower(Value),
try
case binary_to_existing_atom(V, utf8) of
B when is_boolean(B) -> {ok, B};
_ -> validation_error(Rule, Name)
end
catch
error:badarg ->
validation_error(Rule, Name)
end;
validate(Rule = {type, 'date'}, Name, Value, _ValidatorState) ->
case is_binary(Value) of
true -> ok;
false -> validation_error(Rule, Name)
end;
validate(Rule = {type, 'datetime'}, Name, Value, _ValidatorState) ->
case is_binary(Value) of
true -> ok;
false -> validation_error(Rule, Name)
end;
validate(Rule = {enum, Values}, Name, Value, _ValidatorState) ->
try
FormattedValue = erlang:binary_to_existing_atom(Value, utf8),
case lists:member(FormattedValue, Values) of
true -> {ok, FormattedValue};
false -> validation_error(Rule, Name)
end
catch
error:badarg ->
validation_error(Rule, Name)
end;
validate(Rule = {max, Max}, Name, Value, _ValidatorState) ->
case Value =< Max of
true -> ok;
false -> validation_error(Rule, Name)
end;
validate(Rule = {exclusive_max, ExclusiveMax}, Name, Value, _ValidatorState) ->
case Value > ExclusiveMax of
true -> ok;
false -> validation_error(Rule, Name)
end;
validate(Rule = {min, Min}, Name, Value, _ValidatorState) ->
case Value >= Min of
true -> ok;
false -> validation_error(Rule, Name)
end;
validate(Rule = {exclusive_min, ExclusiveMin}, Name, Value, _ValidatorState) ->
case Value =< ExclusiveMin of
true -> ok;
false -> validation_error(Rule, Name)
end;
validate(Rule = {max_length, MaxLength}, Name, Value, _ValidatorState) ->
case size(Value) =< MaxLength of
true -> ok;
false -> validation_error(Rule, Name)
end;
validate(Rule = {min_length, MinLength}, Name, Value, _ValidatorState) ->
case size(Value) >= MinLength of
true -> ok;
false -> validation_error(Rule, Name)
end;
validate(Rule = {pattern, Pattern}, Name, Value, _ValidatorState) ->
{ok, MP} = re:compile(Pattern),
case re:run(Value, MP) of
{match, _} -> ok;
_ -> validation_error(Rule, Name)
end;
validate(Rule = schema, Name, Value, ValidatorState) ->
Definition = list_to_binary("#/components/schemas/" ++ {{packageName}}_utils:to_list(Name)),
try
_ = validate_with_schema(Value, Definition, ValidatorState),
ok
catch
throw:[{schema_invalid, _, Error} | _] ->
Info = #{
type => schema_invalid,
error => Error
},
validation_error(Rule, Name, Info);
throw:[{data_invalid, Schema, Error, _, Path} | _] ->
Info = #{
type => data_invalid,
error => Error,
schema => Schema,
path => Path
},
validation_error(Rule, Name, Info)
end;
validate(Rule, Name, _Value, _ValidatorState) ->
error_logger:info_msg("Can't validate ~p with ~p", [Name, Rule]),
error({unknown_validation_rule, Rule}).
-spec validation_error(Rule :: any(), Name :: any()) -> no_return().
validation_error(ViolatedRule, Name) ->
validation_error(ViolatedRule, Name, #{}).
-spec validation_error(Rule :: any(), Name :: any(), Info :: #{}) -> no_return().
validation_error(ViolatedRule, Name, Info) ->
throw({wrong_param, Name, ViolatedRule, Info}).
-spec get_value(body | qs_val | header | binding, Name :: any(), Req0 :: cowboy_req:req()) ->
{Value :: any(), Req :: cowboy_req:req()} |
{error, Reason :: any(), Req :: cowboy_req:req()}.
get_value(body, _Name, Req0) ->
{ok, Body, Req} = cowboy_req:read_body(Req0),
case prepare_body(Body) of
{error, Reason} ->
{error, Reason, Req};
Value ->
{Value, Req}
end;
get_value(qs_val, Name, Req) ->
QS = cowboy_req:parse_qs(Req),
Value = {{packageName}}_utils:get_opt({{packageName}}_utils:to_qs(Name), QS),
{Value, Req};
get_value(header, Name, Req) ->
Headers = cowboy_req:headers(Req),
Value = maps:get({{packageName}}_utils:to_header(Name), Headers, undefined),
{Value, Req};
get_value(binding, Name, Req) ->
Value = cowboy_req:binding({{packageName}}_utils:to_binding(Name), Req),
{Value, Req}.
prepare_body(Body) ->
case Body of
<<"">> -> <<"">>;
_ ->
try
jsx:decode(Body, [return_maps])
catch
error:_ ->
{error, {invalid_body, not_json, Body}}
end
end.
validate_with_schema(Body, Definition, ValidatorState) ->
jesse_schema_validator:validate_with_state(
[{<<"$ref">>, Definition}],
Body,
ValidatorState
).
prepare_param(Rules, Name, Value, ValidatorState) ->
try
Result = lists:foldl(
fun(Rule, Acc) ->
case validate(Rule, Name, Acc, ValidatorState) of
ok -> Acc;
{ok, Prepared} -> Prepared
end
end,
Value,
Rules
),
{ok, Result}
catch
throw:Reason ->
{error, Reason}
end.
binary_to_lower(V) when is_binary(V) ->
list_to_binary(string:to_lower({{packageName}}_utils:to_list(V))).

View File

@@ -1,19 +0,0 @@
{application, {{packageName}}, [
{description, {{#appDescription}}"{{.}}"{{/appDescription}}{{^appDescription}}"OpenAPI rest server library"{{/appDescription}}},
{vsn, "{{apiVersion}}"},
{registered, []},
{applications, [
kernel,
stdlib,
ssl,
inets,
jsx,
jesse,
cowboy
]},
{env, [
]},
{modules, []},
{licenses, [{{#licenseInfo}}"{{.}}"{{/licenseInfo}}]},
{links, [{{#infoUrl}}"{{.}}"{{/infoUrl}}]}
]}.

View File

@@ -1,52 +0,0 @@
-module({{packageName}}_auth).
-export([authorize_api_key/5]).
-spec authorize_api_key(
LogicHandler :: atom(),
OperationID :: {{packageName}}_api:operation_id(),
From :: header | qs_val,
KeyParam :: iodata() | atom(),
Req ::cowboy_req:req()
)-> {true, Context :: #{binary() => any()}, Req ::cowboy_req:req()} |
{false, AuthHeader :: binary(), Req ::cowboy_req:req()}.
authorize_api_key(LogicHandler, OperationID, From, KeyParam, Req0) ->
{ApiKey, Req} = get_api_key(From, KeyParam, Req0),
case ApiKey of
undefined ->
AuthHeader = <<"">>,
{false, AuthHeader, Req};
_ ->
Result = {{packageName}}_logic_handler:authorize_api_key(
LogicHandler,
OperationID,
ApiKey
),
case Result of
{{#authMethods}}
{{#isApiKey}}
{true, Context} ->
{true, Context, Req};
{{/isApiKey}}
{{/authMethods}}
false ->
AuthHeader = <<"">>,
{false, AuthHeader, Req}
end
end.
get_api_key(header, KeyParam, Req) ->
Headers = cowboy_req:headers(Req),
{
maps:get(
{{packageName}}_utils:to_header(KeyParam),
Headers,
undefined
),
Req
};
get_api_key(qs_val, KeyParam, Req) ->
QS = cowboy_req:parse_qs(Req),
{ {{packageName}}_utils:get_opt(KeyParam, QS), Req}.

View File

@@ -1,252 +0,0 @@
%% basic handler
-module({{classname}}).
%% Cowboy REST callbacks
-export([allowed_methods/2]).
-export([init/2]).
-export([allow_missing_post/2]).
-export([content_types_accepted/2]).
-export([content_types_provided/2]).
-export([delete_resource/2]).
-export([is_authorized/2]).
-export([known_content_type/2]).
-export([malformed_request/2]).
-export([valid_content_headers/2]).
-export([valid_entity_length/2]).
%% Handlers
-export([handle_request_json/2]).
-record(state, {
operation_id :: {{packageName}}_api:operation_id(),
logic_handler :: atom(),
validator_state :: jesse_state:state(),
context=#{} :: #{}
}).
-type state() :: state().
-spec init(Req :: cowboy_req:req(), Opts :: {{packageName}}_router:init_opts()) ->
{cowboy_rest, Req :: cowboy_req:req(), State :: state()}.
init(Req, {Operations, LogicHandler, ValidatorMod}) ->
Method = cowboy_req:method(Req),
OperationID = maps:get(Method, Operations, undefined),
ValidatorState = ValidatorMod:get_validator_state(),
error_logger:info_msg("Attempt to process operation: ~p", [OperationID]),
State = #state{
operation_id = OperationID,
logic_handler = LogicHandler,
validator_state = ValidatorState
},
{cowboy_rest, Req, State}.
-spec allowed_methods(Req :: cowboy_req:req(), State :: state()) ->
{Value :: [binary()], Req :: cowboy_req:req(), State :: state()}.
{{#operations}}{{#operation}}
allowed_methods(
Req,
State = #state{
operation_id = '{{operationId}}'
}
) ->
{[<<"{{httpMethod}}">>], Req, State};
{{/operation}}{{/operations}}
allowed_methods(Req, State) ->
{[], Req, State}.
-spec is_authorized(Req :: cowboy_req:req(), State :: state()) ->
{
Value :: true | {false, AuthHeader :: iodata()},
Req :: cowboy_req:req(),
State :: state()
}.
{{#operations}}
{{#operation}}
{{#authMethods}}
is_authorized(
Req0,
State = #state{
operation_id = '{{operationId}}' = OperationID,
logic_handler = LogicHandler
}
) ->
{{#isApiKey}}
From = {{#isKeyInQuery}}qs_val{{/isKeyInQuery}}{{#isKeyInHeader}}header{{/isKeyInHeader}},
Result = {{packageName}}_auth:authorize_api_key(
LogicHandler,
OperationID,
From,
"{{keyParamName}}",
Req0
),
case Result of
{true, Context, Req} -> {true, Req, State#state{context = Context}};
{false, AuthHeader, Req} -> {{false, AuthHeader}, Req, State}
end;
{{/isApiKey}}
{{#isOAuth}}
From = header,
Result = {{packageName}}_auth:authorize_api_key(
LogicHandler,
OperationID,
From,
"Authorization",
Req0
),
case Result of
{true, Context, Req} -> {true, Req, State#state{context = Context}};
{false, AuthHeader, Req} -> {{false, AuthHeader}, Req, State}
end;
{{/isOAuth}}
{{/authMethods}}
{{/operation}}
{{/operations}}
{{^authMethods}}
is_authorized(Req, State) ->
{true, Req, State}.
{{/authMethods}}
{{#authMethods}}
is_authorized(Req, State) ->
{{false, <<"">>}, Req, State}.
{{/authMethods}}
-spec content_types_accepted(Req :: cowboy_req:req(), State :: state()) ->
{
Value :: [{binary(), AcceptResource :: atom()}],
Req :: cowboy_req:req(),
State :: state()
}.
content_types_accepted(Req, State) ->
{[
{<<"application/json">>, handle_request_json}
], Req, State}.
-spec valid_content_headers(Req :: cowboy_req:req(), State :: state()) ->
{Value :: boolean(), Req :: cowboy_req:req(), State :: state()}.
{{#operations}}{{#operation}}
valid_content_headers(
Req0,
State = #state{
operation_id = '{{operationId}}'
}
) ->
Headers = [{{#headerParams}}"{{baseName}}"{{^-last}},{{/-last}}{{/headerParams}}],
{Result, Req} = validate_headers(Headers, Req0),
{Result, Req, State};
{{/operation}}{{/operations}}
valid_content_headers(Req, State) ->
{false, Req, State}.
-spec content_types_provided(Req :: cowboy_req:req(), State :: state()) ->
{
Value :: [{binary(), ProvideResource :: atom()}],
Req :: cowboy_req:req(),
State :: state()
}.
content_types_provided(Req, State) ->
{[
{<<"application/json">>, handle_request_json}
], Req, State}.
-spec malformed_request(Req :: cowboy_req:req(), State :: state()) ->
{Value :: false, Req :: cowboy_req:req(), State :: state()}.
malformed_request(Req, State) ->
{false, Req, State}.
-spec allow_missing_post(Req :: cowboy_req:req(), State :: state()) ->
{Value :: false, Req :: cowboy_req:req(), State :: state()}.
allow_missing_post(Req, State) ->
{false, Req, State}.
-spec delete_resource(Req :: cowboy_req:req(), State :: state()) ->
processed_response().
delete_resource(Req, State) ->
handle_request_json(Req, State).
-spec known_content_type(Req :: cowboy_req:req(), State :: state()) ->
{Value :: true, Req :: cowboy_req:req(), State :: state()}.
known_content_type(Req, State) ->
{true, Req, State}.
-spec valid_entity_length(Req :: cowboy_req:req(), State :: state()) ->
{Value :: true, Req :: cowboy_req:req(), State :: state()}.
valid_entity_length(Req, State) ->
%% @TODO check the length
{true, Req, State}.
%%%%
-type result_ok() :: {
ok,
{Status :: cowboy:http_status(), Headers :: cowboy:http_headers(), Body :: iodata()}
}.
-type result_error() :: {error, Reason :: any()}.
-type processed_response() :: {stop, cowboy_req:req(), state()}.
-spec process_response(result_ok() | result_error(), cowboy_req:req(), state()) ->
processed_response().
process_response(Response, Req0, State = #state{operation_id = OperationID}) ->
case Response of
{ok, {Code, Headers, Body}} ->
Req = cowboy_req:reply(Code, Headers, Body, Req0),
{stop, Req, State};
{error, Message} ->
error_logger:error_msg("Unable to process request for ~p: ~p", [OperationID, Message]),
Req = cowboy_req:reply(400, Req0),
{stop, Req, State}
end.
-spec handle_request_json(cowboy_req:req(), state()) -> processed_response().
handle_request_json(
Req0,
State = #state{
operation_id = OperationID,
logic_handler = LogicHandler,
validator_state = ValidatorState
}
) ->
case {{packageName}}_api:populate_request(OperationID, Req0, ValidatorState) of
{ok, Populated, Req1} ->
{Code, Headers, Body} = {{packageName}}_logic_handler:handle_request(
LogicHandler,
OperationID,
Req1,
maps:merge(State#state.context, Populated)
),
_ = {{packageName}}_api:validate_response(
OperationID,
Code,
Body,
ValidatorState
),
PreparedBody = prepare_body(Code, Body),
Response = {ok, {Code, Headers, PreparedBody}},
process_response(Response, Req1, State);
{error, Reason, Req1} ->
process_response({error, Reason}, Req1, State)
end.
validate_headers(_, Req) -> {true, Req}.
prepare_body(204, Body) when map_size(Body) == 0; length(Body) == 0 ->
<<>>;
prepare_body(304, Body) when map_size(Body) == 0; length(Body) == 0 ->
<<>>;
prepare_body(_Code, Body) ->
jsx:encode(Body).

View File

@@ -1,60 +0,0 @@
-module({{packageName}}_logic_handler).
-export([handle_request/4]).
{{#authMethods}}
{{#isApiKey}}
{{#-first}}
-export([authorize_api_key/3]).
{{/-first}}
{{/isApiKey}}
{{/authMethods}}
{{^authMethods}}
-export([authorize_api_key/3]).
{{/authMethods}}
-type context() :: #{binary() => any()}.
-type handler_response() ::{
Status :: cowboy:http_status(),
Headers :: cowboy:http_headers(),
Body :: jsx:json_term()}.
-export_type([handler_response/0]).
{{#authMethods}}
{{#isApiKey}}
-callback authorize_api_key(
OperationID :: {{packageName}}_api:operation_id(),
ApiKey :: binary()
) ->
Result :: boolean() | {boolean(), context()}.
{{/isApiKey}}
{{/authMethods}}
-callback handle_request(OperationID :: {{packageName}}_api:operation_id(), cowboy_req:req(), Context :: context()) ->
handler_response().
-spec handle_request(
Handler :: atom(),
OperationID :: {{packageName}}_api:operation_id(),
Request :: cowboy_req:req(),
Context :: context()
) ->
handler_response().
handle_request(Handler, OperationID, Req, Context) ->
Handler:handle_request(OperationID, Req, Context).
{{#authMethods}}
{{#isApiKey}}
-spec authorize_api_key(Handler :: atom(), OperationID :: {{packageName}}_api:operation_id(), ApiKey :: binary()) ->
Result :: false | {true, context()}.
authorize_api_key(Handler, OperationID, ApiKey) ->
Handler:authorize_api_key(OperationID, ApiKey).
{{/isApiKey}}
{{/authMethods}}
{{^authMethods}}
-spec authorize_api_key(Handler :: atom(), OperationID :: {{packageName}}_api:operation_id(), ApiKey :: binary()) ->
Result :: false.
authorize_api_key(_Handler, _OperationID, _ApiKey) ->
false.
{{/authMethods}}

View File

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

View File

@@ -1,78 +0,0 @@
-module({{packageName}}_router).
-export([get_paths/1, get_validator_state/0]).
-type operations() :: #{
Method :: binary() => {{packageName}}_api:operation_id()
}.
-type init_opts() :: {
Operations :: operations(),
LogicHandler :: atom(),
ValidatorMod :: module()
}.
-export_type([init_opts/0]).
-spec get_paths(LogicHandler :: atom()) -> [{'_',[{
Path :: string(),
Handler :: atom(),
InitOpts :: init_opts()
}]}].
get_paths(LogicHandler) ->
ValidatorState = prepare_validator(),
PreparedPaths = maps:fold(
fun(Path, #{operations := Operations, handler := Handler}, Acc) ->
[{Path, Handler, Operations} | Acc]
end,
[],
group_paths()
),
[
{'_',
[{P, H, {O, LogicHandler, ValidatorState}} || {P, H, O} <- PreparedPaths]
}
].
group_paths() ->
maps:fold(
fun(OperationID, #{path := Path, method := Method, handler := Handler}, Acc) ->
case maps:find(Path, Acc) of
{ok, PathInfo0 = #{operations := Operations0}} ->
Operations = Operations0#{Method => OperationID},
PathInfo = PathInfo0#{operations => Operations},
Acc#{Path => PathInfo};
error ->
Operations = #{Method => OperationID},
PathInfo = #{handler => Handler, operations => Operations},
Acc#{Path => PathInfo}
end
end,
#{},
get_operations()
).
get_operations() ->
#{ {{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}
'{{operationId}}' => #{
path => "{{{basePathWithoutHost}}}{{{path}}}",
method => <<"{{httpMethod}}">>,
handler => '{{classname}}'
}{{^-last}},{{/-last}}{{/operation}}{{^-last}},{{/-last}}{{/operations}}{{/apis}}{{/apiInfo}}
}.
get_validator_state() ->
persistent_term:get({?MODULE, validator_state}).
prepare_validator() ->
R = jsx:decode(element(2, file:read_file(get_openapi_path()))),
JesseState = jesse_state:new(R, [{default_schema_ver, <<"http://json-schema.org/draft-04/schema#">>}]),
persistent_term:put({?MODULE, validator_state}, JesseState),
?MODULE.
get_openapi_path() ->
{ok, AppName} = application:get_application(?MODULE),
filename:join({{packageName}}_utils:priv_dir(AppName), "{{{openAPISpecName}}}.json").

View File

@@ -1,67 +0,0 @@
-module({{packageName}}_server).
-define(DEFAULT_LOGIC_HANDLER, {{packageName}}_default_logic_handler).
-export([start/2]).
-spec start( ID :: any(), #{
ip => inet:ip_address(),
port => inet:port_number(),
logic_handler => module(),
net_opts => []
}) -> {ok, pid()} | {error, any()}.
start(ID, #{
ip := IP ,
port := Port,
net_opts := NetOpts
} = Params) ->
{Transport, TransportOpts} = get_socket_transport(IP, Port, NetOpts),
LogicHandler = maps:get(logic_handler, Params, ?DEFAULT_LOGIC_HANDLER),
ExtraOpts = maps:get(cowboy_extra_opts, Params, []),
CowboyOpts = get_cowboy_config(LogicHandler, ExtraOpts),
case Transport of
ssl ->
cowboy:start_tls(ID, TransportOpts, CowboyOpts);
tcp ->
cowboy:start_clear(ID, TransportOpts, CowboyOpts)
end.
get_socket_transport(IP, Port, Options) ->
Opts = [
{ip, IP},
{port, Port}
],
case {{packageName}}_utils:get_opt(ssl, Options) of
SslOpts = [_|_] ->
{ssl, Opts ++ SslOpts};
undefined ->
{tcp, Opts}
end.
get_cowboy_config(LogicHandler, ExtraOpts) ->
get_cowboy_config(LogicHandler, ExtraOpts, get_default_opts(LogicHandler)).
get_cowboy_config(_LogicHandler, [], Opts) ->
Opts;
get_cowboy_config(LogicHandler, [{env, Env} | Rest], Opts) ->
NewEnv = case proplists:get_value(dispatch, Env) of
undefined -> [get_default_dispatch(LogicHandler) | Env];
_ -> Env
end,
get_cowboy_config(LogicHandler, Rest, store_key(env, NewEnv, Opts));
get_cowboy_config(LogicHandler, [{Key, Value}| Rest], Opts) ->
get_cowboy_config(LogicHandler, Rest, store_key(Key, Value, Opts)).
get_default_dispatch(LogicHandler) ->
Paths = {{packageName}}_router:get_paths(LogicHandler),
#{dispatch => cowboy_router:compile(Paths)}.
get_default_opts(LogicHandler) ->
#{env => get_default_dispatch(LogicHandler)}.
store_key(Key, Value, Opts) ->
maps:put(Key, Value, Opts).

View File

@@ -4,33 +4,54 @@
An Erlang server stub generated by [OpenAPI Generator](https://openapi-generator.tech) given an OpenAPI spec.
Dependencies: Erlang OTP/27 and rebar3. Also:
- [Cowboy](https://hex.pm/packages/cowboy)
- [Ranch](https://hex.pm/packages/ranch)
- [Jesse](https://hex.pm/packages/jesse)
Dependency: [Cowboy](https://github.com/ninenines/cowboy)
## Prerequisites
TODO
## Getting started
Use erlang-server with rebar3
Use erlang-server with erlang.mk
1, Create an application by using rebar3
$ rebar3 new app http_server
1, Create an application by using erlang.mk
$ mkdir http_server
$ cd http_server
$ wget https://erlang.mk/erlang.mk
$ make -f erlang.mk bootstrap bootstrap-rel
$ make run
2, Generate erlang-server project using openapi-generator
2, Modify the Makefile in the http_server directory to the following to introduce the dependency library:
PROJECT = http_server
PROJECT_DESCRIPTION = New project
PROJECT_VERSION = 0.1.0
DEPS = cowboy jesse jsx
dep_cowboy_commit = 2.5.0
dep_jesse_commit = 1.5.2
dep_jsx_commit = 2.9.0
DEP_PLUGINS = cowboy jesse jsx
PACKAGES += rfc3339
pkg_rfc3339_name = rfc3339
pkg_rfc3339_description = an erlang/elixir rfc3339 lib
pkg_rfc3339_homepage = https://github.com/talentdeficit/rfc3339
pkg_rfc3339_fetch = git
pkg_rfc3339_repo = https://github.com/talentdeficit/rfc3339
pkg_rfc3339_commit = master
include erlang.mk
3, Generate erlang-server project using openapi-generator
https://github.com/OpenAPITools/openapi-generator#2---getting-started
3, Copy erlang-server file to http_server project, and don't forget the 'priv' folder.
4, Copy erlang-server file to http_server project,Don't forget the 'priv' folder.
4, Start in the http_server project:
5, Start in the http_server project:
1, Introduce the following line in the http_server_app:start(_Type, _Args) function
openapi_server:start(http_server, #{ip => {127,0,0,1}, port => 8080})
2, Compile your http_server project
$ rebar3 compile
openapi_server:start(http_server, #{ip=>{127,0,0,1}, port=>8080, net_opts=>[]})
2, Compilation http_server project
$ make
3, Start erlang virtual machine
$ rebar3 shell
$erl -pa ./deps/cowboy/ebin -pa ./deps/cowlib/ebin -pa ./deps/ranch/ebin -pa ./deps/jsx/ebin -pa ./deps/jesse/ebin -pa ./deps/rfc3339/ebin -pa ./ebin
4, Start project
application:ensure_all_started(http_server).
To implement your own business logic, create a module called `http_server_logic` that implements the
behaviour `openapi_logic_handler`. Refer to `openapi_logic_handler` documentation for details.

View File

@@ -1,60 +1,37 @@
-module({{packageName}}_api).
-moduledoc """
This module offers an API for JSON schema validation, using `jesse` under the hood.
If validation is desired, a jesse state can be loaded using `prepare_validator/1`,
and request and response can be validated using `populate_request/3`
and `validate_response/4` respectively.
For example, the user-defined `Module:accept_callback/4` can be implemented as follows:
```
-spec accept_callback(atom(), openapi_api:operation_id(), cowboy_req:req(), context()) ->
{cowboy:http_status(), cowboy:http_headers(), json:encode_value()}.
accept_callback(Class, OperationID, Req, Context) ->
ValidatorState = openapi_api:prepare_validator(),
case openapi_api:populate_request(OperationID, Req0, ValidatorState) of
{ok, Populated, Req1} ->
{Code, Headers, Body} = openapi_logic_handler:handle_request(
LogicHandler,
OperationID,
Req1,
maps:merge(State#state.context, Populated)
),
_ = openapi_api:validate_response(
OperationID,
Code,
Body,
ValidatorState
),
PreparedBody = prepare_body(Code, Body),
Response = {ok, {Code, Headers, PreparedBody}},
process_response(Response, Req1, State);
{error, Reason, Req1} ->
process_response({error, Reason}, Req1, State)
end.
```
""".
-export([prepare_validator/0, prepare_validator/1, prepare_validator/2]).
-export([populate_request/3, validate_response/4]).
-ignore_xref([populate_request/3, validate_response/4]).
-ignore_xref([prepare_validator/0, prepare_validator/1, prepare_validator/2]).
-export([request_params/1]).
-export([request_param_info/2]).
-export([populate_request/3]).
-export([validate_response/4]).
%% exported to silence openapi complains
-export([get_value/3, validate_response_body/4]).
-type operation_id() :: atom().
-type request_param() :: atom().
-export_type([operation_id/0]).
-dialyzer({nowarn_function, [to_binary/1, to_list/1, validate_response_body/4]}).
-spec request_params(OperationID :: operation_id()) -> [Param :: request_param()].
{{#apiInfo}}{{#apis}}
{{#operations}}{{#operation}}
request_params('{{operationId}}') ->
[{{#allParams}}{{^isBodyParam}}
'{{baseName}}'{{/isBodyParam}}{{#isBodyParam}}
'{{dataType}}'{{/isBodyParam}}{{^-last}},{{/-last}}{{/allParams}}
];
{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
request_params(_) ->
error(unknown_operation).
-type rule() ::
{type, binary} |
{type, integer} |
{type, float} |
{type, boolean} |
{type, date} |
{type, datetime} |
{type, 'binary'} |
{type, 'integer'} |
{type, 'float'} |
{type, 'binary'} |
{type, 'boolean'} |
{type, 'date'} |
{type, 'datetime'} |
{enum, [atom()]} |
{max, Max :: number()} |
{exclusive_max, Max :: number()} |
@@ -67,99 +44,59 @@ accept_callback(Class, OperationID, Req, Context) ->
required |
not_required.
-doc #{equiv => prepare_validator/2}.
-spec prepare_validator() -> jesse_state:state().
prepare_validator() ->
prepare_validator(<<"http://json-schema.org/draft-06/schema#">>).
-spec request_param_info(OperationID :: operation_id(), Name :: request_param()) -> #{
source => qs_val | binding | header | body,
rules => [rule()]
}.
-doc #{equiv => prepare_validator/2}.
-spec prepare_validator(binary()) -> jesse_state:state().
prepare_validator(SchemaVer) ->
prepare_validator(get_openapi_path(), SchemaVer).
-doc """
Loads the JSON schema and the desired validation draft into a `t:jesse_state:state()`.
""".
-spec prepare_validator(file:name_all(), binary()) -> jesse_state:state().
prepare_validator(OpenApiPath, SchemaVer) ->
{ok, FileContents} = file:read_file(OpenApiPath),
R = json:decode(FileContents),
jesse_state:new(R, [{default_schema_ver, SchemaVer}]).
-doc """
Automatically loads the entire body from the cowboy req
and validates the JSON body against the schema.
""".
-spec populate_request(
OperationID :: operation_id(),
Req :: cowboy_req:req(),
ValidatorState :: jesse_state:state()) ->
{ok, Model :: #{}, Req :: cowboy_req:req()} |
{error, Reason :: any(), Req :: cowboy_req:req()}.
populate_request(OperationID, Req, ValidatorState) ->
Params = request_params(OperationID),
populate_request_params(OperationID, Params, Req, ValidatorState, #{}).
-doc """
Validates that the provided `Code` and `Body` comply with the `ValidatorState` schema
for the `OperationID` operation.
""".
-spec validate_response(
OperationID :: operation_id(),
Code :: 200..599,
Body :: jesse:json_term(),
ValidatorState :: jesse_state:state()) ->
ok | {ok, term()} | [ok | {ok, term()}] | no_return().
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}{{#responses}}validate_response('{{operationId}}', {{code}}, Body, ValidatorState) ->
validate_response_body('{{dataType}}', '{{baseType}}', Body, ValidatorState);
{{/responses}}
{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}validate_response(_OperationID, _Code, _Body, _ValidatorState) ->
ok.
%%%
-spec request_params(OperationID :: operation_id()) -> [Param :: request_param()].
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}request_params('{{operationId}}') ->
[{{#allParams}}{{^isBodyParam}}
'{{baseName}}'{{/isBodyParam}}{{#isBodyParam}}
'{{dataType}}'{{/isBodyParam}}{{^-last}},{{/-last}}{{/allParams}}
];
{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}request_params(_) ->
error(unknown_operation).
-spec request_param_info(OperationID :: operation_id(), Name :: request_param()) ->
#{source => qs_val | binding | header | body, rules => [rule()]}.
{{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}{{#allParams}}request_param_info('{{operationId}}', {{^isBodyParam}}'{{baseName}}'{{/isBodyParam}}{{#isBodyParam}}'{{dataType}}'{{/isBodyParam}}) ->
{{#apiInfo}}{{#apis}}
{{#operations}}{{#operation}}{{#allParams}}
request_param_info('{{operationId}}', {{^isBodyParam}}'{{baseName}}'{{/isBodyParam}}{{#isBodyParam}}'{{dataType}}'{{/isBodyParam}}) ->
#{
source => {{#isQueryParam}}qs_val{{/isQueryParam}}{{#isPathParam}}binding{{/isPathParam}}{{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}{{#isFormParam}}body{{/isFormParam}},
source => {{#isQueryParam}}qs_val{{/isQueryParam}} {{#isPathParam}}binding{{/isPathParam}} {{#isHeaderParam}}header{{/isHeaderParam}}{{#isBodyParam}}body{{/isBodyParam}}{{#isFormParam}}body{{/isFormParam}},
rules => [{{#isString}}
{type, binary},{{/isString}}{{#isInteger}}
{type, integer},{{/isInteger}}{{#isLong}}
{type, integer},{{/isLong}}{{#isFloat}}
{type, float},{{/isFloat}}{{#isDouble}}
{type, float},{{/isDouble}}{{#isByteArray}}
{type, binary},{{/isByteArray}}{{#isBinary}}
{type, binary},{{/isBinary}}{{#isBoolean}}
{type, boolean},{{/isBoolean}}{{#isDate}}
{type, date},{{/isDate}}{{#isDateTime}}
{type, datetime},{{/isDateTime}}{{#isEnum}}
{type, 'binary'},{{/isString}}{{#isInteger}}
{type, 'integer'},{{/isInteger}}{{#isLong}}
{type, 'integer'},{{/isLong}}{{#isFloat}}
{type, 'float'},{{/isFloat}}{{#isDouble}}
{type, 'float'},{{/isDouble}}{{#isByteArray}}
{type, 'binary'},{{/isByteArray}}{{#isBinary}}
{type, 'binary'},{{/isBinary}}{{#isBoolean}}
{type, 'boolean'},{{/isBoolean}}{{#isDate}}
{type, 'date'},{{/isDate}}{{#isDateTime}}
{type, 'datetime'},{{/isDateTime}}{{#isEnum}}
{enum, [{{#allowableValues}}{{#values}}'{{.}}'{{^-last}}, {{/-last}}{{/values}}{{/allowableValues}}] },{{/isEnum}}{{#maximum}}
{max, {{maximum}}},{{/maximum}}{{#exclusiveMaximum}}
{exclusive_max, {{exclusiveMaximum}}},{{/exclusiveMaximum}}{{#minimum}}
{min, {{minimum}}},{{/minimum}}{{#exclusiveMinimum}}
{exclusive_min, {{exclusiveMinimum}}},{{/exclusiveMinimum}}{{#maxLength}}
{max_length, {{maxLength}}},{{/maxLength}}{{#minLength}}
{min_length, {{minLength}}},{{/minLength}}{{#pattern}}
{pattern, "{{{pattern}}}"},{{/pattern}}{{#isBodyParam}}
{max, {{maximum}} }, {{/maximum}}{{#exclusiveMaximum}}
{exclusive_max, {{exclusiveMaximum}} },{{/exclusiveMaximum}}{{#minimum}}
{min, {{minimum}} },{{/minimum}}{{#exclusiveMinimum}}
{exclusive_min, {{exclusiveMinimum}} },{{/exclusiveMinimum}}{{#maxLength}}
{max_length, {{maxLength}} },{{/maxLength}}{{#minLength}}
{min_length, {{minLength}} },{{/minLength}}{{#pattern}}
{pattern, "{{{pattern}}}" },{{/pattern}}{{#isBodyParam}}
schema,{{/isBodyParam}}{{#required}}
required{{/required}}{{^required}}
not_required{{/required}}
]
};
{{/allParams}}{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}request_param_info(OperationID, Name) ->
{{/allParams}}{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
request_param_info(OperationID, Name) ->
error({unknown_param, OperationID, Name}).
-spec populate_request(
OperationID :: operation_id(),
Req :: cowboy_req:req(),
ValidatorState :: jesse_state:state()
) ->
{ok, Model :: #{}, Req :: cowboy_req:req()} |
{error, Reason :: any(), Req :: cowboy_req:req()}.
populate_request(OperationID, Req, ValidatorState) ->
Params = request_params(OperationID),
populate_request_params(OperationID, Params, Req, ValidatorState, #{}).
populate_request_params(_, [], Req, _, Model) ->
{ok, Model, Req};
populate_request_params(OperationID, [FieldParams | T], Req0, ValidatorState, Model) ->
case populate_request_param(OperationID, FieldParams, Req0, ValidatorState) of
{ok, K, V, Req} ->
@@ -181,9 +118,24 @@ populate_request_param(OperationID, Name, Req0, ValidatorState) ->
end
end.
-include_lib("kernel/include/logger.hrl").
-spec validate_response(
OperationID :: operation_id(),
Code :: 200..599,
Body :: jesse:json_term(),
ValidatorState :: jesse_state:state()
) -> ok | no_return().
{{#apiInfo}}{{#apis}}
{{#operations}}{{#operation}}
{{#responses}}
validate_response('{{operationId}}', {{code}}, Body, ValidatorState) ->
validate_response_body('{{dataType}}', '{{baseType}}', Body, ValidatorState);
{{/responses}}
{{/operation}}{{/operations}}{{/apis}}{{/apiInfo}}
validate_response_body(list, ReturnBaseType, Body, ValidatorState) ->
validate_response(_OperationID, _Code, _Body, _ValidatorState) ->
ok.
validate_response_body('list', ReturnBaseType, Body, ValidatorState) ->
[
validate(schema, ReturnBaseType, Item, ValidatorState)
|| Item <- Body];
@@ -191,37 +143,45 @@ validate_response_body(list, ReturnBaseType, Body, ValidatorState) ->
validate_response_body(_, ReturnBaseType, Body, ValidatorState) ->
validate(schema, ReturnBaseType, Body, ValidatorState).
%%%
validate(Rule = required, Name, Value, _ValidatorState) ->
case Value of
undefined -> validation_error(Rule, Name);
_ -> ok
end;
validate(not_required, _Name, _Value, _ValidatorState) ->
ok;
validate(_, _Name, undefined, _ValidatorState) ->
ok;
validate(Rule = {type, integer}, Name, Value, _ValidatorState) ->
validate(Rule = {type, 'integer'}, Name, Value, _ValidatorState) ->
try
{ok, to_int(Value)}
{ok, {{packageName}}_utils:to_int(Value)}
catch
error:badarg ->
validation_error(Rule, Name)
end;
validate(Rule = {type, float}, Name, Value, _ValidatorState) ->
validate(Rule = {type, 'float'}, Name, Value, _ValidatorState) ->
try
{ok, to_float(Value)}
{ok, {{packageName}}_utils:to_float(Value)}
catch
error:badarg ->
validation_error(Rule, Name)
end;
validate(Rule = {type, binary}, Name, Value, _ValidatorState) ->
validate(Rule = {type, 'binary'}, Name, Value, _ValidatorState) ->
case is_binary(Value) of
true -> ok;
false -> validation_error(Rule, Name)
end;
validate(_Rule = {type, boolean}, _Name, Value, _ValidatorState) when is_boolean(Value) ->
validate(_Rule = {type, 'boolean'}, _Name, Value, _ValidatorState) when is_boolean(Value) ->
{ok, Value};
validate(Rule = {type, boolean}, Name, Value, _ValidatorState) ->
validate(Rule = {type, 'boolean'}, Name, Value, _ValidatorState) ->
V = binary_to_lower(Value),
try
case binary_to_existing_atom(V, utf8) of
@@ -232,16 +192,19 @@ validate(Rule = {type, boolean}, Name, Value, _ValidatorState) ->
error:badarg ->
validation_error(Rule, Name)
end;
validate(Rule = {type, date}, Name, Value, _ValidatorState) ->
validate(Rule = {type, 'date'}, Name, Value, _ValidatorState) ->
case is_binary(Value) of
true -> ok;
false -> validation_error(Rule, Name)
end;
validate(Rule = {type, datetime}, Name, Value, _ValidatorState) ->
validate(Rule = {type, 'datetime'}, Name, Value, _ValidatorState) ->
case is_binary(Value) of
true -> ok;
false -> validation_error(Rule, Name)
end;
validate(Rule = {enum, Values}, Name, Value, _ValidatorState) ->
try
FormattedValue = erlang:binary_to_existing_atom(Value, utf8),
@@ -253,44 +216,52 @@ validate(Rule = {enum, Values}, Name, Value, _ValidatorState) ->
error:badarg ->
validation_error(Rule, Name)
end;
validate(Rule = {max, Max}, Name, Value, _ValidatorState) ->
case Value =< Max of
true -> ok;
false -> validation_error(Rule, Name)
end;
validate(Rule = {exclusive_max, ExclusiveMax}, Name, Value, _ValidatorState) ->
case Value > ExclusiveMax of
true -> ok;
false -> validation_error(Rule, Name)
end;
validate(Rule = {min, Min}, Name, Value, _ValidatorState) ->
case Value >= Min of
true -> ok;
false -> validation_error(Rule, Name)
end;
validate(Rule = {exclusive_min, ExclusiveMin}, Name, Value, _ValidatorState) ->
case Value =< ExclusiveMin of
true -> ok;
false -> validation_error(Rule, Name)
end;
validate(Rule = {max_length, MaxLength}, Name, Value, _ValidatorState) ->
case size(Value) =< MaxLength of
true -> ok;
false -> validation_error(Rule, Name)
end;
validate(Rule = {min_length, MinLength}, Name, Value, _ValidatorState) ->
case size(Value) >= MinLength of
true -> ok;
false -> validation_error(Rule, Name)
end;
validate(Rule = {pattern, Pattern}, Name, Value, _ValidatorState) ->
{ok, MP} = re:compile(Pattern),
case re:run(Value, MP) of
{match, _} -> ok;
_ -> validation_error(Rule, Name)
end;
validate(Rule = schema, Name, Value, ValidatorState) ->
Definition = list_to_binary("#/components/schemas/" ++ to_list(Name)),
Definition = list_to_binary("#/components/schemas/" ++ {{packageName}}_utils:to_list(Name)),
try
_ = validate_with_schema(Value, Definition, ValidatorState),
ok
@@ -310,15 +281,18 @@ validate(Rule = schema, Name, Value, ValidatorState) ->
},
validation_error(Rule, Name, Info)
end;
validate(Rule, Name, _Value, _ValidatorState) ->
?LOG_INFO(#{what => "Cannot validate rule", name => Name, rule => Rule}),
error_logger:info_msg("Can't validate ~p with ~p", [Name, Rule]),
error({unknown_validation_rule, Rule}).
-spec validation_error(Rule :: any(), Name :: any()) -> no_return().
validation_error(ViolatedRule, Name) ->
validation_error(ViolatedRule, Name, #{}).
-spec validation_error(Rule :: any(), Name :: any(), Info :: #{_ := _}) -> no_return().
-spec validation_error(Rule :: any(), Name :: any(), Info :: #{}) -> no_return().
validation_error(ViolatedRule, Name, Info) ->
throw({wrong_param, Name, ViolatedRule, Info}).
@@ -333,26 +307,31 @@ get_value(body, _Name, Req0) ->
Value ->
{Value, Req}
end;
get_value(qs_val, Name, Req) ->
QS = cowboy_req:parse_qs(Req),
Value = get_opt(to_qs(Name), QS),
Value = {{packageName}}_utils:get_opt({{packageName}}_utils:to_qs(Name), QS),
{Value, Req};
get_value(header, Name, Req) ->
Headers = cowboy_req:headers(Req),
Value = maps:get(to_header(Name), Headers, undefined),
Value = maps:get({{packageName}}_utils:to_header(Name), Headers, undefined),
{Value, Req};
get_value(binding, Name, Req) ->
Value = cowboy_req:binding(to_binding(Name), Req),
Value = cowboy_req:binding({{packageName}}_utils:to_binding(Name), Req),
{Value, Req}.
prepare_body(<<>>) ->
<<>>;
prepare_body(Body) ->
try
json:decode(Body)
catch
error:_ ->
{error, {invalid_body, not_json, Body}}
case Body of
<<"">> -> <<"">>;
_ ->
try
jsx:decode(Body, [return_maps])
catch
error:_ ->
{error, {invalid_body, not_json, Body}}
end
end.
validate_with_schema(Body, Definition, ValidatorState) ->
@@ -380,84 +359,5 @@ prepare_param(Rules, Name, Value, ValidatorState) ->
{error, Reason}
end.
-spec to_binary(iodata() | atom() | number()) -> binary().
to_binary(V) when is_binary(V) -> V;
to_binary(V) when is_list(V) -> iolist_to_binary(V);
to_binary(V) when is_atom(V) -> atom_to_binary(V, utf8);
to_binary(V) when is_integer(V) -> integer_to_binary(V);
to_binary(V) when is_float(V) -> float_to_binary(V).
-spec to_list(iodata() | atom() | number()) -> binary().
to_list(V) when is_list(V) -> V;
to_list(V) when is_binary(V) -> binary_to_list(V);
to_list(V) when is_atom(V) -> atom_to_list(V);
to_list(V) when is_integer(V) -> integer_to_list(V);
to_list(V) when is_float(V) -> float_to_list(V).
-spec to_float(iodata()) -> float().
to_float(V) ->
binary_to_float(iolist_to_binary([V])).
-spec to_int(integer() | binary() | list()) -> integer().
to_int(Data) when is_integer(Data) ->
Data;
to_int(Data) when is_binary(Data) ->
binary_to_integer(Data);
to_int(Data) when is_list(Data) ->
list_to_integer(Data).
-spec to_header(iodata() | atom() | number()) -> binary().
to_header(Name) ->
to_binary(string:lowercase(to_binary(Name))).
binary_to_lower(V) when is_binary(V) ->
string:lowercase(V).
-spec to_qs(iodata() | atom() | number()) -> binary().
to_qs(Name) ->
to_binary(Name).
-spec to_binding(iodata() | atom() | number()) -> atom().
to_binding(Name) ->
Prepared = to_binary(Name),
binary_to_existing_atom(Prepared, utf8).
-spec get_opt(any(), []) -> any().
get_opt(Key, Opts) ->
get_opt(Key, Opts, undefined).
-spec get_opt(any(), [], any()) -> any().
get_opt(Key, Opts, Default) ->
case lists:keyfind(Key, 1, Opts) of
{_, Value} -> Value;
false -> Default
end.
get_openapi_path() ->
{ok, AppName} = application:get_application(?MODULE),
filename:join(priv_dir(AppName), "{{{openAPISpecName}}}.json").
-include_lib("kernel/include/file.hrl").
-spec priv_dir(Application :: atom()) -> file:name_all().
priv_dir(AppName) ->
case code:priv_dir(AppName) of
Value when is_list(Value) ->
Value ++ "/";
_Error ->
select_priv_dir([filename:join(["apps", atom_to_list(AppName), "priv"]), "priv"])
end.
select_priv_dir(Paths) ->
case lists:dropwhile(fun test_priv_dir/1, Paths) of
[Path | _] -> Path;
_ -> exit(no_priv_dir)
end.
test_priv_dir(Path) ->
case file:read_file_info(Path) of
{ok, #file_info{type = directory}} ->
false;
_ ->
true
end.
list_to_binary(string:to_lower({{packageName}}_utils:to_list(V))).

View File

@@ -1,11 +1,19 @@
{application,
{{packageName}},
[{description,
{{#appDescription}}"{{.}}"{{/appDescription}}{{^appDescription}}"OpenAPI rest server library"{{/appDescription}}},
{vsn, "{{apiVersion}}"},
{registered, []},
{applications, [kernel, stdlib, public_key, ssl, inets, ranch, cowboy]},
{env, []},
{modules, []},
{licenses, [{{#licenseInfo}}"{{.}}"{{/licenseInfo}}]},
{links, [{{#infoUrl}}"{{.}}"{{/infoUrl}}]}]}.
{application, {{packageName}}, [
{description, {{#appDescription}}"{{.}}"{{/appDescription}}{{^appDescription}}"OpenAPI rest server library"{{/appDescription}}},
{vsn, "{{apiVersion}}"},
{registered, []},
{applications, [
kernel,
stdlib,
ssl,
inets,
jsx,
jesse,
cowboy
]},
{env, [
]},
{modules, []},
{licenses, [{{#licenseInfo}}"{{.}}"{{/licenseInfo}}]},
{links, [{{#infoUrl}}"{{.}}"{{/infoUrl}}]}
]}.

View File

@@ -2,44 +2,51 @@
-export([authorize_api_key/5]).
-spec authorize_api_key({{packageName}}_logic_handler:api_key_callback(),
{{packageName}}_api:operation_id(),
header | qs_val,
iodata() | atom(),
cowboy_req:req()) ->
{true, {{packageName}}_logic_handler:context(), cowboy_req:req()} |
{false, binary(), cowboy_req:req()}.
authorize_api_key(Handler, OperationID, From, KeyParam, Req0) ->
-spec authorize_api_key(
LogicHandler :: atom(),
OperationID :: {{packageName}}_api:operation_id(),
From :: header | qs_val,
KeyParam :: iodata() | atom(),
Req ::cowboy_req:req()
)-> {true, Context :: #{binary() => any()}, Req ::cowboy_req:req()} |
{false, AuthHeader :: binary(), Req ::cowboy_req:req()}.
authorize_api_key(LogicHandler, OperationID, From, KeyParam, Req0) ->
{ApiKey, Req} = get_api_key(From, KeyParam, Req0),
case ApiKey of
undefined ->
AuthHeader = <<>>,
AuthHeader = <<"">>,
{false, AuthHeader, Req};
_ ->
case Handler(OperationID, ApiKey) of
{true, Context} ->
Result = {{packageName}}_logic_handler:authorize_api_key(
LogicHandler,
OperationID,
ApiKey
),
case Result of
{{#authMethods}}
{{#isApiKey}}
{true, Context} ->
{true, Context, Req};
{false, AuthHeader} ->
{{/isApiKey}}
{{/authMethods}}
false ->
AuthHeader = <<"">>,
{false, AuthHeader, Req}
end
end.
get_api_key(header, KeyParam, Req) ->
Headers = cowboy_req:headers(Req),
{maps:get(KeyParam, Headers, undefined), Req};
{
maps:get(
{{packageName}}_utils:to_header(KeyParam),
Headers,
undefined
),
Req
};
get_api_key(qs_val, KeyParam, Req) ->
QS = cowboy_req:parse_qs(Req),
{get_opt(KeyParam, QS), Req}.
-spec get_opt(any(), []) -> any().
get_opt(Key, Opts) ->
get_opt(Key, Opts, undefined).
-spec get_opt(any(), [], any()) -> any().
get_opt(Key, Opts, Default) ->
case lists:keyfind(Key, 1, Opts) of
{_, Value} ->
Value;
false ->
Default
end.
{ {{packageName}}_utils:get_opt(KeyParam, QS), Req}.

View File

@@ -1,129 +1,252 @@
%% basic handler
-module({{classname}}).
-behaviour(cowboy_rest).
-include_lib("kernel/include/logger.hrl").
%% Cowboy REST callbacks
-export([init/2]).
-export([allowed_methods/2]).
-export([init/2]).
-export([allow_missing_post/2]).
-export([content_types_accepted/2]).
-export([content_types_provided/2]).
-export([delete_resource/2]).
-export([is_authorized/2]).
-export([known_content_type/2]).
-export([malformed_request/2]).
-export([valid_content_headers/2]).
-export([handle_type_accepted/2, handle_type_provided/2]).
-export([valid_entity_length/2]).
-ignore_xref([handle_type_accepted/2, handle_type_provided/2]).
%% Handlers
-export([handle_request_json/2]).
-record(state,
{operation_id :: {{packageName}}_api:operation_id(),
accept_callback :: {{packageName}}_logic_handler:accept_callback(),
provide_callback :: {{packageName}}_logic_handler:provide_callback(),
api_key_handler :: {{packageName}}_logic_handler:api_key_callback(),
context = #{} :: {{packageName}}_logic_handler:context()}).
-record(state, {
operation_id :: {{packageName}}_api:operation_id(),
logic_handler :: atom(),
validator_state :: jesse_state:state(),
context=#{} :: #{}
}).
-type state() :: #state{}.
-type state() :: state().
-spec init(cowboy_req:req(), {{packageName}}_router:init_opts()) ->
{cowboy_rest, cowboy_req:req(), state()}.
init(Req, {Operations, Module}) ->
-spec init(Req :: cowboy_req:req(), Opts :: {{packageName}}_router:init_opts()) ->
{cowboy_rest, Req :: cowboy_req:req(), State :: state()}.
init(Req, {Operations, LogicHandler, ValidatorMod}) ->
Method = cowboy_req:method(Req),
OperationID = maps:get(Method, Operations, undefined),
?LOG_INFO(#{what => "Attempt to process operation",
method => Method,
operation_id => OperationID}),
State = #state{operation_id = OperationID,
accept_callback = fun Module:accept_callback/4,
provide_callback = fun Module:provide_callback/4,
api_key_handler = fun Module:authorize_api_key/2},
ValidatorState = ValidatorMod:get_validator_state(),
error_logger:info_msg("Attempt to process operation: ~p", [OperationID]),
State = #state{
operation_id = OperationID,
logic_handler = LogicHandler,
validator_state = ValidatorState
},
{cowboy_rest, Req, State}.
-spec allowed_methods(cowboy_req:req(), state()) ->
{[binary()], cowboy_req:req(), state()}.
{{#operations}}{{#operation}}allowed_methods(Req, #state{operation_id = '{{operationId}}'} = State) ->
-spec allowed_methods(Req :: cowboy_req:req(), State :: state()) ->
{Value :: [binary()], Req :: cowboy_req:req(), State :: state()}.
{{#operations}}{{#operation}}
allowed_methods(
Req,
State = #state{
operation_id = '{{operationId}}'
}
) ->
{[<<"{{httpMethod}}">>], Req, State};
{{/operation}}{{/operations}}allowed_methods(Req, State) ->
{{/operation}}{{/operations}}
allowed_methods(Req, State) ->
{[], Req, State}.
-spec is_authorized(cowboy_req:req(), state()) ->
{true | {false, iodata()}, cowboy_req:req(), state()}.
-spec is_authorized(Req :: cowboy_req:req(), State :: state()) ->
{
Value :: true | {false, AuthHeader :: iodata()},
Req :: cowboy_req:req(),
State :: state()
}.
{{#operations}}
{{#operation}}
{{#authMethods.size}}
is_authorized(Req0,
#state{operation_id = '{{operationId}}' = OperationID,
api_key_handler = Handler} = State) ->
case {{packageName}}_auth:authorize_api_key(Handler, OperationID, {{#isApiKey.isKeyInQuery}}qs_val, {{/isApiKey.isKeyInQuery}}{{^isApiKey.isKeyInQuery}}header, {{/isApiKey.isKeyInQuery}}{{#isApiKey}}"{{keyParamName}}", {{/isApiKey}}{{^isApiKey}}"authorization", {{/isApiKey}}Req0) of
{true, Context, Req} ->
{true, Req, State#state{context = Context}};
{false, AuthHeader, Req} ->
{{false, AuthHeader}, Req, State}
{{#authMethods}}
is_authorized(
Req0,
State = #state{
operation_id = '{{operationId}}' = OperationID,
logic_handler = LogicHandler
}
) ->
{{#isApiKey}}
From = {{#isKeyInQuery}}qs_val{{/isKeyInQuery}}{{#isKeyInHeader}}header{{/isKeyInHeader}},
Result = {{packageName}}_auth:authorize_api_key(
LogicHandler,
OperationID,
From,
"{{keyParamName}}",
Req0
),
case Result of
{true, Context, Req} -> {true, Req, State#state{context = Context}};
{false, AuthHeader, Req} -> {{false, AuthHeader}, Req, State}
end;
{{/authMethods.size}}
{{/isApiKey}}
{{#isOAuth}}
From = header,
Result = {{packageName}}_auth:authorize_api_key(
LogicHandler,
OperationID,
From,
"Authorization",
Req0
),
case Result of
{true, Context, Req} -> {true, Req, State#state{context = Context}};
{false, AuthHeader, Req} -> {{false, AuthHeader}, Req, State}
end;
{{/isOAuth}}
{{/authMethods}}
{{/operation}}
{{/operations}}
{{^authMethods}}
is_authorized(Req, State) ->
{true, Req, State}.
{{/authMethods}}
{{#authMethods}}
is_authorized(Req, State) ->
{{false, <<"">>}, Req, State}.
{{/authMethods}}
-spec content_types_accepted(cowboy_req:req(), state()) ->
{[{binary(), atom()}], cowboy_req:req(), state()}.
{{#operations}}{{#operation}}content_types_accepted(Req, #state{operation_id = '{{operationId}}'} = State) ->
{{^consumes.size}}
{[], Req, State};
{{/consumes.size}}
{{#consumes.size}}
-spec content_types_accepted(Req :: cowboy_req:req(), State :: state()) ->
{
Value :: [{binary(), AcceptResource :: atom()}],
Req :: cowboy_req:req(),
State :: state()
}.
content_types_accepted(Req, State) ->
{[
{{#consumes}}
{<<"{{mediaType}}">>, handle_type_accepted}{{^-last}}{{#consumes.size}},{{/consumes.size}}{{/-last}}
{{/consumes}}
], Req, State};
{{/consumes.size}}
{{/operation}}{{/operations}}content_types_accepted(Req, State) ->
{[], Req, State}.
{<<"application/json">>, handle_request_json}
], Req, State}.
-spec valid_content_headers(cowboy_req:req(), state()) ->
{boolean(), cowboy_req:req(), state()}.
{{#operations}}{{#operation}}valid_content_headers(Req, #state{operation_id = '{{operationId}}'} = State) ->
{true, Req, State};
{{/operation}}{{/operations}}valid_content_headers(Req, State) ->
-spec valid_content_headers(Req :: cowboy_req:req(), State :: state()) ->
{Value :: boolean(), Req :: cowboy_req:req(), State :: state()}.
{{#operations}}{{#operation}}
valid_content_headers(
Req0,
State = #state{
operation_id = '{{operationId}}'
}
) ->
Headers = [{{#headerParams}}"{{baseName}}"{{^-last}},{{/-last}}{{/headerParams}}],
{Result, Req} = validate_headers(Headers, Req0),
{Result, Req, State};
{{/operation}}{{/operations}}
valid_content_headers(Req, State) ->
{false, Req, State}.
-spec content_types_provided(cowboy_req:req(), state()) ->
{[{binary(), atom()}], cowboy_req:req(), state()}.
{{#operations}}{{#operation}}content_types_provided(Req, #state{operation_id = '{{operationId}}'} = State) ->
{{^produces.size}}
{[], Req, State};
{{/produces.size}}
{{#produces.size}}
-spec content_types_provided(Req :: cowboy_req:req(), State :: state()) ->
{
Value :: [{binary(), ProvideResource :: atom()}],
Req :: cowboy_req:req(),
State :: state()
}.
content_types_provided(Req, State) ->
{[
{{#produces}}
{<<"{{mediaType}}">>, handle_type_provided}{{^-last}}{{#produces.size}},{{/produces.size}}{{/-last}}
{{/produces}}
], Req, State};
{{/produces.size}}
{{/operation}}{{/operations}}content_types_provided(Req, State) ->
{[], Req, State}.
{<<"application/json">>, handle_request_json}
], Req, State}.
-spec malformed_request(Req :: cowboy_req:req(), State :: state()) ->
{Value :: false, Req :: cowboy_req:req(), State :: state()}.
malformed_request(Req, State) ->
{false, Req, State}.
-spec allow_missing_post(Req :: cowboy_req:req(), State :: state()) ->
{Value :: false, Req :: cowboy_req:req(), State :: state()}.
allow_missing_post(Req, State) ->
{false, Req, State}.
-spec delete_resource(Req :: cowboy_req:req(), State :: state()) ->
processed_response().
-spec delete_resource(cowboy_req:req(), state()) ->
{boolean(), cowboy_req:req(), state()}.
delete_resource(Req, State) ->
{Res, Req1, State} = handle_type_accepted(Req, State),
{true =:= Res, Req1, State}.
handle_request_json(Req, State).
-spec handle_type_accepted(cowboy_req:req(), state()) ->
{ {{packageName}}_logic_handler:accept_callback_return(), cowboy_req:req(), state()}.
handle_type_accepted(Req, #state{operation_id = OperationID,
accept_callback = Handler,
context = Context} = State) ->
{Res, Req1, Context1} = Handler({{operations.pathPrefix}}, OperationID, Req, Context),
{Res, Req1, State#state{context = Context1}}.
-spec known_content_type(Req :: cowboy_req:req(), State :: state()) ->
{Value :: true, Req :: cowboy_req:req(), State :: state()}.
-spec handle_type_provided(cowboy_req:req(), state()) ->
{cowboy_req:resp_body(), cowboy_req:req(), state()}.
handle_type_provided(Req, #state{operation_id = OperationID,
provide_callback = Handler,
context = Context} = State) ->
{Res, Req1, Context1} = Handler({{operations.pathPrefix}}, OperationID, Req, Context),
{Res, Req1, State#state{context = Context1}}.
known_content_type(Req, State) ->
{true, Req, State}.
-spec valid_entity_length(Req :: cowboy_req:req(), State :: state()) ->
{Value :: true, Req :: cowboy_req:req(), State :: state()}.
valid_entity_length(Req, State) ->
%% @TODO check the length
{true, Req, State}.
%%%%
-type result_ok() :: {
ok,
{Status :: cowboy:http_status(), Headers :: cowboy:http_headers(), Body :: iodata()}
}.
-type result_error() :: {error, Reason :: any()}.
-type processed_response() :: {stop, cowboy_req:req(), state()}.
-spec process_response(result_ok() | result_error(), cowboy_req:req(), state()) ->
processed_response().
process_response(Response, Req0, State = #state{operation_id = OperationID}) ->
case Response of
{ok, {Code, Headers, Body}} ->
Req = cowboy_req:reply(Code, Headers, Body, Req0),
{stop, Req, State};
{error, Message} ->
error_logger:error_msg("Unable to process request for ~p: ~p", [OperationID, Message]),
Req = cowboy_req:reply(400, Req0),
{stop, Req, State}
end.
-spec handle_request_json(cowboy_req:req(), state()) -> processed_response().
handle_request_json(
Req0,
State = #state{
operation_id = OperationID,
logic_handler = LogicHandler,
validator_state = ValidatorState
}
) ->
case {{packageName}}_api:populate_request(OperationID, Req0, ValidatorState) of
{ok, Populated, Req1} ->
{Code, Headers, Body} = {{packageName}}_logic_handler:handle_request(
LogicHandler,
OperationID,
Req1,
maps:merge(State#state.context, Populated)
),
_ = {{packageName}}_api:validate_response(
OperationID,
Code,
Body,
ValidatorState
),
PreparedBody = prepare_body(Code, Body),
Response = {ok, {Code, Headers, PreparedBody}},
process_response(Response, Req1, State);
{error, Reason, Req1} ->
process_response({error, Reason}, Req1, State)
end.
validate_headers(_, Req) -> {true, Req}.
prepare_body(204, Body) when map_size(Body) == 0; length(Body) == 0 ->
<<>>;
prepare_body(304, Body) when map_size(Body) == 0; length(Body) == 0 ->
<<>>;
prepare_body(_Code, Body) ->
jsx:encode(Body).

View File

@@ -1,63 +1,60 @@
-module({{packageName}}_logic_handler).
-include_lib("kernel/include/logger.hrl").
-export([handle_request/4]).
{{#authMethods}}
{{#isApiKey}}
{{#-first}}
-export([authorize_api_key/3]).
{{/-first}}
{{/isApiKey}}
{{/authMethods}}
{{^authMethods}}
-export([authorize_api_key/3]).
{{/authMethods}}
-type context() :: #{binary() => any()}.
-type handler_response() ::{
Status :: cowboy:http_status(),
Headers :: cowboy:http_headers(),
Body :: jsx:json_term()}.
-type accept_callback_return() ::
stop
| boolean()
| {true, iodata()}
| {created, iodata()}
| {see_other, iodata()}.
-type api_key_callback() ::
fun(({{packageName}}_api:operation_id(), binary()) -> {true, context()} | {false, iodata()}).
-type accept_callback() ::
fun((atom(), {{packageName}}_api:operation_id(), cowboy_req:req(), context()) ->
{accept_callback_return(), cowboy_req:req(), context()}).
-type provide_callback() ::
fun((atom(), {{packageName}}_api:operation_id(), cowboy_req:req(), context()) ->
{cowboy_req:resp_body(), cowboy_req:req(), context()}).
-type context() :: #{_ := _}.
-export_type([handler_response/0]).
-export_type([context/0, api_key_callback/0,
accept_callback_return/0, accept_callback/0, provide_callback/0]).
{{#authMethods}}
{{#isApiKey}}
-callback authorize_api_key(
OperationID :: {{packageName}}_api:operation_id(),
ApiKey :: binary()
) ->
Result :: boolean() | {boolean(), context()}.
{{/isApiKey}}
{{/authMethods}}
-optional_callbacks([api_key_callback/2]).
-callback api_key_callback({{packageName}}_api:operation_id(), binary()) ->
{true, context()} | {false, iodata()}.
-callback handle_request(OperationID :: {{packageName}}_api:operation_id(), cowboy_req:req(), Context :: context()) ->
handler_response().
-callback accept_callback(atom(), {{packageName}}_api:operation_id(), cowboy_req:req(), context()) ->
{accept_callback_return(), cowboy_req:req(), context()}.
-spec handle_request(
Handler :: atom(),
OperationID :: {{packageName}}_api:operation_id(),
Request :: cowboy_req:req(),
Context :: context()
) ->
handler_response().
-callback provide_callback(atom(), {{packageName}}_api:operation_id(), cowboy_req:req(), context()) ->
{cowboy_req:resp_body(), cowboy_req:req(), context()}.
handle_request(Handler, OperationID, Req, Context) ->
Handler:handle_request(OperationID, Req, Context).
-export([api_key_callback/2, accept_callback/4, provide_callback/4]).
-ignore_xref([api_key_callback/2, accept_callback/4, provide_callback/4]).
-spec api_key_callback({{packageName}}_api:operation_id(), binary()) -> {true, #{}}.
api_key_callback(OperationID, ApiKey) ->
?LOG_ERROR(#{what => "Got not implemented api_key_callback request",
operation_id => OperationID,
api_key => ApiKey}),
{true, #{}}.
-spec accept_callback(atom(), {{packageName}}_api:operation_id(), cowboy_req:req(), context()) ->
{accept_callback_return(), cowboy_req:req(), context()}.
accept_callback(Class, OperationID, Req, Context) ->
?LOG_ERROR(#{what => "Got not implemented request to process",
class => Class,
operation_id => OperationID,
request => Req,
context => Context}),
{false, Req, Context}.
-spec provide_callback(atom(), {{packageName}}_api:operation_id(), cowboy_req:req(), context()) ->
{cowboy_req:resp_body(), cowboy_req:req(), context()}.
provide_callback(Class, OperationID, Req, Context) ->
?LOG_ERROR(#{what => "Got not implemented request to process",
class => Class,
operation_id => OperationID,
request => Req,
context => Context}),
{<<>>, Req, Context}.
{{#authMethods}}
{{#isApiKey}}
-spec authorize_api_key(Handler :: atom(), OperationID :: {{packageName}}_api:operation_id(), ApiKey :: binary()) ->
Result :: false | {true, context()}.
authorize_api_key(Handler, OperationID, ApiKey) ->
Handler:authorize_api_key(OperationID, ApiKey).
{{/isApiKey}}
{{/authMethods}}
{{^authMethods}}
-spec authorize_api_key(Handler :: atom(), OperationID :: {{packageName}}_api:operation_id(), ApiKey :: binary()) ->
Result :: false.
authorize_api_key(_Handler, _OperationID, _ApiKey) ->
false.
{{/authMethods}}

View File

@@ -1,15 +1,6 @@
{minimum_otp_vsn, "27"}.
{deps, [
{cowboy, "2.12.0"},
{ranch, "2.1.0"},
{jesse, "1.8.1"}
{cowboy, {git, "https://github.com/ninenines/cowboy.git", {tag, "2.8.0"}}},
{rfc3339, {git, "https://github.com/talentdeficit/rfc3339.git", {tag, "master"}}},
{jsx, {git, "https://github.com/talentdeficit/jsx.git", {tag, "v3.1.0"}}},
{jesse, {git, "https://github.com/for-GET/jesse.git", {tag, "1.5.6"}}}
]}.
{dialyzer,
[{plt_extra_apps, [cowboy, cowlib, ranch, jesse]},
{warnings, [missing_return, unknown]}
]}.
{xref_checks,
[undefined_function_calls, deprecated_function_calls, deprecated_functions]}.

View File

@@ -1,36 +1,57 @@
-module({{packageName}}_router).
-export([get_paths/1]).
-export([get_paths/1, get_validator_state/0]).
-type method() :: binary().
-type operations() :: #{method() => {{packageName}}_api:operation_id()}.
-type init_opts() :: {operations(), module()}.
-type operations() :: #{
Method :: binary() => {{packageName}}_api:operation_id()
}.
-type init_opts() :: {
Operations :: operations(),
LogicHandler :: atom(),
ValidatorMod :: module()
}.
-export_type([init_opts/0]).
-spec get_paths(LogicHandler :: module()) -> cowboy_router:routes().
-spec get_paths(LogicHandler :: atom()) -> [{'_',[{
Path :: string(),
Handler :: atom(),
InitOpts :: init_opts()
}]}].
get_paths(LogicHandler) ->
ValidatorState = prepare_validator(),
PreparedPaths = maps:fold(
fun(Path, #{operations := Operations, handler := Handler}, Acc) ->
[{Path, Handler, Operations} | Acc]
end, [], group_paths()
),
[{'_', [{P, H, {O, LogicHandler}} || {P, H, O} <- PreparedPaths]}].
fun(Path, #{operations := Operations, handler := Handler}, Acc) ->
[{Path, Handler, Operations} | Acc]
end,
[],
group_paths()
),
[
{'_',
[{P, H, {O, LogicHandler, ValidatorState}} || {P, H, O} <- PreparedPaths]
}
].
group_paths() ->
maps:fold(
fun(OperationID, #{path := Path, method := Method, handler := Handler}, Acc) ->
case maps:find(Path, Acc) of
{ok, PathInfo0 = #{operations := Operations0}} ->
Operations = Operations0#{Method => OperationID},
PathInfo = PathInfo0#{operations => Operations},
Acc#{Path => PathInfo};
error ->
Operations = #{Method => OperationID},
PathInfo = #{handler => Handler, operations => Operations},
Acc#{Path => PathInfo}
end
end, #{}, get_operations()).
fun(OperationID, #{path := Path, method := Method, handler := Handler}, Acc) ->
case maps:find(Path, Acc) of
{ok, PathInfo0 = #{operations := Operations0}} ->
Operations = Operations0#{Method => OperationID},
PathInfo = PathInfo0#{operations => Operations},
Acc#{Path => PathInfo};
error ->
Operations = #{Method => OperationID},
PathInfo = #{handler => Handler, operations => Operations},
Acc#{Path => PathInfo}
end
end,
#{},
get_operations()
).
get_operations() ->
#{ {{#apiInfo}}{{#apis}}{{#operations}}{{#operation}}
@@ -40,3 +61,18 @@ get_operations() ->
handler => '{{classname}}'
}{{^-last}},{{/-last}}{{/operation}}{{^-last}},{{/-last}}{{/operations}}{{/apis}}{{/apiInfo}}
}.
get_validator_state() ->
persistent_term:get({?MODULE, validator_state}).
prepare_validator() ->
R = jsx:decode(element(2, file:read_file(get_openapi_path()))),
JesseState = jesse_state:new(R, [{default_schema_ver, <<"http://json-schema.org/draft-04/schema#">>}]),
persistent_term:put({?MODULE, validator_state}, JesseState),
?MODULE.
get_openapi_path() ->
{ok, AppName} = application:get_application(?MODULE),
filename:join({{packageName}}_utils:priv_dir(AppName), "{{{openAPISpecName}}}.json").

View File

@@ -1,21 +1,26 @@
-module({{packageName}}_server).
-define(DEFAULT_LOGIC_HANDLER, {{packageName}}_logic_handler).
-define(DEFAULT_LOGIC_HANDLER, {{packageName}}_default_logic_handler).
-export([start/2]).
-ignore_xref([start/2]).
-spec start(term(), #{transport => tcp | ssl,
transport_opts => ranch:opts(),
protocol_opts => cowboy:opts(),
logic_handler => module()}) ->
{ok, pid()} | {error, any()}.
start(ID, Params) ->
Transport = maps:get(transport, Params, tcp),
TransportOpts = maps:get(transport_opts, Params, #{}),
ProtocolOpts = maps:get(procotol_opts, Params, #{}),
-spec start( ID :: any(), #{
ip => inet:ip_address(),
port => inet:port_number(),
logic_handler => module(),
net_opts => []
}) -> {ok, pid()} | {error, any()}.
start(ID, #{
ip := IP ,
port := Port,
net_opts := NetOpts
} = Params) ->
{Transport, TransportOpts} = get_socket_transport(IP, Port, NetOpts),
LogicHandler = maps:get(logic_handler, Params, ?DEFAULT_LOGIC_HANDLER),
CowboyOpts = get_cowboy_config(LogicHandler, ProtocolOpts),
ExtraOpts = maps:get(cowboy_extra_opts, Params, []),
CowboyOpts = get_cowboy_config(LogicHandler, ExtraOpts),
case Transport of
ssl ->
cowboy:start_tls(ID, TransportOpts, CowboyOpts);
@@ -23,17 +28,33 @@ start(ID, Params) ->
cowboy:start_clear(ID, TransportOpts, CowboyOpts)
end.
get_cowboy_config(LogicHandler, ExtraOpts) ->
DefaultOpts = get_default_opts(LogicHandler),
maps:fold(fun get_cowboy_config/3, DefaultOpts, ExtraOpts).
get_socket_transport(IP, Port, Options) ->
Opts = [
{ip, IP},
{port, Port}
],
case {{packageName}}_utils:get_opt(ssl, Options) of
SslOpts = [_|_] ->
{ssl, Opts ++ SslOpts};
undefined ->
{tcp, Opts}
end.
get_cowboy_config(env, #{dispatch := _Dispatch} = Env, AccIn) ->
maps:put(env, Env, AccIn);
get_cowboy_config(env, NewEnv, #{env := OldEnv} = AccIn) ->
Env = maps:merge(OldEnv, NewEnv),
maps:put(env, Env, AccIn);
get_cowboy_config(Key, Value, AccIn) ->
maps:put(Key, Value, AccIn).
get_cowboy_config(LogicHandler, ExtraOpts) ->
get_cowboy_config(LogicHandler, ExtraOpts, get_default_opts(LogicHandler)).
get_cowboy_config(_LogicHandler, [], Opts) ->
Opts;
get_cowboy_config(LogicHandler, [{env, Env} | Rest], Opts) ->
NewEnv = case proplists:get_value(dispatch, Env) of
undefined -> [get_default_dispatch(LogicHandler) | Env];
_ -> Env
end,
get_cowboy_config(LogicHandler, Rest, store_key(env, NewEnv, Opts));
get_cowboy_config(LogicHandler, [{Key, Value}| Rest], Opts) ->
get_cowboy_config(LogicHandler, Rest, store_key(Key, Value, Opts)).
get_default_dispatch(LogicHandler) ->
Paths = {{packageName}}_router:get_paths(LogicHandler),
@@ -41,3 +62,6 @@ get_default_dispatch(LogicHandler) ->
get_default_opts(LogicHandler) ->
#{env => get_default_dispatch(LogicHandler)}.
store_key(Key, Value, Opts) ->
maps:put(Key, Value, Opts).

View File

@@ -21,51 +21,36 @@ import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.SerialDescriptor
{{/kotlinx_serialization}}
{{^threetenbp}}
{{^kotlinx-datetime}}
import java.time.LocalDate
import java.time.format.DateTimeFormatter
{{/kotlinx-datetime}}
{{/threetenbp}}
{{#threetenbp}}
import org.threeten.bp.LocalDate
import org.threeten.bp.format.DateTimeFormatter
{{/threetenbp}}
{{#kotlinx-datetime}}
import kotlinx.datetime.LocalDate
{{/kotlinx-datetime}}
{{#moshi}}
{{#nonPublicApi}}internal {{/nonPublicApi}}class LocalDateAdapter {
@ToJson
fun toJson(value: LocalDate): String {
{{#kotlinx-datetime}}
return value.toString()
{{/kotlinx-datetime}}
{{^kotlinx-datetime}}
return DateTimeFormatter.ISO_LOCAL_DATE.format(value)
{{/kotlinx-datetime}}
}
@FromJson
fun fromJson(value: String): LocalDate {
return LocalDate.parse(value{{^kotlinx-datetime}}, DateTimeFormatter.ISO_LOCAL_DATE{{/kotlinx-datetime}})
return LocalDate.parse(value, DateTimeFormatter.ISO_LOCAL_DATE)
}
}
{{/moshi}}
{{#gson}}
{{#nonPublicApi}}internal {{/nonPublicApi}}class LocalDateAdapter({{^kotlinx-datetime}}private val formatter: DateTimeFormatter = DateTimeFormatter.ISO_LOCAL_DATE{{/kotlinx-datetime}}) : TypeAdapter<LocalDate>() {
{{#nonPublicApi}}internal {{/nonPublicApi}}class LocalDateAdapter(private val formatter: DateTimeFormatter = DateTimeFormatter.ISO_LOCAL_DATE) : TypeAdapter<LocalDate>() {
@Throws(IOException::class)
override fun write(out: JsonWriter?, value: LocalDate?) {
if (value == null) {
out?.nullValue()
} else {
{{#kotlinx-datetime}}
out?.value(value.toString())
{{/kotlinx-datetime}}
{{^kotlinx-datetime}}
out?.value(formatter.format(value))
{{/kotlinx-datetime}}
}
}
@@ -79,14 +64,13 @@ import kotlinx.datetime.LocalDate
return null
}
else -> {
return LocalDate.parse(out.nextString(){{^kotlinx-datetime}}, formatter{{/kotlinx-datetime}})
return LocalDate.parse(out.nextString(), formatter)
}
}
}
}
{{/gson}}
{{#kotlinx_serialization}}
{{^kotlinx-datetime}}
@Serializer(forClass = LocalDate::class)
{{#nonPublicApi}}internal {{/nonPublicApi}}object LocalDateAdapter : KSerializer<LocalDate> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.STRING)
@@ -99,5 +83,4 @@ import kotlinx.datetime.LocalDate
return LocalDate.parse(decoder.decodeString(), DateTimeFormatter.ISO_LOCAL_DATE)
}
}
{{/kotlinx-datetime}}
{{/kotlinx_serialization}}

View File

@@ -21,51 +21,36 @@ import kotlinx.serialization.descriptors.PrimitiveKind
import kotlinx.serialization.descriptors.SerialDescriptor
{{/kotlinx_serialization}}
{{^threetenbp}}
{{^kotlinx-datetime}}
import java.time.LocalDateTime
import java.time.format.DateTimeFormatter
{{/kotlinx-datetime}}
{{/threetenbp}}
{{#threetenbp}}
import org.threeten.bp.LocalDateTime
import org.threeten.bp.format.DateTimeFormatter
{{/threetenbp}}
{{#kotlinx-datetime}}
import kotlinx.datetime.LocalDateTime
{{/kotlinx-datetime}}
{{#moshi}}
{{#nonPublicApi}}internal {{/nonPublicApi}}class LocalDateTimeAdapter {
@ToJson
fun toJson(value: LocalDateTime): String {
{{#kotlinx-datetime}}
return value.toString()
{{/kotlinx-datetime}}
{{^kotlinx-datetime}}
return DateTimeFormatter.ISO_LOCAL_DATE_TIME.format(value)
{{/kotlinx-datetime}}
}
@FromJson
fun fromJson(value: String): LocalDateTime {
return LocalDateTime.parse(value{{^kotlinx-datetime}}, DateTimeFormatter.ISO_LOCAL_DATE_TIME{{/kotlinx-datetime}})
return LocalDateTime.parse(value, DateTimeFormatter.ISO_LOCAL_DATE_TIME)
}
}
{{/moshi}}
{{#gson}}
{{#nonPublicApi}}internal {{/nonPublicApi}}class LocalDateTimeAdapter({{^kotlinx-datetime}}private val formatter: DateTimeFormatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME{{/kotlinx-datetime}}) : TypeAdapter<LocalDateTime>() {
{{#nonPublicApi}}internal {{/nonPublicApi}}class LocalDateTimeAdapter(private val formatter: DateTimeFormatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME) : TypeAdapter<LocalDateTime>() {
@Throws(IOException::class)
override fun write(out: JsonWriter?, value: LocalDateTime?) {
if (value == null) {
out?.nullValue()
} else {
{{#kotlinx-datetime}}
out?.value(value.toString())
{{/kotlinx-datetime}}
{{^kotlinx-datetime}}
out?.value(formatter.format(value))
{{/kotlinx-datetime}}
}
}
@@ -79,14 +64,13 @@ import kotlinx.datetime.LocalDateTime
return null
}
else -> {
return LocalDateTime.parse(out.nextString(){{^kotlinx-datetime}}, formatter{{/kotlinx-datetime}})
return LocalDateTime.parse(out.nextString(), formatter)
}
}
}
}
{{/gson}}
{{#kotlinx_serialization}}
{{^kotlinx-datetime}}
@Serializer(forClass = LocalDateTime::class)
{{#nonPublicApi}}internal {{/nonPublicApi}}object LocalDateTimeAdapter : KSerializer<LocalDateTime> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LocalDateTime", PrimitiveKind.STRING)
@@ -99,5 +83,4 @@ import kotlinx.datetime.LocalDateTime
return LocalDateTime.parse(decoder.decodeString(), DateTimeFormatter.ISO_LOCAL_DATE_TIME)
}
}
{{/kotlinx-datetime}}
{{/kotlinx_serialization}}

View File

@@ -1,56 +0,0 @@
package {{packageName}}.infrastructure
{{#moshi}}
import com.squareup.moshi.FromJson
import com.squareup.moshi.ToJson
{{/moshi}}
{{#gson}}
import com.google.gson.TypeAdapter
import com.google.gson.stream.JsonReader
import com.google.gson.stream.JsonWriter
import com.google.gson.stream.JsonToken.NULL
import java.io.IOException
{{/gson}}
import kotlinx.datetime.LocalTime
{{#moshi}}
{{#nonPublicApi}}internal {{/nonPublicApi}}class LocalTimeAdapter {
@ToJson
fun toJson(value: LocalTime): String {
return value.toString()
}
@FromJson
fun fromJson(value: String): LocalTime {
return LocalTime.parse(value)
}
}
{{/moshi}}
{{#gson}}
{{#nonPublicApi}}internal {{/nonPublicApi}}class LocalTimeAdapter : TypeAdapter<LocalTime>() {
@Throws(IOException::class)
override fun write(out: JsonWriter?, value: LocalTime?) {
if (value == null) {
out?.nullValue()
} else {
out?.value(value.toString())
}
}
@Throws(IOException::class)
override fun read(out: JsonReader?): LocalTime? {
out ?: return null
when (out.peek()) {
NULL -> {
out.nextNull()
return null
}
else -> {
return LocalTime.parse(out.nextString())
}
}
}
}
{{/gson}}

View File

@@ -24,12 +24,13 @@ import org.threeten.bp.OffsetDateTime
{{/threetenbp}}
{{#kotlinx-datetime}}
import kotlinx.datetime.Instant
import kotlinx.datetime.LocalTime
{{/kotlinx-datetime}}
import java.util.UUID
{{/gson}}
{{#jackson}}
{{#enumUnknownDefaultCase}}
import com.fasterxml.jackson.databind.DeserializationFeature
{{/enumUnknownDefaultCase}}
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.databind.SerializationFeature
import com.fasterxml.jackson.annotation.JsonInclude
@@ -65,7 +66,6 @@ import java.util.concurrent.atomic.AtomicLong
.add(OffsetDateTimeAdapter())
{{#kotlinx-datetime}}
.add(InstantAdapter())
.add(LocalTimeAdapter())
{{/kotlinx-datetime}}
.add(LocalDateTimeAdapter())
.add(LocalDateAdapter())
@@ -92,7 +92,6 @@ import java.util.concurrent.atomic.AtomicLong
.registerTypeAdapter(OffsetDateTime::class.java, OffsetDateTimeAdapter())
{{#kotlinx-datetime}}
.registerTypeAdapter(Instant::class.java, InstantAdapter())
.registerTypeAdapter(LocalTime::class.java, LocalTimeAdapter())
{{/kotlinx-datetime}}
.registerTypeAdapter(LocalDateTime::class.java, LocalDateTimeAdapter())
.registerTypeAdapter(LocalDate::class.java, LocalDateAdapter())
@@ -112,7 +111,6 @@ import java.util.concurrent.atomic.AtomicLong
.configure(DeserializationFeature.READ_UNKNOWN_ENUM_VALUES_USING_DEFAULT_VALUE, true)
{{/enumUnknownDefaultCase}}
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, {{failOnUnknownProperties}})
{{/jackson}}
{{#kotlinx_serialization}}
@Deprecated("Use Serializer.kotlinxSerializationAdapters instead", replaceWith = ReplaceWith("Serializer.kotlinxSerializationAdapters"))
@@ -124,11 +122,9 @@ import java.util.concurrent.atomic.AtomicLong
val kotlinxSerializationAdapters = SerializersModule {
contextual(BigDecimal::class, BigDecimalAdapter)
contextual(BigInteger::class, BigIntegerAdapter)
{{^kotlinx-datetime}}
contextual(LocalDate::class, LocalDateAdapter)
contextual(LocalDateTime::class, LocalDateTimeAdapter)
contextual(OffsetDateTime::class, OffsetDateTimeAdapter)
{{/kotlinx-datetime}}
contextual(UUID::class, UUIDAdapter)
contextual(AtomicInteger::class, AtomicIntegerAdapter)
contextual(AtomicLong::class, AtomicLongAdapter)

View File

@@ -1,6 +1,5 @@
package {{apiPackage}}
import org.springframework.context.annotation.Configuration
import org.springframework.http.HttpStatus
import org.springframework.web.bind.annotation.ControllerAdvice
import org.springframework.web.bind.annotation.ExceptionHandler
@@ -12,7 +11,7 @@ sealed class ApiException(msg: String, val code: Int) : Exception(msg)
class NotFoundException(msg: String, code: Int = HttpStatus.NOT_FOUND.value()) : ApiException(msg, code)
@Configuration("{{apiPackage}}.DefaultExceptionHandler")
@ControllerAdvice
class DefaultExceptionHandler {

View File

@@ -36,8 +36,8 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxCoroutinesVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:$kotlinxCoroutinesVersion"){{/reactive}}{{#springDocDocumentationProvider}}{{#useSwaggerUI}}
implementation("org.springdoc:springdoc-openapi-starter-{{#reactive}}webflux{{/reactive}}{{^reactive}}webmvc{{/reactive}}-ui:2.6.0"){{/useSwaggerUI}}{{^useSwaggerUI}}
implementation("org.springdoc:springdoc-openapi-starter-{{#reactive}}webflux{{/reactive}}{{^reactive}}webmvc{{/reactive}}-api:2.6.0"){{/useSwaggerUI}}{{/springDocDocumentationProvider}}{{#springFoxDocumentationProvider}}
implementation("org.springdoc:springdoc-openapi-starter-{{#reactive}}webflux{{/reactive}}{{^reactive}}webmvc{{/reactive}}-ui:2.2.0"){{/useSwaggerUI}}{{^useSwaggerUI}}
implementation("org.springdoc:springdoc-openapi-{{#reactive}}webflux{{/reactive}}{{^reactive}}webmvc{{/reactive}}-core:2.2.0-M5"){{/useSwaggerUI}}{{/springDocDocumentationProvider}}{{#springFoxDocumentationProvider}}
implementation("io.springfox:springfox-swagger2:2.9.2"){{/springFoxDocumentationProvider}}{{#useSwaggerUI}}{{^springDocDocumentationProvider}}
implementation("org.webjars:swagger-ui:4.10.3")
implementation("org.webjars:webjars-locator-core"){{/springDocDocumentationProvider}}{{/useSwaggerUI}}{{^springFoxDocumentationProvider}}{{^springDocDocumentationProvider}}{{#swagger1AnnotationLibrary}}

View File

@@ -44,8 +44,8 @@ dependencies {
implementation("org.springframework.boot:spring-boot-starter-webflux")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:$kotlinxCoroutinesVersion")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-reactor:$kotlinxCoroutinesVersion"){{/reactive}}{{#springDocDocumentationProvider}}{{#useSwaggerUI}}
implementation("org.springdoc:springdoc-openapi-starter-{{#reactive}}webflux{{/reactive}}{{^reactive}}webmvc{{/reactive}}-ui:2.6.0"){{/useSwaggerUI}}{{^useSwaggerUI}}
implementation("org.springdoc:springdoc-openapi-starter-{{#reactive}}webflux{{/reactive}}{{^reactive}}webmvc{{/reactive}}-api:2.6.0"){{/useSwaggerUI}}{{/springDocDocumentationProvider}}{{#springFoxDocumentationProvider}}
implementation("org.springdoc:springdoc-openapi-starter-{{#reactive}}webflux{{/reactive}}{{^reactive}}webmvc{{/reactive}}-ui:2.0.0-M5"){{/useSwaggerUI}}{{^useSwaggerUI}}
implementation("org.springdoc:springdoc-openapi-{{#reactive}}webflux{{/reactive}}{{^reactive}}webmvc{{/reactive}}-core:2.0.0-M5"){{/useSwaggerUI}}{{/springDocDocumentationProvider}}{{#springFoxDocumentationProvider}}
implementation("io.springfox:springfox-swagger2:2.9.2"){{/springFoxDocumentationProvider}}{{#useSwaggerUI}}{{^springDocDocumentationProvider}}
implementation("org.webjars:swagger-ui:4.10.3")
implementation("org.webjars:webjars-locator-core"){{/springDocDocumentationProvider}}{{/useSwaggerUI}}{{^springFoxDocumentationProvider}}{{^springDocDocumentationProvider}}{{#swagger1AnnotationLibrary}}

View File

@@ -2,15 +2,8 @@ enum {{classname}}: {{vendorExtensions.x-php-enum-type}}
{
{{#allowableValues}}
{{#enumVars}}
{{#enumDescription}}
/**
* {{enumDescription}}
*/
{{/enumDescription}}
case {{{name}}} = {{{value}}};
{{^-last}}
case {{^isString}}NUMBER_{{/isString}}{{{name}}} = {{{value}}};
{{/-last}}
{{/enumVars}}
{{/allowableValues}}
}

View File

@@ -52,7 +52,7 @@ class {{classname}} {{#parentSchema}}extends {{{parent}}} {{/parentSchema}}
*/
public function getSerialized{{nameInPascalCase}}(): string|null
{
return !is_null($this->{{name}}?->value) ? (string) $this->{{name}}->value : null;
return $this->{{name}}?->value ? (string) $this->{{name}}->value : null;
}
/**

View File

@@ -5,12 +5,7 @@ class {{classname}}
*/
{{#allowableValues}}
{{#enumVars}}
{{#enumDescription}}
/**
* {{enumDescription}}
*/
{{/enumDescription}}
public const {{{name}}} = {{{value}}};
public const {{^isString}}NUMBER_{{/isString}}{{{name}}} = {{{value}}};
{{/enumVars}}
{{/allowableValues}}
@@ -23,7 +18,7 @@ class {{classname}}
return [
{{#allowableValues}}
{{#enumVars}}
self::{{{name}}}{{^-last}},
self::{{^isString}}NUMBER_{{/isString}}{{{name}}}{{^-last}},
{{/-last}}
{{/enumVars}}
{{/allowableValues}}

View File

@@ -176,8 +176,7 @@ class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}}
if callable(to_json):
return self.actual_instance.to_dict()
else:
# primitive type
return self.actual_instance
return json.dumps(self.actual_instance)
def to_str(self) -> str:
"""Returns the string representation of the actual instance"""

View File

@@ -7,31 +7,17 @@ import unittest
from {{apiPackage}}.{{classFilename}} import {{classname}} # noqa: E501
class {{#operations}}Test{{classname}}(unittest.{{#asyncio}}IsolatedAsyncio{{/asyncio}}TestCase):
class {{#operations}}Test{{classname}}(unittest.TestCase):
"""{{classname}} unit test stubs"""
{{#asyncio}}
async def asyncSetUp(self) -> None:
self.api = {{classname}}()
async def asyncTearDown(self) -> None:
pass
{{/asyncio}}
{{^asyncio}}
def setUp(self) -> None:
self.api = {{classname}}()
self.api = {{classname}}() # noqa: E501
def tearDown(self) -> None:
pass
{{/asyncio}}
{{#operation}}
{{#asyncio}}
async def test_{{operationId}}(self) -> None:
{{/asyncio}}
{{^asyncio}}
{{#operation}}
def test_{{operationId}}(self) -> None:
{{/asyncio}}
"""Test case for {{{operationId}}}
{{#summary}}

View File

@@ -143,11 +143,6 @@ class RESTClientObject:
filename=v[0],
content_type=v[2])
else:
# Ensures that dict objects are serialized
if isinstance(v, dict):
v = json.dumps(v)
elif isinstance(v, int):
v = str(v)
data.add_field(k, v)
args["data"] = data

View File

@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
steps:
- uses: actions/checkout@v3

View File

@@ -166,8 +166,7 @@ class {{classname}}({{#parent}}{{{.}}}{{/parent}}{{^parent}}BaseModel{{/parent}}
if callable(to_json):
return self.actual_instance.to_dict()
else:
# primitive type
return self.actual_instance
return json.dumps(self.actual_instance)
def to_str(self) -> str:
"""Returns the string representation of the actual instance"""

View File

@@ -10,7 +10,7 @@ keywords = ["OpenAPI", "OpenAPI-Generator", "{{{appName}}}"]
include = ["{{packageName}}/py.typed"]
[tool.poetry.dependencies]
python = "^3.8"
python = "^3.7"
urllib3 = ">= 1.25.3"
python-dateutil = ">=2.8.2"

View File

@@ -1,6 +1,6 @@
python_dateutil >= 2.5.3
setuptools >= 21.0.0
urllib3 >= 1.25.3, < 3.0.0
urllib3 >= 1.25.3, < 2.1.0
pydantic >= 1.10.5, < 2
aenum >= 3.1.11
{{#asyncio}}

View File

@@ -190,8 +190,6 @@ class RESTClientObject:
# Content-Type which generated by urllib3 will be
# overwritten.
del headers['Content-Type']
# Ensures that dict objects are serialized
post_params = [(a, json.dumps(b)) if isinstance(b, dict) else (a,b) for a, b in post_params]
r = self.pool_manager.request(
method, url,
fields=post_params,

View File

@@ -17,7 +17,7 @@ PYTHON_REQUIRES = ">=3.7"
{{#apis}}
{{#-last}}
REQUIRES = [
"urllib3 >= 1.25.3, < 3.0.0",
"urllib3 >= 1.25.3, < 2.1.0",
"python-dateutil",
{{#asyncio}}
"aiohttp >= 3.0.0",

View File

@@ -7,31 +7,17 @@ import unittest
from {{apiPackage}}.{{classFilename}} import {{classname}}
class {{#operations}}Test{{classname}}(unittest.{{#asyncio}}IsolatedAsyncio{{/asyncio}}TestCase):
class {{#operations}}Test{{classname}}(unittest.TestCase):
"""{{classname}} unit test stubs"""
{{#asyncio}}
async def asyncSetUp(self) -> None:
self.api = {{classname}}()
async def asyncTearDown(self) -> None:
pass
{{/asyncio}}
{{^asyncio}}
def setUp(self) -> None:
self.api = {{classname}}()
def tearDown(self) -> None:
pass
{{/asyncio}}
{{#operation}}
{{#asyncio}}
async def test_{{operationId}}(self) -> None:
{{/asyncio}}
{{^asyncio}}
{{#operation}}
def test_{{operationId}}(self) -> None:
{{/asyncio}}
"""Test case for {{{operationId}}}
{{#summary}}

View File

@@ -174,11 +174,6 @@ class RESTClientObject:
content_type=v[2]
)
else:
# Ensures that dict objects are serialized
if isinstance(v, dict):
v = json.dumps(v)
elif isinstance(v, int):
v = str(v)
data.add_field(k, v)
args["data"] = data
@@ -203,3 +198,8 @@ class RESTClientObject:
r = await pool_manager.request(**args)
return RESTResponse(r)

View File

@@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
steps:
- uses: actions/checkout@v3

View File

@@ -10,7 +10,7 @@ keywords = ["OpenAPI", "OpenAPI-Generator", "{{{appName}}}"]
include = ["{{packageName}}/py.typed"]
[tool.poetry.dependencies]
python = "^3.8"
python = "^3.7"
urllib3 = ">= 1.25.3"
python-dateutil = ">=2.8.2"
@@ -33,7 +33,7 @@ pytest = ">=7.2.1"
tox = ">=3.9.0"
flake8 = ">=4.0.0"
types-python-dateutil = ">=2.8.19.14"
mypy = ">=1.5"
mypy = "1.4.1"
[build-system]

View File

@@ -1,6 +1,6 @@
python_dateutil >= 2.5.3
setuptools >= 21.0.0
urllib3 >= 1.25.3, < 3.0.0
urllib3 >= 1.25.3, < 2.1.0
pydantic >= 2
typing-extensions >= 4.7.1
{{#asyncio}}

View File

@@ -17,7 +17,7 @@ PYTHON_REQUIRES = ">=3.7"
{{#apis}}
{{#-last}}
REQUIRES = [
"urllib3 >= 1.25.3, < 3.0.0",
"urllib3 >= 1.25.3, < 2.1.0",
"python-dateutil",
{{#asyncio}}
"aiohttp >= 3.0.0",

View File

@@ -95,7 +95,7 @@
{{#hasAuthMethods}}
#[allow(clippy::collapsible_match)]
if let Some(auth_data) = Has::<Option<AuthData>>::get(context).as_ref() {
{{! Currently only authentication with Basic and Bearer are supported }}
// Currently only authentication with Basic and Bearer are supported
#[allow(clippy::single_match, clippy::match_single_binding)]
match auth_data {
{{#authMethods}}

View File

@@ -18,7 +18,7 @@ use crate::header;
/// which helps with FFI.
#[allow(non_camel_case_types)]
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, Hash)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)]
#[cfg_attr(feature = "conversion", derive(frunk_enum_derive::LabelledGenericEnum))]{{#xmlName}}
#[serde(rename = "{{{.}}}")]{{/xmlName}}
pub enum {{{classname}}} {
@@ -332,7 +332,7 @@ pub struct {{{classname}}} {
)]
{{/hasValidation}}
{{#required}}
pub {{{name}}}: {{{dataType}}},
pub {{{name}}}: {{#isNullable}}swagger::Nullable<{{/isNullable}}{{{dataType}}}{{#isNullable}}>{{/isNullable}},
{{/required}}
{{^required}}
{{#isNullable}}
@@ -340,7 +340,7 @@ pub struct {{{classname}}} {
#[serde(default = "swagger::nullable_format::default_optional_nullable")]
{{/isNullable}}
#[serde(skip_serializing_if="Option::is_none")]
pub {{{name}}}: Option<{{{dataType}}}>,
pub {{{name}}}: Option<{{#isNullable}}swagger::Nullable<{{/isNullable}}{{{dataType}}}{{#isNullable}}>{{/isNullable}}>,
{{/required}}
{{/vars}}
@@ -373,7 +373,7 @@ fn validate_byte_{{#lambda.lowercase}}{{{classname}}}_{{{name}}}{{/lambda.lowerc
impl {{{classname}}} {
#[allow(clippy::new_without_default)]
pub fn new({{#vars}}{{^defaultValue}}{{{name}}}: {{{dataType}}}, {{/defaultValue}}{{/vars}}) -> {{{classname}}} {
pub fn new({{#vars}}{{^defaultValue}}{{{name}}}: {{#isNullable}}swagger::Nullable<{{/isNullable}}{{{dataType}}}{{#isNullable}}>{{/isNullable}}, {{/defaultValue}}{{/vars}}) -> {{{classname}}} {
{{{classname}}} {
{{#vars}} {{#defaultValue}}{{{name}}}: {{{defaultValue}}}{{/defaultValue}}{{^defaultValue}}{{{name}}}{{/defaultValue}},
{{/vars}}
@@ -389,21 +389,18 @@ impl std::string::ToString for {{{classname}}} {
let params: Vec<Option<String>> = vec![
{{#vars}}
{{#isByteArray}}
// Skipping byte array {{baseName}} in query parameter serialization
// Skipping {{baseName}} in query parameter serialization
{{/isByteArray}}
{{^isByteArray}}
{{#isBinary}}
// Skipping binary data {{baseName}} in query parameter serialization
// Skipping {{baseName}} in query parameter serialization
{{/isBinary}}
{{^isBinary}}
{{#isMap}}
// Skipping map {{baseName}} in query parameter serialization
// Skipping {{baseName}} in query parameter serialization
{{/isMap}}
{{^isMap}}
{{^isPrimitiveType}}
// Skipping non-primitive type {{baseName}} in query parameter serialization
// Skipping {{baseName}} in query parameter serialization
{{/isPrimitiveType}}
{{#isPrimitiveType}}
{{^isByteArray}}{{^isBinary}}{{^isMap}}{{#isPrimitiveType}}
{{#required}}
Some("{{{baseName}}}".to_string()),
{{^isArray}}
@@ -446,10 +443,7 @@ impl std::string::ToString for {{{classname}}} {
].join(",")
}),
{{/required}}
{{/isPrimitiveType}}
{{/isMap}}
{{/isBinary}}
{{/isByteArray}}
{{/isPrimitiveType}}{{/isMap}}{{/isBinary}}{{/isByteArray}}
{{/vars}}
];

View File

@@ -155,12 +155,9 @@ extension KeyedEncodingContainerProtocol {
}
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} mutating func encode(_ value: Decimal, forKey key: Self.Key) throws {
let decimalNumber = NSDecimalNumber(decimal: value)
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
numberFormatter.locale = Locale(identifier: "en_US")
let formattedString = numberFormatter.string(from: decimalNumber) ?? "\(value)"
try encode(formattedString, forKey: key)
var mutableValue = value
let stringValue = NSDecimalString(&mutableValue, Locale(identifier: "en_US"))
try encode(stringValue, forKey: key)
}
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} mutating func encodeIfPresent(_ value: Decimal?, forKey key: Self.Key) throws {

View File

@@ -1,7 +1,7 @@
/**
* {{{description}}}
* @export
* @enum {{=<% %>=}}{<%&dataType%>}<%={{ }}=%>
* @enum {string}
*/
{{#isBoolean}}
export type {{classname}} = {{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}} | {{/-last}}{{/enumVars}}{{/allowableValues}}

View File

@@ -90,7 +90,7 @@ public class GoGinServerCodegenTest {
.setGeneratorName("go-gin-server")
.setGitUserId("my-user")
.setGitRepoId("my-repo")
.setPackageName("mypackage")
.setPackageName("my-package")
.setOutputDir(output.getAbsolutePath().replace("\\", "/"));
}

View File

@@ -1,86 +0,0 @@
/*
* 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.goserver;
import org.openapitools.codegen.DefaultGenerator;
import org.openapitools.codegen.TestUtils;
import org.openapitools.codegen.config.CodegenConfigurator;
import org.testng.Assert;
import org.testng.annotations.Test;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
public class GoServerCodegenTest {
@Test
public void verifyGoMod() throws IOException {
File output = Files.createTempDirectory("test").toFile();
output.deleteOnExit();
final CodegenConfigurator configurator = createDefaultCodegenConfigurator(output)
.setInputSpec("src/test/resources/3_0/go-server/route-order.yaml");
DefaultGenerator generator = new DefaultGenerator();
List<File> files = generator.opts(configurator.toClientOptInput()).generate();
files.forEach(File::deleteOnExit);
TestUtils.assertFileExists(Paths.get(output + "/go.mod"));
TestUtils.assertFileContains(Paths.get(output + "/go.mod"),
"module github.com/my-user/my-repo");
TestUtils.assertFileContains(Paths.get(output + "/go.mod"),
"require github.com/gorilla/mux v1.8.0");
}
@Test
public void verifyOrder() throws IOException {
File output = Files.createTempDirectory("test").toFile();
output.deleteOnExit();
final CodegenConfigurator configurator = createDefaultCodegenConfigurator(output)
.setInputSpec("src/test/resources/3_0/go-server/route-order.yaml");
DefaultGenerator generator = new DefaultGenerator();
List<File> files = generator.opts(configurator.toClientOptInput()).generate();
files.forEach(File::deleteOnExit);
TestUtils.assertFileExists(Paths.get(output + "/go/routers.go"));
TestUtils.assertFileContains(Paths.get(output + "/go/routers.go"),
"type Routes map[string]Route");
TestUtils.assertFileExists(Paths.get(output + "/go/api_dev.go"));
// verify /getPath/latest is first route
Assert.assertEquals(Files.readAllLines(Paths.get(output + "/go/api_dev.go")).get(52), "\t\t\"GetLatest\": Route{");
// verify /getPath/{id} is second route
Assert.assertEquals(Files.readAllLines(Paths.get(output + "/go/api_dev.go")).get(57), "\t\t\"GetById\": Route{");
}
private static CodegenConfigurator createDefaultCodegenConfigurator(File output) {
return new CodegenConfigurator()
.setGeneratorName("go-server")
.setGitUserId("my-user")
.setGitRepoId("my-repo")
.setPackageName("mypackage")
.setOutputDir(output.getAbsolutePath().replace("\\", "/"));
}
}

View File

@@ -34,7 +34,6 @@ import org.openapitools.codegen.DefaultGenerator;
import org.openapitools.codegen.TestUtils;
import org.openapitools.codegen.config.CodegenConfigurator;
import org.openapitools.codegen.languages.KotlinClientCodegen;
import org.openapitools.codegen.testutils.ConfigAssert;
import org.testng.Assert;
import org.testng.annotations.DataProvider;
import org.testng.annotations.Test;
@@ -407,32 +406,6 @@ public class KotlinClientCodegenModelTest {
TestUtils.assertFileNotExists(Paths.get(path, "gradle", "wrapper", "gradle-wrapper.jar"));
}
@Test
public void testFailOnUnknownPropertiesAdditionalProperty() {
final KotlinClientCodegen codegen = new KotlinClientCodegen();
// Default case, nothing provided
codegen.processOpts();
ConfigAssert configAssert = new ConfigAssert(codegen.additionalProperties());
// Default to false
configAssert.assertValue(KotlinClientCodegen.FAIL_ON_UNKNOWN_PROPERTIES, codegen::isFailOnUnknownProperties, Boolean.FALSE);
// Provide true
codegen.additionalProperties().put(KotlinClientCodegen.FAIL_ON_UNKNOWN_PROPERTIES, true);
codegen.processOpts();
// Should be true
configAssert.assertValue(KotlinClientCodegen.FAIL_ON_UNKNOWN_PROPERTIES, codegen::isFailOnUnknownProperties, Boolean.TRUE);
// Provide false
codegen.additionalProperties().put(KotlinClientCodegen.FAIL_ON_UNKNOWN_PROPERTIES, false);
codegen.processOpts();
// Should be false
configAssert.assertValue(KotlinClientCodegen.FAIL_ON_UNKNOWN_PROPERTIES, codegen::isFailOnUnknownProperties, Boolean.FALSE);
}
private static class ModelNameTest {
private final String expectedName;
private final String expectedClassName;

View File

@@ -348,7 +348,7 @@ public class PhpModelTest {
Assert.assertEquals(prope.allowableValues.get("values"), Arrays.asList(1, -1));
HashMap<String, Object> one = new HashMap<String, Object>();
one.put("name", "NUMBER_1");
one.put("name", "1");
one.put("value", "1");
one.put("isString", false);
HashMap<String, Object> minusOne = new HashMap<String, Object>();

View File

@@ -1,30 +0,0 @@
openapi: 3.0.0
info:
version: 1.0.0
title: Test for valid enums
paths:
/test:
get:
responses:
'200':
description: OK
content:
application/json:
schema:
$ref: '#/components/schemas/Sample'
components:
schemas:
Sample:
properties:
type:
enum:
- 'a'
- 'b'
# This enum is invalid for Avro schemas, as it contains a `-`
- 'uh-oh'
# This next one starts with a number, which is invalid for Avro schemas
- '0h_oh'
# The next two is to make sure collisions are resolved properly
- 'coll-ision'
- 'coll_ision'
type: 'string'

View File

@@ -1,33 +0,0 @@
openapi: 3.0.0
info:
version: 1.0.0
title: Simple no path and body param spec
paths:
/getPath/latest:
get:
tags:
- dev
summary: summary
description: description
operationId: getLatest
responses:
'204':
description: successful operation
/getPath/{id}:
get:
tags:
- dev
summary: summary
description: description
operationId: GetById
parameters:
- name: id
in: path
required: true
schema:
type: string
responses:
'204':
description: successful operation

View File

@@ -2066,19 +2066,4 @@ components:
ArrayRef:
type: array
items:
type: string
EnumWithNameAndDescription:
type: integer
enum:
- 1
- 2
- 3
- 4
x-enum-varnames:
- ONE
- "2"
- " 3"
x-enum-descriptions:
- The word one
- The digit two
- The digit three prefixed by a space
type: string

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