forked from loafle/openapi-generator-original
Compare commits
1 Commits
kotin-spri
...
devhl-labs
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
da9ef216a0 |
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
2
.github/PULL_REQUEST_TEMPLATE.md
vendored
@@ -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.
|
||||
|
||||
18
.github/workflows/samples-erlang.yaml
vendored
18
.github/workflows/samples-erlang.yaml
vendored
@@ -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 }}
|
||||
|
||||
24
.github/workflows/samples-go.yaml
vendored
24
.github/workflows/samples-go.yaml
vendored
@@ -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
|
||||
2
.github/workflows/samples-postman.yaml
vendored
2
.github/workflows/samples-postman.yaml
vendored
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
2
.github/workflows/samples-python-server.yaml
vendored
2
.github/workflows/samples-python-server.yaml
vendored
@@ -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
|
||||
|
||||
41
README.md
41
README.md
@@ -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) |
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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:
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
@@ -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>
|
||||
|
||||
@@ -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 < 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|
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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";
|
||||
}
|
||||
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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))
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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");
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}}
|
||||
@@ -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}}
|
||||
@@ -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}}
|
||||
|
||||
@@ -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');
|
||||
}
|
||||
@@ -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}}
|
||||
@@ -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).
|
||||
@@ -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))).
|
||||
@@ -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}}]}
|
||||
]}.
|
||||
@@ -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}.
|
||||
@@ -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).
|
||||
@@ -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}}
|
||||
@@ -1 +0,0 @@
|
||||
{{{openapi-json}}}
|
||||
@@ -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"}}}
|
||||
]}.
|
||||
@@ -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").
|
||||
@@ -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).
|
||||
@@ -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.
|
||||
|
||||
@@ -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))).
|
||||
|
||||
@@ -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}}]}
|
||||
]}.
|
||||
|
||||
@@ -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}.
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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]}.
|
||||
|
||||
@@ -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").
|
||||
|
||||
@@ -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).
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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}}
|
||||
@@ -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)
|
||||
|
||||
@@ -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 {
|
||||
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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}}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"""
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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]
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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}}
|
||||
|
||||
@@ -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}}
|
||||
];
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
/**
|
||||
* {{{description}}}
|
||||
* @export
|
||||
* @enum {{=<% %>=}}{<%&dataType%>}<%={{ }}=%>
|
||||
* @enum {string}
|
||||
*/
|
||||
{{#isBoolean}}
|
||||
export type {{classname}} = {{#allowableValues}}{{#enumVars}}{{{value}}}{{^-last}} | {{/-last}}{{/enumVars}}{{/allowableValues}}
|
||||
|
||||
@@ -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("\\", "/"));
|
||||
}
|
||||
|
||||
|
||||
@@ -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("\\", "/"));
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
@@ -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>();
|
||||
|
||||
@@ -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'
|
||||
@@ -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
|
||||
@@ -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
Reference in New Issue
Block a user