Compare commits

...

44 Commits

Author SHA1 Message Date
William Cheng
ba619010be pre-populate info if its not defined 2024-09-19 12:08:35 +08:00
William Cheng
8084e6722b Revert "add method to return the title"
This reverts commit 090f1b31ba.
2024-09-19 11:58:27 +08:00
William Cheng
0338d84274 update springdoc-openapi-starter to 2.6.0 2024-09-19 11:58:04 +08:00
William Cheng
090f1b31ba add method to return the title 2024-09-19 11:29:27 +08:00
un1024
410113fd6d [kotlin-spring] fix springdoc dependencies (#19579)
* fix springdoc dependencies for kotlin-spring

* fix suffix core to api.
2024-09-19 11:19:42 +08:00
Joscha Feth
171804eef7 feat(avro-schema): logical type support (#19607)
* feat(avro-schema): logical type support

* style: newline at eof
2024-09-19 09:34:51 +08:00
Joscha Feth
0f561b05cb chore: pull request template mentions an old current version (#19609)
Replaces `7.6.0` with `7.x.0`
2024-09-19 09:33:02 +08:00
Mike Phillips
34aeb16c5e Improve urllib3 semver flexibility (#19458) 2024-09-18 16:54:43 +08:00
Jonathan Ballet
40967a3d38 python: test with more modern versions (#19452)
Python 3.12 has been released in October 2023, it should be tested in
the CI.

Python 3.7 is not maintained anymore, removing it from the minimum
required version in `pyproject.toml` files.
2024-09-18 16:53:51 +08:00
William Cheng
b3e72feaa9 update java samples 2024-09-18 16:14:47 +08:00
Samuel Lijin
07787973e8 fix: make generated java readme use correct package (#19563) 2024-09-18 16:03:33 +08:00
Richard Whitehouse
d03c90cb97 [Rust Server] Handle additional properties being nullable (#19594)
With:
```
    sampleObject:
      type: object
      additionalProperties:
        $ref: '#/components/schemas/SampleData'
      minProperties: 1
      description: Map of Charging data policy decisions.
```

and
```
    SampleData:
      type: object
...
      nullable: true
```

We currently generate: HashMap<String, SampleData>, which doesn't allow
null charging data entries. This MR changes this to be
`HashMap<String, swagger::Nullable<SampleData>>`, which thus will allow null data entries.

We do this by moving null-handling to the Java code - primarily `getTypeDeclaration()`.

Note, to some extent this is wrong. In this MR (and previously) we are treating
`nullable: true` as an extrinsic property (like required), whereas it should be an
intrinsic property (and thus `HashMap<String, SampleData>` is correct, but `SampleData`
absorbs the nullability.

This would be possible with this code:

```
enum ChargingData = {
   Null,
   Present {
       ...
   }
}
```

Which would remove the usage of https://docs.rs/swagger/2.0.2/swagger/nullable_format/enum.Nullable.html.

I haven't resolved this - and have instead done a more targeted fix.

This, along with some other crude code, creates a scenario where we need to
unpick whether something is null. I've left that, though flagged a TODO to tidy it up at some point.
2024-09-18 15:25:06 +08:00
Richard Whitehouse
f07f8bc997 [Rust Server] Add derive of Hash to enums (#19603)
* [Rust Server] Add derive of Hash to enums

* Update samples
2024-09-18 15:23:27 +08:00
Richard Whitehouse
cf5d17bbfe [Rust Server] Convert Rust comment to Mustache (#19595)
* [Rust Server] Convert Rust comment to Mustache

The comment about auth types supported by the generator shouldn't be
included in the generated code as it's confusing when the API doesn't
support the same auth types.

As such, we convert it from a Rust comment to a Mustache comment

* Update samples
2024-09-18 15:22:51 +08:00
Joscha Feth
0b084cd75d [typescript] fixture: broken Array<Array> (#19548)
* [typescript] fixture: broken `Array<Array>`

* Delete samples/client/others/typescript/builds/array-of-lists/models/L.ts

* fix: remove incorrect mapping

* chore: update generated code samples
2024-09-17 15:04:04 +02:00
Rasmus Zweidorff Iversen
fd85359548 [Java] [apache-httpclient] Fix issue with toString causing wrong value with collections (#19576)
* Fix issue with toString causing wrong value with collections

With collections, calling toString will result in the value becoming [a,b] instead of just a,b.

Using the existing apiClient::parameterToString method ensures it gets parsed correctly if it is a collection

* Updated samples

---------

Co-authored-by: Rasmus Zweidorff Iversen <rzi@jysk.com>
2024-09-17 17:57:35 +08:00
dependabot[bot]
30b1a74979 Bump actions/setup-go from 3 to 5 (#19597)
Bumps [actions/setup-go](https://github.com/actions/setup-go) from 3 to 5.
- [Release notes](https://github.com/actions/setup-go/releases)
- [Commits](https://github.com/actions/setup-go/compare/v3...v5)

---
updated-dependencies:
- dependency-name: actions/setup-go
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-17 10:42:21 +08:00
rankoz
ff1fe256d8 Fix dictionary/map deserialization (#19496) 2024-09-16 23:18:04 +08:00
Csaba Kozák
1b30c1995f [kotlin-client][multiplatform] add support for kotlinx.datetime.LocalTime (#19590) 2024-09-16 16:10:32 +01:00
Julian Vennen
3832cb4eb7 [PHP] Update enum tests (#19591) 2024-09-16 19:48:58 +08:00
Eric
425aa7db44 [Kotlin] Add a new additional property to configure Jackson's failOnUnknownProperties (#19506)
* [Kotlin] Add a new additional property to configure Jackson's `failOnUnknownProperties`

Default to false

* [Kotlin] Unconditionally import `com.fasterxml.jackson.databind.DeserializationFeature`

* [Kotlin] Refactor and add test
2024-09-16 17:59:17 +08:00
Julian Vennen
0c5142a6f6 [php-symfony] Fix #19562 (#19568) 2024-09-16 17:41:49 +08:00
Julian Vennen
a5384d42b4 [PHP] Remove NUMBER_ prefix from enum vars if a name is provided, show enum descriptions (#19555)
* Remove NUMBER_ prefix from enum vars if a name is provided, show enum descriptions

* Update php model tests
2024-09-16 17:23:11 +08:00
Eric Rolli
bbafeaed40 [Java Jersey] Update ApiClient.mustache Jersey doesn't allow entities in method DELETE (#19530)
* Update ApiClient.mustache

* Update ApiClient.mustache

Jersey doesn't allow request entities in method DELETE

* Update ApiClient.java

Jersey doesn't allow entities in method DELETE

* Update ApiClient.java

Jersey doesn't allow entities in method DELETE

* Update ApiClient.java

Jersey doesn't allow entities in method DELETE

* Update ApiClient.java

Jersey doesn't allow entities in method DELETE

* Update ApiClient.java

Jersey doesn't allow entities in method DELETE

* Update ApiClient.java

Jersey doesn't allow entities in method DELETE

* Update ApiClient.java

Jersey doesn't allow entities in method DELETE

* Update ApiClient.java

Jersey doesn't allow entities in method DELETE

* Update ApiClient.java

Jersey doesn't allow entities in method DELETE

* Update ApiClient.java

Jersey doesn't allow entities in method DELETE

* jersey ApiClient.mustache invoke DELETE without entity if empty

* jersey ApiClient.mustache invoke DELETE without entity if empty

* jersey ApiClient invoke DELETE without entity if empty

* jersey ApiClient invoke DELETE without entity if empty

* jersey ApiClient invoke DELETE without entity if empty

* jersey ApiClient invoke DELETE without entity if empty

* jersey ApiClient invoke DELETE without entity if empty

* jersey ApiClient invoke DELETE without entity if empty

* jersey ApiClient invoke DELETE without entity if empty

* jersey ApiClient invoke DELETE without entity if empty

* jersey ApiClient invoke DELETE without entity if empty

* jersey ApiClient invoke DELETE without entity if empty
2024-09-16 17:03:50 +08:00
Beppe Catanese
cf9de0bf94 Correct package name in test (#19581) 2024-09-16 16:52:19 +08:00
Beppe Catanese
243f501aef [GO] Go Server: preserve order of the routes as defined in the OpenAPI file (#19550)
* Add skipSortingOperations configuration option

* Skip sorting operations for go-server

* Add test verifyOrder

* Regenerate samples
2024-09-16 16:52:01 +08:00
Beppe Catanese
2f179fe41d [Go] Verify content of Go server generated files (samples) (#19504)
* Add Python tests to verify generates Go files

* Run Python tests during CI

* Remove Python tests

* Add Go tests

* Update workflow to run Go tests
2024-09-16 16:48:30 +08:00
Vasiliy Ditsyak
7dcaececf8 [BUGFIX][dart-dio] add unknownEnumValue to JsonKey (#19416)
* [BUGFIX][dart-dio] add unknownEnumValue to JsonKey

* review fix

---------

Co-authored-by: Vasiliy Ditsyak <vasilich6107@users.noreply.github.com>
2024-09-15 16:35:24 +08:00
Vasiliy Ditsyak
abf94168e6 [BUGFIX][dart-dio] Align enum and enum_inline generation result (#19510)
* [BUGFIX][dart-dio] Align `enum` and `enum_inline` generation result

* review fixes

* Revert "review fixes"

This reverts commit 9d0b263f5c.

---------

Co-authored-by: Vasiliy Ditsyak <vasilich6107@users.noreply.github.com>
2024-09-15 13:22:09 +08:00
Chirag Jain
740b971074 python-pydantic-v1: Return the primitive type in to_dict for anyOf models (#19488)
* python: Return the primitive type in to_dict for anyOf models

* Regenerate samples

* Update test
2024-09-13 18:50:55 +08:00
Joscha Feth
cd349dc5ea [avro-schema] fix enum logic (sanitize) (#19549) 2024-09-12 17:00:19 +08:00
Joscha Feth
5c6b8f3b16 docs: add hint about how to attach VSCode to the suspended Java process (#19551) 2024-09-12 16:59:57 +08:00
Nelson Vides
d02aae7469 Erlang Server – cowboy return types improvements (#19561)
* Declare the correct types for cowboy rest requests

* Adding myself to the erlang team
2024-09-12 16:59:06 +08:00
Hidetoshi
ca378b424a [typescript-axios] fix: enum datatype jsdoc (#19570) (#19571)
* fix: enum dataType JSDoc

* chore: generate samples
2024-09-12 12:59:40 +08:00
Lili Shi
0e763b096e [swift5] Fix Xcode 16 compilation crash with Extensions.swift generation (#19564)
* Update Extensions template for Swift5 generation

* update samples
2024-09-11 08:59:23 +01:00
Julian Vennen
1658264261 Regenerate php-nextgen-sample (#19556)
* Regenerate php-nextgen-sample

* Add missing changes
2024-09-10 14:32:30 +08:00
Rory Schadler
8171648eb4 fix(python,asyncio): multipart form data serialization (#19302)
* fix: object serialization for multipart requests

This PR is essentially
<https://github.com/OpenAPITools/openapi-generator/pull/18140> but for
the asyncio client.

* fix: int serialization for multipart requests

urllib3 handles serializing ints in post params (ref 1), while asyncio
explicitly does not (ref 2).

ref 1: <9316764e90/src/urllib3/filepost.py (L75-L76)>
ref 2: <https://github.com/aio-libs/aiohttp/issues/920>

* test: new fake multipart endpoint with files and body

* test: regression test for stringified body params

* fix: mypy tweak

* fix: FILES regeneration

* feat: object, int serialization for multipart reqs

Extends previous commits (and #18140) to cover the python-pydantic-v1
client as well.

* fix: use async with in test

* test: regression test for pydantic-v1-aiohttp

* test: add regression test to pydantic-v1

Also brings the second test in line with the first, patching
`urllib3.PoolManager.urlopen`
2024-09-09 17:43:25 +08:00
Evgeny Shichenko
0026e15030 fix kotlin spring boot configuration conflict (#19515) 2024-09-09 11:03:18 +08:00
William Cheng
8511a533d6 add erlang-server-deprecated for fallback after refactoring (#19547) 2024-09-07 17:18:06 +08:00
Nelson Vides
596d446f54 Erlang server overhaul (#19465)
* Upgrade erlang-server code generation and fix is_authorized crashes

* Introduce structured logging

* Improve general formatting

* Update generated files

* Enable erlang server on CI

* Add echo-server testing to CI

* Require OTP27 explicitly in the generated rebar.config file

* Rework handler and API

With this work, json validation becomes optional, fully implemented in
the `_api` module as it was before, but without being forcibly called by
the `_handler`. It is instead left as optional for the user to take
advantage of the exposed callbacks. Jesse also chooses draft-06 as a
default, but these can be chosen manually by the user too, as long as
jesse implements them.

`_handler` also becomes lighter, it now handles all mime types
transparently by forwarding to a user-given module that must implement
`accept_callback/4` and `provide_callback/4` as described in the
`_logic_handler` callbacks. These will simply be the return values of
cowboy_rest's `content_types_accepted` and `content_types_provided`
respectively, and should simply comply with their defined APIs. They
only get two parameters extending the behaviour, so that the user-given
callback can pattern-match on them: the path prefix of the logic
handler, and the operationID of the call.

* Fix return types for provide_callbacks

* Upgrade jesse to incur no dependencies

The less dependencies the built code requires the better.

* Fix dialyzer errors in the generated code

* Apply stronger dialyzer checks
2024-09-07 16:45:42 +08:00
Kresten P. Vester
a98f45b4ac [Java][Spring]Update dependencies to remove vulnerability in org.springframework:spring-webmvc (#19533)
* Update dependencies to remove vulnerability in org.springframework:spring-webmvc

* Updated samples
2024-09-07 16:26:42 +08:00
Joscha Feth
14e691b9c9 docs: link to existing documentation for snapshot builds and locally built JARs (#19544)
Follow-up from https://github.com/OpenAPITools/openapi-generator/pull/19497 since https://github.com/OpenAPITools/openapi-generator-cli/pull/794 has been merged.
2024-09-07 16:12:24 +08:00
devhl-labs
195438d1e7 version bump (#19545) 2024-09-07 16:10:47 +08:00
William Cheng
a93a6d5a35 Add dotcom-monitor to the sponsor list (#19546)
* add dm to the sponsor list

* update
2024-09-07 16:06:21 +08:00
828 changed files with 16743 additions and 45794 deletions

View File

@@ -17,5 +17,5 @@
These must match the expectations made by your contribution.
You may regenerate an individual generator by passing the relevant config(s) as an argument to the script, for example `./bin/generate-samples.sh bin/configs/java*`.
IMPORTANT: Do **NOT** purge/delete any folders/files (e.g. tests) when regenerating the samples as manually written tests may be removed.
- [ ] File the PR against the [correct branch](https://github.com/OpenAPITools/openapi-generator/wiki/Git-Branches): `master` (upcoming 7.6.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.x.0` minor release - breaking changes with fallbacks), `8.0.x` (breaking changes without fallbacks)
- [ ] If your PR is targeting a particular programming language, @mention the [technical committee](https://github.com/openapitools/openapi-generator/#62---openapi-generator-technical-committee) members, so they are more likely to review the pull request.

View File

@@ -3,33 +3,33 @@ name: Samples Erlang
on:
push:
paths:
# 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/server/echo_api/erlang-server/**
- samples/server/petstore/erlang-server/**
- samples/client/petstore/erlang-client/**
- samples/client/petstore/erlang-proper/**
pull_request:
paths:
#- samples/server/petstore/erlang-server/**
- samples/server/echo_api/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-20.04
runs-on: ubuntu-24.04
strategy:
fail-fast: false
matrix:
sample:
#- samples/server/petstore/erlang-server/
- samples/server/echo_api/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: '22.2'
rebar3-version: '3.14.3'
otp-version: '27'
rebar3-version: '3.23.0'
- run: rebar3 compile
working-directory: ${{ matrix.sample }}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -72,6 +72,7 @@ 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
@@ -446,41 +447,7 @@ npm install @openapitools/openapi-generator-cli -D
```
<!-- /RELEASE_VERSION -->
#### 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)
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.
## [2 - Getting Started](#table-of-contents)
@@ -1107,7 +1074,7 @@ Here is a list of template creators:
* C++ (Qt5 QHttpEngine): @etherealjoy
* C++ Pistache: @sebymiano
* C++ Restbed: @stkrwork
* Erlang Server: @galaxie
* Erlang Server: @galaxie @nelsonvides
* F# (Giraffe) Server: @nmfisher
* Go Server: @guohuang
* Go Server (refactored in 7.0.0): @lwj5
@@ -1218,7 +1185,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) |
| Erlang | @tsloughter (2017/11) @jfacorro (2018/10) @robertoaloi (2018/10) @nelsonvides (2024/09) |
| F# | @nmfisher (2019/05) |
| Go | @antihax (2017/11) @grokify (2018/07) @kemokemo (2018/09) @jirikuncar (2021/01) @ph4r5h4d (2021/04) @lwj5 (2023/04) |
| GraphQL | @renepardon (2018/12) |

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1,190 @@
---
title: Documentation for the erlang-server-deprecated Generator
---
## METADATA
| Property | Value | Notes |
| -------- | ----- | ----- |
| generator name | erlang-server-deprecated | pass this to the generate command after -g |
| generator stability | DEPRECATED | |
| generator type | SERVER | |
| generator language | Erlang | |
| generator default templating engine | mustache | |
| helpTxt | Generates an Erlang server library (deprecated) using OpenAPI Generator (https://openapi-generator.tech). By default, it will also generate service classes, which can be disabled with the `-Dnoservice` environment variable. | |
## CONFIG OPTIONS
These options may be applied as additional-properties (cli) or configOptions (plugins). Refer to [configuration docs](https://openapi-generator.tech/docs/configuration) for more details.
| Option | Description | Values | Default |
| ------ | ----------- | ------ | ------- |
|openAPISpecName|Openapi Spec Name.| |openapi|
|packageName|Erlang package name (convention: lowercase).| |openapi|
## IMPORT MAPPING
| Type/Alias | Imports |
| ---------- | ------- |
## INSTANTIATION TYPES
| Type/Alias | Instantiated By |
| ---------- | --------------- |
## LANGUAGE PRIMITIVES
<ul class="column-ul">
</ul>
## RESERVED WORDS
<ul class="column-ul">
<li>after</li>
<li>and</li>
<li>andalso</li>
<li>band</li>
<li>begin</li>
<li>bnot</li>
<li>bor</li>
<li>bsl</li>
<li>bsr</li>
<li>bxor</li>
<li>case</li>
<li>catch</li>
<li>cond</li>
<li>div</li>
<li>end</li>
<li>fun</li>
<li>if</li>
<li>let</li>
<li>not</li>
<li>of</li>
<li>or</li>
<li>orelse</li>
<li>receive</li>
<li>rem</li>
<li>try</li>
<li>when</li>
<li>xor</li>
</ul>
## FEATURE SET
### Client Modification Feature
| Name | Supported | Defined By |
| ---- | --------- | ---------- |
|BasePath|✗|ToolingExtension
|Authorizations|✗|ToolingExtension
|UserAgent|✗|ToolingExtension
|MockServer|✗|ToolingExtension
### Data Type Feature
| Name | Supported | Defined By |
| ---- | --------- | ---------- |
|Custom|✗|OAS2,OAS3
|Int32|✓|OAS2,OAS3
|Int64|✓|OAS2,OAS3
|Float|✓|OAS2,OAS3
|Double|✓|OAS2,OAS3
|Decimal|✓|ToolingExtension
|String|✓|OAS2,OAS3
|Byte|✓|OAS2,OAS3
|Binary|✓|OAS2,OAS3
|Boolean|✓|OAS2,OAS3
|Date|✓|OAS2,OAS3
|DateTime|✓|OAS2,OAS3
|Password|✓|OAS2,OAS3
|File|✓|OAS2
|Uuid|✗|
|Array|✓|OAS2,OAS3
|Null|✗|OAS3
|AnyType|✗|OAS2,OAS3
|Object|✓|OAS2,OAS3
|Maps|✓|ToolingExtension
|CollectionFormat|✓|OAS2
|CollectionFormatMulti|✓|OAS2
|Enum|✓|OAS2,OAS3
|ArrayOfEnum|✓|ToolingExtension
|ArrayOfModel|✓|ToolingExtension
|ArrayOfCollectionOfPrimitives|✓|ToolingExtension
|ArrayOfCollectionOfModel|✓|ToolingExtension
|ArrayOfCollectionOfEnum|✓|ToolingExtension
|MapOfEnum|✓|ToolingExtension
|MapOfModel|✓|ToolingExtension
|MapOfCollectionOfPrimitives|✓|ToolingExtension
|MapOfCollectionOfModel|✓|ToolingExtension
|MapOfCollectionOfEnum|✓|ToolingExtension
### Documentation Feature
| Name | Supported | Defined By |
| ---- | --------- | ---------- |
|Readme|✓|ToolingExtension
|Model|✓|ToolingExtension
|Api|✓|ToolingExtension
### Global Feature
| Name | Supported | Defined By |
| ---- | --------- | ---------- |
|Host|✓|OAS2,OAS3
|BasePath|✓|OAS2,OAS3
|Info|✓|OAS2,OAS3
|Schemes|✗|OAS2,OAS3
|PartialSchemes|✓|OAS2,OAS3
|Consumes|✓|OAS2
|Produces|✓|OAS2
|ExternalDocumentation|✓|OAS2,OAS3
|Examples|✓|OAS2,OAS3
|XMLStructureDefinitions|✗|OAS2,OAS3
|MultiServer|✗|OAS3
|ParameterizedServer|✗|OAS3
|ParameterStyling|✗|OAS3
|Callbacks|✗|OAS3
|LinkObjects|✗|OAS3
### Parameter Feature
| Name | Supported | Defined By |
| ---- | --------- | ---------- |
|Path|✓|OAS2,OAS3
|Query|✓|OAS2,OAS3
|Header|✓|OAS2,OAS3
|Body|✓|OAS2
|FormUnencoded|✓|OAS2
|FormMultipart|✓|OAS2
|Cookie|✗|OAS3
### Schema Support Feature
| Name | Supported | Defined By |
| ---- | --------- | ---------- |
|Simple|✓|OAS2,OAS3
|Composite|✓|OAS2,OAS3
|Polymorphism|✗|OAS2,OAS3
|Union|✗|OAS3
|allOf|✗|OAS2,OAS3
|anyOf|✗|OAS3
|oneOf|✗|OAS3
|not|✗|OAS3
### Security Feature
| Name | Supported | Defined By |
| ---- | --------- | ---------- |
|BasicAuth|✗|OAS2,OAS3
|ApiKey|✓|OAS2,OAS3
|OpenIDConnect|✗|OAS3
|BearerToken|✗|OAS3
|OAuth2_Implicit|✓|OAS2,OAS3
|OAuth2_Password|✗|OAS2,OAS3
|OAuth2_ClientCredentials|✗|OAS2,OAS3
|OAuth2_AuthorizationCode|✗|OAS2,OAS3
|SignatureAuth|✗|OAS3
|AWSV4Signature|✗|ToolingExtension
### Wire Format Feature
| Name | Supported | Defined By |
| ---- | --------- | ---------- |
|JSON|✓|OAS2,OAS3
|XML|✗|OAS2,OAS3
|PROTOBUF|✗|ToolingExtension
|Custom|✗|OAS2,OAS3

View File

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

View File

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

View File

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

View File

@@ -224,6 +224,8 @@ 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(;.*)?");
@@ -6154,6 +6156,16 @@ public class DefaultCodegen implements CodegenConfig {
this.skipOperationExample = skipOperationExample;
}
@Override
public boolean isSkipSortingOperations() {
return this.skipSortingOperations;
}
@Override
public void setSkipSortingOperations(boolean skipSortingOperations) {
this.skipSortingOperations = skipSortingOperations;
}
@Override
public boolean isHideGenerationTimestamp() {
return hideGenerationTimestamp;

View File

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

View File

@@ -20,6 +20,7 @@ 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;
@@ -283,10 +284,24 @@ public class OpenAPINormalizer {
this.openAPI.getComponents().setSchemas(new HashMap<String, Schema>());
}
normalizeInfo();
normalizePaths();
normalizeComponentsSchemas();
}
/**
* Pre-populate info if it's not defined.
*/
private void normalizeInfo() {
if (this.openAPI.getInfo() == null) {
Info info = new Info();
info.setTitle("OpenAPI");
info.setVersion("0.0.1");
info.setDescription("OpenAPI");
this.openAPI.setInfo(info);
}
}
/**
* Normalizes inline models in Paths
*/

View File

@@ -684,6 +684,28 @@ 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)) {
@@ -704,11 +726,11 @@ public abstract class AbstractPhpCodegen extends DefaultCodegen implements Codeg
return enumNameMapping.get(name);
}
if (name.length() == 0) {
if (name.isEmpty()) {
return "EMPTY";
}
if (name.trim().length() == 0) {
if (name.trim().isEmpty()) {
return "SPACE_" + name.length();
}
@@ -719,11 +741,12 @@ public abstract class AbstractPhpCodegen extends DefaultCodegen implements Codeg
// number
if ("int".equals(datatype) || "float".equals(datatype)) {
String varName = name;
varName = varName.replaceAll("-", "MINUS_");
varName = varName.replaceAll("\\+", "PLUS_");
varName = varName.replaceAll("\\.", "_DOT_");
return varName;
if (name.matches("\\d.*")) { // starts with number
name = "NUMBER_" + name;
}
name = name.replaceAll("-", "MINUS_");
name = name.replaceAll("\\+", "PLUS_");
name = name.replaceAll("\\.", "_DOT_");
}
// string

View File

@@ -25,15 +25,45 @@ 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();
@@ -88,6 +118,15 @@ 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
@@ -101,6 +140,16 @@ 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
@@ -148,4 +197,44 @@ public class AvroSchemaCodegen extends DefaultCodegen implements CodegenConfig {
return input;
}
@Override
protected List<Map<String, Object>> buildEnumVars(List<Object> values, String dataType) {
List<Object> sanitizedValues = values.stream().map(Object::toString).map(this::sanitizeEnumValue)
.collect(Collectors.toList());
removeEnumValueCollisions(sanitizedValues);
return super.buildEnumVars(sanitizedValues, dataType);
}
/**
* Valid enums in Avro need to adhere to [A-Za-z_][A-Za-z0-9_]*
* See https://avro.apache.org/docs/1.12.0/specification/#enums
*/
private String sanitizeEnumValue(String value) {
// Replace any non-alphanumeric characters with an underscore
String sanitizedValue = value.replaceAll("[^A-Za-z0-9_]", "_");
// If the enum starts with a number, prefix it with an underscore
sanitizedValue = sanitizedValue.replaceAll("^([0-9])", "_$1");
return sanitizedValue;
}
private void removeEnumValueCollisions(List<Object> values) {
Collections.reverse(values);
for (int i = 0; i < values.size(); i++) {
final String value = values.get(i).toString();
long count = values.stream().filter(v1 -> v1.equals(value)).count();
if (count > 1) {
String uniqueEnumValue = getUniqueEnumValue(value.toString(), values);
LOGGER.debug("Changing duplicate enumeration value from " + value + " to " + uniqueEnumValue);
values.set(i, uniqueEnumValue);
}
}
Collections.reverse(values);
}
private String getUniqueEnumValue(String value, List<Object> values) {
long count = values.stream().filter(v -> v.equals(value)).count();
return count > 1
? getUniqueEnumValue(value + count, values)
: value;
}
}

View File

@@ -99,9 +99,9 @@ public class ErlangServerCodegen extends DefaultCodegen implements CodegenConfig
*/
setReservedWordsLowerCase(
Arrays.asList(
"after", "and", "andalso", "band", "begin", "bnot", "bor", "bsl", "bsr", "bxor", "case",
"catch", "cond", "div", "end", "fun", "if", "let", "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", "maybe", "not",
"of", "or", "orelse", "receive", "rem", "try", "when", "xor"
)
);
@@ -172,10 +172,8 @@ 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());
@@ -223,7 +221,7 @@ public class ErlangServerCodegen extends DefaultCodegen implements CodegenConfig
@Override
public String toApiName(String name) {
if (name.length() == 0) {
return this.packageName + "_default_handler";
return this.packageName + "_handler";
}
return this.packageName + "_" + underscore(name) + "_handler";
}

View File

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

View File

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

View File

@@ -85,6 +85,7 @@ 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";
@@ -108,6 +109,7 @@ 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;
@@ -259,6 +261,7 @@ 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"));
@@ -438,6 +441,13 @@ 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()) {
@@ -583,12 +593,15 @@ 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) {
@@ -654,7 +667,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"));
addKotlinxDateTimeInstantAdapter(infrastructureFolder);
addKotlinxDateTimeAdapters(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"));
@@ -665,7 +678,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"));
addKotlinxDateTimeInstantAdapter(infrastructureFolder);
addKotlinxDateTimeAdapters(infrastructureFolder);
break;
case jackson:
@@ -689,9 +702,10 @@ public class KotlinClientCodegen extends AbstractKotlinCodegen {
}
}
private void addKotlinxDateTimeInstantAdapter(final String infrastructureFolder) {
private void addKotlinxDateTimeAdapters(final String infrastructureFolder) {
if (DateLibrary.KOTLINX_DATETIME.value.equals(dateLibrary)) {
supportingFiles.add(new SupportingFile("jvm-common/infrastructure/InstantAdapter.kt.mustache", infrastructureFolder, "InstantAdapter.kt"));
supportingFiles.add(new SupportingFile("jvm-common/infrastructure/LocalTimeAdapter.kt.mustache", infrastructureFolder, "LocalTimeAdapter.kt"));
}
}

View File

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

View File

@@ -1063,39 +1063,48 @@ 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);
return typeMapping.get("array") + "<" + innerType + ">";
type = 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(">");
return typeDeclaration.toString();
type = typeDeclaration.toString();
} else if (!StringUtils.isEmpty(p.get$ref())) {
String dataType;
try {
dataType = modelFromSchema(p);
type = modelFromSchema(p);
if (dataType != null) {
dataType = "models::" + dataType;
LOGGER.debug("Returning " + dataType + " from ref");
if (type != null) {
type = "models::" + type;
LOGGER.debug("Returning " + type + " from ref");
}
} catch (Exception e) {
dataType = null;
type = 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) {
return typeMapping.get("File").toString();
type = typeMapping.get("File").toString();
} else {
type = super.getTypeDeclaration(p);
}
return super.getTypeDeclaration(p);
// 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;
}
@Override
@@ -1400,6 +1409,19 @@ 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
@@ -1409,7 +1431,9 @@ public class RustServerCodegen extends AbstractRustCodegen implements CodegenCon
@Override
public void postProcessModelProperty(CodegenModel model, CodegenProperty property) {
super.postProcessModelProperty(model, property);
if (!languageSpecificPrimitives.contains(property.dataType)) {
// TODO: We should avoid reverse engineering primitive type status from the data type
if (!languageSpecificPrimitives.contains(stripNullable(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(":");
@@ -1417,7 +1441,7 @@ public class RustServerCodegen extends AbstractRustCodegen implements CodegenCon
} else {
property.dataType = camelize(property.dataType);
}
property.isPrimitiveType = property.isContainer && languageSpecificPrimitives.contains(typeMapping.get(property.complexType));
property.isPrimitiveType = property.isContainer && languageSpecificPrimitives.contains(typeMapping.get(stripNullable(property.complexType)));
} else {
property.isPrimitiveType = true;
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -39,10 +39,10 @@
<PackageReference Include="Microsoft.Extensions.Http" Version="{{#lambda.first}}{{#netStandard}}5.0.0 {{/netStandard}}{{#net47}}7.0.0 {{/net47}}{{#net48}}7.0.0 {{/net48}}{{#net6.0}}6.0.0 {{/net6.0}}{{#net7.0}}7.0.0 {{/net7.0}}{{#net8.0}}8.0.0 {{/net8.0}}{{/lambda.first}}" />
<PackageReference Include="Microsoft.Extensions.Hosting" Version="{{#lambda.first}}{{#netStandard}}5.0.0 {{/netStandard}}{{#net47}}7.0.0 {{/net47}}{{#net48}}7.0.0 {{/net48}}{{#net6.0}}6.0.1 {{/net6.0}}{{#net7.0}}7.0.1 {{/net7.0}}{{#net8.0}}8.0.0 {{/net8.0}}{{/lambda.first}}" />
{{#supportsRetry}}
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="{{#lambda.first}}{{#netStandard}}5.0.1 {{/netStandard}}{{#net47}}7.0.0 {{/net47}}{{#net48}}7.0.0 {{/net48}}{{#net6.0}}6.0.19 {{/net6.0}}{{#net7.0}}7.0.11 {{/net7.0}}{{#net8.0}}8.0.3 {{/net8.0}}{{/lambda.first}}" />
<PackageReference Include="Microsoft.Extensions.Http.Polly" Version="{{#lambda.first}}{{#netStandard}}5.0.1 {{/netStandard}}{{#net47}}7.0.0 {{/net47}}{{#net48}}7.0.0 {{/net48}}{{#net6.0}}6.0.19 {{/net6.0}}{{#net7.0}}7.0.11 {{/net7.0}}{{#net8.0}}8.0.8 {{/net8.0}}{{/lambda.first}}" />
{{/supportsRetry}}
{{#net80OrLater}}
<PackageReference Include="Microsoft.Net.Http.Headers" Version="8.0.3" />
<PackageReference Include="Microsoft.Net.Http.Headers" Version="8.0.8" />
{{/net80OrLater}}
{{^net60OrLater}}
<PackageReference Include="System.Threading.Channels" Version="8.0.0" />

View File

@@ -9,9 +9,9 @@
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="{{^netStandard}}17.9.0{{/netStandard}}{{#netStandard}}15.9.2{{/netStandard}}" />
<PackageReference Include="xunit" Version="{{^netStandard}}2.7.0{{/netStandard}}{{#netStandard}}2.4.2{{/netStandard}}" />
<PackageReference Include="xunit.runner.visualstudio" Version="{{^netStandard}}2.5.7{{/netStandard}}{{#netStandard}}2.4.5{{/netStandard}}" />
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="{{^netStandard}}17.11.1{{/netStandard}}{{#netStandard}}15.9.2{{/netStandard}}" />
<PackageReference Include="xunit" Version="{{^netStandard}}2.9.0{{/netStandard}}{{#netStandard}}2.4.2{{/netStandard}}" />
<PackageReference Include="xunit.runner.visualstudio" Version="{{^netStandard}}2.8.2{{/netStandard}}{{#netStandard}}2.4.5{{/netStandard}}" />
</ItemGroup>
<ItemGroup>

View File

@@ -46,7 +46,12 @@ 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}}
includeIfNull: {{#required}}{{#isNullable}}true{{/isNullable}}{{^isNullable}}false{{/isNullable}}{{/required}}{{^required}}false{{/required}},
{{#isEnumOrRef}}
{{#enumUnknownDefaultCase}}
unknownEnumValue: {{{datatypeWithEnum}}}.unknownDefaultOpenApi,
{{/enumUnknownDefaultCase}}
{{/isEnumOrRef}}
)
{{/isBinary}}
{{#isBinary}}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -0,0 +1 @@
{{{openapi-json}}}

View File

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

View File

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

View File

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

View File

@@ -4,54 +4,33 @@
An Erlang server stub generated by [OpenAPI Generator](https://openapi-generator.tech) given an OpenAPI spec.
Dependency: [Cowboy](https://github.com/ninenines/cowboy)
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)
## Prerequisites
TODO
## Getting started
Use erlang-server with erlang.mk
Use erlang-server with rebar3
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
1, Create an application by using rebar3
$ rebar3 new app http_server
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
2, 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.
3, Copy erlang-server file to http_server project, and don't forget the 'priv' folder.
5, Start in the http_server project:
4, 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
openapi_server:start(http_server, #{ip => {127,0,0,1}, port => 8080})
2, Compile your http_server project
$ rebar3 compile
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
$ rebar3 shell
4, Start project
application:ensure_all_started(http_server).
To implement your own business logic, create a module called `http_server_logic` that implements the
behaviour `openapi_logic_handler`. Refer to `openapi_logic_handler` documentation for details.

View File

@@ -1,37 +1,60 @@
-module({{packageName}}_api).
-moduledoc """
This module offers an API for JSON schema validation, using `jesse` under the hood.
-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]).
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]).
-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).
-dialyzer({nowarn_function, [to_binary/1, to_list/1, validate_response_body/4]}).
-type rule() ::
{type, 'binary'} |
{type, 'integer'} |
{type, 'float'} |
{type, 'binary'} |
{type, 'boolean'} |
{type, 'date'} |
{type, 'datetime'} |
{type, binary} |
{type, integer} |
{type, float} |
{type, boolean} |
{type, date} |
{type, datetime} |
{enum, [atom()]} |
{max, Max :: number()} |
{exclusive_max, Max :: number()} |
@@ -44,59 +67,99 @@ request_params(_) ->
required |
not_required.
-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() -> jesse_state:state().
prepare_validator() ->
prepare_validator(<<"http://json-schema.org/draft-06/schema#">>).
{{#apiInfo}}{{#apis}}
{{#operations}}{{#operation}}{{#allParams}}
request_param_info('{{operationId}}', {{^isBodyParam}}'{{baseName}}'{{/isBodyParam}}{{#isBodyParam}}'{{dataType}}'{{/isBodyParam}}) ->
-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}}) ->
#{
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} ->
@@ -118,24 +181,9 @@ populate_request_param(OperationID, Name, Req0, ValidatorState) ->
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}}
-include_lib("kernel/include/logger.hrl").
validate_response(_OperationID, _Code, _Body, _ValidatorState) ->
ok.
validate_response_body('list', ReturnBaseType, Body, ValidatorState) ->
validate_response_body(list, ReturnBaseType, Body, ValidatorState) ->
[
validate(schema, ReturnBaseType, Item, ValidatorState)
|| Item <- Body];
@@ -143,45 +191,37 @@ 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, {{packageName}}_utils:to_int(Value)}
{ok, 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, {{packageName}}_utils:to_float(Value)}
{ok, 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
@@ -192,19 +232,16 @@ 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),
@@ -216,52 +253,44 @@ 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/" ++ {{packageName}}_utils:to_list(Name)),
Definition = list_to_binary("#/components/schemas/" ++ to_list(Name)),
try
_ = validate_with_schema(Value, Definition, ValidatorState),
ok
@@ -281,18 +310,15 @@ validate(Rule = schema, Name, Value, ValidatorState) ->
},
validation_error(Rule, Name, Info)
end;
validate(Rule, Name, _Value, _ValidatorState) ->
error_logger:info_msg("Can't validate ~p with ~p", [Name, Rule]),
?LOG_INFO(#{what => "Cannot validate rule", name => Name, rule => 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}).
@@ -307,31 +333,26 @@ get_value(body, _Name, Req0) ->
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 = get_opt(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 = maps:get(to_header(Name), Headers, undefined),
{Value, Req};
get_value(binding, Name, Req) ->
Value = cowboy_req:binding({{packageName}}_utils:to_binding(Name), Req),
Value = cowboy_req:binding(to_binding(Name), Req),
{Value, Req}.
prepare_body(<<>>) ->
<<>>;
prepare_body(Body) ->
case Body of
<<"">> -> <<"">>;
_ ->
try
jsx:decode(Body, [return_maps])
catch
error:_ ->
{error, {invalid_body, not_json, Body}}
end
try
json:decode(Body)
catch
error:_ ->
{error, {invalid_body, not_json, Body}}
end.
validate_with_schema(Body, Definition, ValidatorState) ->
@@ -359,5 +380,84 @@ 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) ->
list_to_binary(string:to_lower({{packageName}}_utils:to_list(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.

View File

@@ -1,19 +1,11 @@
{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}}]}
]}.
{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}}]}]}.

View File

@@ -2,51 +2,44 @@
-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) ->
-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) ->
{ApiKey, Req} = get_api_key(From, KeyParam, Req0),
case ApiKey of
undefined ->
AuthHeader = <<"">>,
AuthHeader = <<>>,
{false, AuthHeader, Req};
_ ->
Result = {{packageName}}_logic_handler:authorize_api_key(
LogicHandler,
OperationID,
ApiKey
),
case Result of
{{#authMethods}}
{{#isApiKey}}
{true, Context} ->
case Handler(OperationID, ApiKey) of
{true, Context} ->
{true, Context, Req};
{{/isApiKey}}
{{/authMethods}}
false ->
AuthHeader = <<"">>,
{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
};
{maps:get(KeyParam, Headers, undefined), Req};
get_api_key(qs_val, KeyParam, Req) ->
QS = cowboy_req:parse_qs(Req),
{ {{packageName}}_utils:get_opt(KeyParam, 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.

View File

@@ -1,252 +1,129 @@
%% basic handler
-module({{classname}}).
-behaviour(cowboy_rest).
-include_lib("kernel/include/logger.hrl").
%% Cowboy REST callbacks
-export([allowed_methods/2]).
-export([init/2]).
-export([allow_missing_post/2]).
-export([allowed_methods/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]).
-export([handle_type_accepted/2, handle_type_provided/2]).
%% Handlers
-export([handle_request_json/2]).
-ignore_xref([handle_type_accepted/2, handle_type_provided/2]).
-record(state, {
operation_id :: {{packageName}}_api:operation_id(),
logic_handler :: atom(),
validator_state :: jesse_state:state(),
context=#{} :: #{}
}).
-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()}).
-type state() :: state().
-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}) ->
-spec init(cowboy_req:req(), {{packageName}}_router:init_opts()) ->
{cowboy_rest, cowboy_req:req(), state()}.
init(Req, {Operations, Module}) ->
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
},
?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},
{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}}'
}
) ->
-spec allowed_methods(cowboy_req:req(), state()) ->
{[binary()], cowboy_req:req(), state()}.
{{#operations}}{{#operation}}allowed_methods(Req, #state{operation_id = '{{operationId}}'} = State) ->
{[<<"{{httpMethod}}">>], Req, State};
{{/operation}}{{/operations}}
allowed_methods(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()
}.
-spec is_authorized(cowboy_req:req(), state()) ->
{true | {false, iodata()}, cowboy_req:req(), 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}
{{#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}
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}}
{{/authMethods.size}}
{{/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) ->
-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}}
{[
{<<"application/json">>, handle_request_json}
], 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}.
-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) ->
-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) ->
{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) ->
-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}}
{[
{<<"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().
{{#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}.
-spec delete_resource(cowboy_req:req(), state()) ->
{boolean(), cowboy_req:req(), state()}.
delete_resource(Req, State) ->
handle_request_json(Req, State).
{Res, Req1, State} = handle_type_accepted(Req, State),
{true =:= Res, Req1, State}.
-spec known_content_type(Req :: cowboy_req:req(), State :: state()) ->
{Value :: true, Req :: cowboy_req:req(), State :: 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}}.
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).
-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}}.

View File

@@ -1,60 +1,63 @@
-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()}.
-include_lib("kernel/include/logger.hrl").
-export_type([handler_response/0]).
-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() :: #{_ := _}.
{{#authMethods}}
{{#isApiKey}}
-callback authorize_api_key(
OperationID :: {{packageName}}_api:operation_id(),
ApiKey :: binary()
) ->
Result :: boolean() | {boolean(), context()}.
{{/isApiKey}}
{{/authMethods}}
-export_type([context/0, api_key_callback/0,
accept_callback_return/0, accept_callback/0, provide_callback/0]).
-optional_callbacks([api_key_callback/2]).
-callback handle_request(OperationID :: {{packageName}}_api:operation_id(), cowboy_req:req(), Context :: context()) ->
handler_response().
-callback api_key_callback({{packageName}}_api:operation_id(), binary()) ->
{true, context()} | {false, iodata()}.
-spec handle_request(
Handler :: atom(),
OperationID :: {{packageName}}_api:operation_id(),
Request :: 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()}.
handle_request(Handler, OperationID, Req, Context) ->
Handler:handle_request(OperationID, Req, Context).
-callback provide_callback(atom(), {{packageName}}_api:operation_id(), cowboy_req:req(), context()) ->
{cowboy_req:resp_body(), cowboy_req: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}}
-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}.

View File

@@ -1,6 +1,15 @@
{minimum_otp_vsn, "27"}.
{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"}}}
{cowboy, "2.12.0"},
{ranch, "2.1.0"},
{jesse, "1.8.1"}
]}.
{dialyzer,
[{plt_extra_apps, [cowboy, cowlib, ranch, jesse]},
{warnings, [missing_return, unknown]}
]}.
{xref_checks,
[undefined_function_calls, deprecated_function_calls, deprecated_functions]}.

View File

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

View File

@@ -1,26 +1,21 @@
-module({{packageName}}_server).
-define(DEFAULT_LOGIC_HANDLER, {{packageName}}_default_logic_handler).
-define(DEFAULT_LOGIC_HANDLER, {{packageName}}_logic_handler).
-export([start/2]).
-ignore_xref([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),
-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, #{}),
LogicHandler = maps:get(logic_handler, Params, ?DEFAULT_LOGIC_HANDLER),
ExtraOpts = maps:get(cowboy_extra_opts, Params, []),
CowboyOpts = get_cowboy_config(LogicHandler, ExtraOpts),
CowboyOpts = get_cowboy_config(LogicHandler, ProtocolOpts),
case Transport of
ssl ->
cowboy:start_tls(ID, TransportOpts, CowboyOpts);
@@ -28,33 +23,17 @@ start(ID, #{
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)).
DefaultOpts = get_default_opts(LogicHandler),
maps:fold(fun get_cowboy_config/3, DefaultOpts, ExtraOpts).
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_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_default_dispatch(LogicHandler) ->
Paths = {{packageName}}_router:get_paths(LogicHandler),
@@ -62,6 +41,3 @@ get_default_dispatch(LogicHandler) ->
get_default_opts(LogicHandler) ->
#{env => get_default_dispatch(LogicHandler)}.
store_key(Key, Value, Opts) ->
maps:put(Key, Value, Opts).

View File

@@ -21,36 +21,51 @@ 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, DateTimeFormatter.ISO_LOCAL_DATE)
return LocalDate.parse(value{{^kotlinx-datetime}}, DateTimeFormatter.ISO_LOCAL_DATE{{/kotlinx-datetime}})
}
}
{{/moshi}}
{{#gson}}
{{#nonPublicApi}}internal {{/nonPublicApi}}class LocalDateAdapter(private val formatter: DateTimeFormatter = DateTimeFormatter.ISO_LOCAL_DATE) : TypeAdapter<LocalDate>() {
{{#nonPublicApi}}internal {{/nonPublicApi}}class LocalDateAdapter({{^kotlinx-datetime}}private val formatter: DateTimeFormatter = DateTimeFormatter.ISO_LOCAL_DATE{{/kotlinx-datetime}}) : 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}}
}
}
@@ -64,13 +79,14 @@ import org.threeten.bp.format.DateTimeFormatter
return null
}
else -> {
return LocalDate.parse(out.nextString(), formatter)
return LocalDate.parse(out.nextString(){{^kotlinx-datetime}}, formatter{{/kotlinx-datetime}})
}
}
}
}
{{/gson}}
{{#kotlinx_serialization}}
{{^kotlinx-datetime}}
@Serializer(forClass = LocalDate::class)
{{#nonPublicApi}}internal {{/nonPublicApi}}object LocalDateAdapter : KSerializer<LocalDate> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LocalDate", PrimitiveKind.STRING)
@@ -83,4 +99,5 @@ import org.threeten.bp.format.DateTimeFormatter
return LocalDate.parse(decoder.decodeString(), DateTimeFormatter.ISO_LOCAL_DATE)
}
}
{{/kotlinx-datetime}}
{{/kotlinx_serialization}}

View File

@@ -21,36 +21,51 @@ 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, DateTimeFormatter.ISO_LOCAL_DATE_TIME)
return LocalDateTime.parse(value{{^kotlinx-datetime}}, DateTimeFormatter.ISO_LOCAL_DATE_TIME{{/kotlinx-datetime}})
}
}
{{/moshi}}
{{#gson}}
{{#nonPublicApi}}internal {{/nonPublicApi}}class LocalDateTimeAdapter(private val formatter: DateTimeFormatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME) : TypeAdapter<LocalDateTime>() {
{{#nonPublicApi}}internal {{/nonPublicApi}}class LocalDateTimeAdapter({{^kotlinx-datetime}}private val formatter: DateTimeFormatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME{{/kotlinx-datetime}}) : 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}}
}
}
@@ -64,13 +79,14 @@ import org.threeten.bp.format.DateTimeFormatter
return null
}
else -> {
return LocalDateTime.parse(out.nextString(), formatter)
return LocalDateTime.parse(out.nextString(){{^kotlinx-datetime}}, formatter{{/kotlinx-datetime}})
}
}
}
}
{{/gson}}
{{#kotlinx_serialization}}
{{^kotlinx-datetime}}
@Serializer(forClass = LocalDateTime::class)
{{#nonPublicApi}}internal {{/nonPublicApi}}object LocalDateTimeAdapter : KSerializer<LocalDateTime> {
override val descriptor: SerialDescriptor = PrimitiveSerialDescriptor("LocalDateTime", PrimitiveKind.STRING)
@@ -83,4 +99,5 @@ import org.threeten.bp.format.DateTimeFormatter
return LocalDateTime.parse(decoder.decodeString(), DateTimeFormatter.ISO_LOCAL_DATE_TIME)
}
}
{{/kotlinx-datetime}}
{{/kotlinx_serialization}}

View File

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

View File

@@ -24,13 +24,12 @@ 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
@@ -66,6 +65,7 @@ import java.util.concurrent.atomic.AtomicLong
.add(OffsetDateTimeAdapter())
{{#kotlinx-datetime}}
.add(InstantAdapter())
.add(LocalTimeAdapter())
{{/kotlinx-datetime}}
.add(LocalDateTimeAdapter())
.add(LocalDateAdapter())
@@ -92,6 +92,7 @@ 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())
@@ -111,6 +112,7 @@ 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"))
@@ -122,9 +124,11 @@ import java.util.concurrent.atomic.AtomicLong
val kotlinxSerializationAdapters = SerializersModule {
contextual(BigDecimal::class, BigDecimalAdapter)
contextual(BigInteger::class, BigIntegerAdapter)
{{^kotlinx-datetime}}
contextual(LocalDate::class, LocalDateAdapter)
contextual(LocalDateTime::class, LocalDateTimeAdapter)
contextual(OffsetDateTime::class, OffsetDateTimeAdapter)
{{/kotlinx-datetime}}
contextual(UUID::class, UUIDAdapter)
contextual(AtomicInteger::class, AtomicIntegerAdapter)
contextual(AtomicLong::class, AtomicLongAdapter)

View File

@@ -1,5 +1,6 @@
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
@@ -11,7 +12,7 @@ sealed class ApiException(msg: String, val code: Int) : Exception(msg)
class NotFoundException(msg: String, code: Int = HttpStatus.NOT_FOUND.value()) : ApiException(msg, code)
@Configuration("{{apiPackage}}.DefaultExceptionHandler")
@ControllerAdvice
class DefaultExceptionHandler {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,17 +7,31 @@ import unittest
from {{apiPackage}}.{{classFilename}} import {{classname}}
class {{#operations}}Test{{classname}}(unittest.TestCase):
class {{#operations}}Test{{classname}}(unittest.{{#asyncio}}IsolatedAsyncio{{/asyncio}}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}}
{{#operation}}
{{#asyncio}}
async def test_{{operationId}}(self) -> None:
{{/asyncio}}
{{^asyncio}}
def test_{{operationId}}(self) -> None:
{{/asyncio}}
"""Test case for {{{operationId}}}
{{#summary}}

View File

@@ -174,6 +174,11 @@ 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
@@ -198,8 +203,3 @@ class RESTClientObject:
r = await pool_manager.request(**args)
return RESTResponse(r)

View File

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

View File

@@ -10,7 +10,7 @@ keywords = ["OpenAPI", "OpenAPI-Generator", "{{{appName}}}"]
include = ["{{packageName}}/py.typed"]
[tool.poetry.dependencies]
python = "^3.7"
python = "^3.8"
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.4.1"
mypy = ">=1.5"
[build-system]

View File

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

View File

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

View File

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

View File

@@ -18,7 +18,7 @@ use crate::header;
/// which helps with FFI.
#[allow(non_camel_case_types)]
#[repr(C)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize)]
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, serde::Serialize, serde::Deserialize, Hash)]
#[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}}}: {{#isNullable}}swagger::Nullable<{{/isNullable}}{{{dataType}}}{{#isNullable}}>{{/isNullable}},
pub {{{name}}}: {{{dataType}}},
{{/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<{{#isNullable}}swagger::Nullable<{{/isNullable}}{{{dataType}}}{{#isNullable}}>{{/isNullable}}>,
pub {{{name}}}: Option<{{{dataType}}}>,
{{/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}}}: {{#isNullable}}swagger::Nullable<{{/isNullable}}{{{dataType}}}{{#isNullable}}>{{/isNullable}}, {{/defaultValue}}{{/vars}}) -> {{{classname}}} {
pub fn new({{#vars}}{{^defaultValue}}{{{name}}}: {{{dataType}}}, {{/defaultValue}}{{/vars}}) -> {{{classname}}} {
{{{classname}}} {
{{#vars}} {{#defaultValue}}{{{name}}}: {{{defaultValue}}}{{/defaultValue}}{{^defaultValue}}{{{name}}}{{/defaultValue}},
{{/vars}}
@@ -389,18 +389,21 @@ impl std::string::ToString for {{{classname}}} {
let params: Vec<Option<String>> = vec![
{{#vars}}
{{#isByteArray}}
// Skipping {{baseName}} in query parameter serialization
// Skipping byte array {{baseName}} in query parameter serialization
{{/isByteArray}}
{{^isByteArray}}
{{#isBinary}}
// Skipping {{baseName}} in query parameter serialization
// Skipping binary data {{baseName}} in query parameter serialization
{{/isBinary}}
{{^isBinary}}
{{#isMap}}
// Skipping {{baseName}} in query parameter serialization
// Skipping map {{baseName}} in query parameter serialization
{{/isMap}}
{{^isMap}}
{{^isPrimitiveType}}
// Skipping {{baseName}} in query parameter serialization
// Skipping non-primitive type {{baseName}} in query parameter serialization
{{/isPrimitiveType}}
{{^isByteArray}}{{^isBinary}}{{^isMap}}{{#isPrimitiveType}}
{{#isPrimitiveType}}
{{#required}}
Some("{{{baseName}}}".to_string()),
{{^isArray}}
@@ -443,7 +446,10 @@ impl std::string::ToString for {{{classname}}} {
].join(",")
}),
{{/required}}
{{/isPrimitiveType}}{{/isMap}}{{/isBinary}}{{/isByteArray}}
{{/isPrimitiveType}}
{{/isMap}}
{{/isBinary}}
{{/isByteArray}}
{{/vars}}
];

View File

@@ -155,9 +155,12 @@ extension KeyedEncodingContainerProtocol {
}
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} mutating func encode(_ value: Decimal, forKey key: Self.Key) throws {
var mutableValue = value
let stringValue = NSDecimalString(&mutableValue, Locale(identifier: "en_US"))
try encode(stringValue, forKey: key)
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)
}
{{#nonPublicApi}}internal{{/nonPublicApi}}{{^nonPublicApi}}public{{/nonPublicApi}} mutating func encodeIfPresent(_ value: Decimal?, forKey key: Self.Key) throws {

View File

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

View File

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

View File

@@ -0,0 +1,86 @@
/*
* Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech)
* Copyright 2018 SmartBear Software
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.openapitools.codegen.goserver;
import org.openapitools.codegen.DefaultGenerator;
import org.openapitools.codegen.TestUtils;
import org.openapitools.codegen.config.CodegenConfigurator;
import org.testng.Assert;
import org.testng.annotations.Test;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.List;
public class GoServerCodegenTest {
@Test
public void verifyGoMod() throws IOException {
File output = Files.createTempDirectory("test").toFile();
output.deleteOnExit();
final CodegenConfigurator configurator = createDefaultCodegenConfigurator(output)
.setInputSpec("src/test/resources/3_0/go-server/route-order.yaml");
DefaultGenerator generator = new DefaultGenerator();
List<File> files = generator.opts(configurator.toClientOptInput()).generate();
files.forEach(File::deleteOnExit);
TestUtils.assertFileExists(Paths.get(output + "/go.mod"));
TestUtils.assertFileContains(Paths.get(output + "/go.mod"),
"module github.com/my-user/my-repo");
TestUtils.assertFileContains(Paths.get(output + "/go.mod"),
"require github.com/gorilla/mux v1.8.0");
}
@Test
public void verifyOrder() throws IOException {
File output = Files.createTempDirectory("test").toFile();
output.deleteOnExit();
final CodegenConfigurator configurator = createDefaultCodegenConfigurator(output)
.setInputSpec("src/test/resources/3_0/go-server/route-order.yaml");
DefaultGenerator generator = new DefaultGenerator();
List<File> files = generator.opts(configurator.toClientOptInput()).generate();
files.forEach(File::deleteOnExit);
TestUtils.assertFileExists(Paths.get(output + "/go/routers.go"));
TestUtils.assertFileContains(Paths.get(output + "/go/routers.go"),
"type Routes map[string]Route");
TestUtils.assertFileExists(Paths.get(output + "/go/api_dev.go"));
// verify /getPath/latest is first route
Assert.assertEquals(Files.readAllLines(Paths.get(output + "/go/api_dev.go")).get(52), "\t\t\"GetLatest\": Route{");
// verify /getPath/{id} is second route
Assert.assertEquals(Files.readAllLines(Paths.get(output + "/go/api_dev.go")).get(57), "\t\t\"GetById\": Route{");
}
private static CodegenConfigurator createDefaultCodegenConfigurator(File output) {
return new CodegenConfigurator()
.setGeneratorName("go-server")
.setGitUserId("my-user")
.setGitRepoId("my-repo")
.setPackageName("mypackage")
.setOutputDir(output.getAbsolutePath().replace("\\", "/"));
}
}

View File

@@ -34,6 +34,7 @@ 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;
@@ -406,6 +407,32 @@ public class KotlinClientCodegenModelTest {
TestUtils.assertFileNotExists(Paths.get(path, "gradle", "wrapper", "gradle-wrapper.jar"));
}
@Test
public void testFailOnUnknownPropertiesAdditionalProperty() {
final KotlinClientCodegen codegen = new KotlinClientCodegen();
// Default case, nothing provided
codegen.processOpts();
ConfigAssert configAssert = new ConfigAssert(codegen.additionalProperties());
// Default to false
configAssert.assertValue(KotlinClientCodegen.FAIL_ON_UNKNOWN_PROPERTIES, codegen::isFailOnUnknownProperties, Boolean.FALSE);
// Provide true
codegen.additionalProperties().put(KotlinClientCodegen.FAIL_ON_UNKNOWN_PROPERTIES, true);
codegen.processOpts();
// Should be true
configAssert.assertValue(KotlinClientCodegen.FAIL_ON_UNKNOWN_PROPERTIES, codegen::isFailOnUnknownProperties, Boolean.TRUE);
// Provide false
codegen.additionalProperties().put(KotlinClientCodegen.FAIL_ON_UNKNOWN_PROPERTIES, false);
codegen.processOpts();
// Should be false
configAssert.assertValue(KotlinClientCodegen.FAIL_ON_UNKNOWN_PROPERTIES, codegen::isFailOnUnknownProperties, Boolean.FALSE);
}
private static class ModelNameTest {
private final String expectedName;
private final String expectedClassName;

View File

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

View File

@@ -0,0 +1,30 @@
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'

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