├── .github ├── dependabot.yml └── workflows │ ├── build.yaml │ └── release.yaml ├── .gitignore ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── build.gradle ├── buildSrc └── src │ └── main │ └── groovy │ ├── VersionPlugin.groovy │ └── VersionTask.groovy ├── docs ├── README.adoc ├── antora.yml └── modules │ └── ROOT │ ├── images │ ├── openapi-processor-spring-at-1280x200.png │ └── openapi-processor-spring@1280x200.png │ ├── nav.adoc │ ├── pages │ ├── data.adoc │ ├── gradle.adoc │ ├── index.adoc │ ├── links.adoc │ ├── mapping │ │ ├── annotation.adoc │ │ ├── basic.adoc │ │ ├── endpoint.adoc │ │ ├── extension.adoc │ │ ├── global.adoc │ │ ├── index.adoc │ │ ├── logging.adoc │ │ ├── null.adoc │ │ ├── package-name.adoc │ │ ├── parameter.adoc │ │ ├── response.adoc │ │ ├── result-status.adoc │ │ ├── result-style.adoc │ │ ├── result.adoc │ │ ├── schema.adoc │ │ ├── single-multi.adoc │ │ └── structure.adoc │ └── processor │ │ ├── bean-validation.adoc │ │ ├── configuration.adoc │ │ ├── deprecated.adoc │ │ ├── endpoint-content.adoc │ │ ├── endpoint-interface.adoc │ │ ├── enums.adoc │ │ ├── identifier.adoc │ │ ├── index.adoc │ │ ├── models.adoc │ │ ├── one-of-interface.adoc │ │ ├── parameter.adoc │ │ ├── requestbody.adoc │ │ ├── response.adoc │ │ └── server-url.adoc │ └── partials │ ├── links.adoc │ └── vars.adoc ├── gradle.properties ├── gradle ├── libs.versions.toml ├── publishing.gradle ├── publishing.tasks.gradle.kts └── wrapper │ ├── gradle-wrapper.jar │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── images ├── openapi-processor-spring@1280x200.png ├── openapi-processor-spring@1280x640.png └── openapi-processor-spring@400x200.png ├── justfile ├── settings.gradle └── src ├── main ├── kotlin │ └── io │ │ └── openapiprocessor │ │ └── spring │ │ ├── model │ │ └── parameters │ │ │ ├── MultipartParameter.kt │ │ │ └── QueryParameter.kt │ │ ├── processor │ │ ├── ProcessingException.kt │ │ ├── SpringFramework.kt │ │ ├── SpringFrameworkAnnotations.kt │ │ ├── SpringProcessor.kt │ │ ├── SpringService.kt │ │ └── SpringServiceV2.kt │ │ └── writer │ │ └── java │ │ ├── AdditionalEnumWriter.kt │ │ ├── EnumConverterFactoryWriter.kt │ │ ├── MappingAnnotationWriter.kt │ │ ├── PackageInfoWriter.kt │ │ ├── ParameterAnnotationWriter.kt │ │ ├── SpringWriterFactory.kt │ │ └── StatusAnnotationWriter.kt └── resources │ └── META-INF │ └── services │ ├── com.github.hauner.openapi.api.OpenApiProcessor │ ├── io.openapiprocessor.api.OpenApiProcessor │ ├── io.openapiprocessor.api.v1.OpenApiProcessor │ └── io.openapiprocessor.api.v2.OpenApiProcessor ├── test ├── groovy │ └── io │ │ └── openapiprocessor │ │ └── spring │ │ └── writer │ │ └── java │ │ ├── CookieParameterAnnotationWriterSpec.groovy │ │ ├── HeaderParameterAnnotationWriterSpec.groovy │ │ ├── MappingAnnotationWriterSpec.groovy │ │ ├── MethodWriterSpec.groovy │ │ ├── ParameterAnnotationWriterSpec.groovy │ │ ├── PathParameterAnnotationWriterSpec.groovy │ │ └── QueryParameterAnnotationWriterSpec.groovy └── kotlin │ └── io │ └── openapiprocessor │ └── spring │ ├── processor │ ├── SpringFrameworkAnnotationsSpec.kt │ └── SpringServiceSpec.kt │ └── writer │ └── java │ └── StatusAnnotationWriterSpec.kt └── testInt ├── kotlin └── io │ └── openapiprocessor │ └── spring │ ├── CompileExpectedSpec.kt │ ├── ProcessorEndToEndJimfsSpec.kt │ ├── ProcessorEndToEndSpec.kt │ ├── ProcessorPendingSpec.kt │ ├── ProcessorTestSets.kt │ └── ProcessorTestSetsSupport.kt └── resources ├── compile └── Generated.java ├── logback-test.xml └── tests ├── endpoint-http-mapping ├── inputs.yaml ├── inputs │ ├── mapping.yaml │ ├── openapi30.yaml │ └── openapi31.yaml ├── outputs.yaml └── outputs │ └── api │ └── EndpointApi.java ├── params-complex-data-types ├── inputs.yaml ├── inputs │ ├── mapping.yaml │ ├── openapi30.yaml │ └── openapi31.yaml ├── outputs.yaml └── outputs │ ├── api │ └── Api.java │ └── model │ ├── _default_ │ └── Props.java │ └── _record_ │ └── Props.java ├── params-enum ├── inputs.yaml ├── inputs │ ├── mapping.yaml │ ├── openapi30.yaml │ └── openapi31.yaml ├── outputs.yaml └── outputs │ ├── api │ └── EnumApi.java │ ├── model │ └── _default_ │ │ └── Foo.java │ └── spring │ ├── StringToEnumConverterFactory.java │ └── package-info.java ├── params-pageable-mapping ├── inputs.yaml ├── inputs │ ├── mapping.yaml │ ├── openapi30.yaml │ └── openapi31.yaml ├── outputs.yaml └── outputs │ └── api │ └── Api.java ├── params-path-simple-data-types ├── inputs.yaml ├── inputs │ ├── mapping.yaml │ ├── openapi30.yaml │ └── openapi31.yaml ├── outputs.yaml └── outputs │ └── api │ └── EndpointApi.java ├── params-query-annotate-simple-mapping ├── inputs.yaml ├── inputs │ ├── mapping.yaml │ ├── openapi30.yaml │ └── openapi31.yaml ├── outputs.yaml └── outputs │ └── api │ └── Api.java ├── params-request-body-multipart-mapping ├── inputs.yaml ├── inputs │ ├── mapping.yaml │ ├── openapi30.yaml │ └── openapi31.yaml ├── outputs.yaml └── outputs │ └── api │ └── Api.java ├── params-request-body ├── inputs.yaml ├── inputs │ ├── mapping.yaml │ ├── openapi30.yaml │ └── openapi31.yaml ├── outputs.yaml └── outputs │ ├── api │ └── Api.java │ └── model │ ├── _default_ │ └── Book.java │ └── _record_ │ └── Book.java ├── params-simple-data-types ├── inputs.yaml ├── inputs │ ├── mapping.yaml │ ├── openapi30.yaml │ └── openapi31.yaml ├── outputs.yaml └── outputs │ └── api │ └── EndpointApi.java └── response-status ├── inputs.yaml ├── inputs ├── mapping.yaml ├── openapi30.yaml └── openapi31.yaml ├── outputs.yaml └── outputs └── api └── Api.java /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | day: friday 8 | 9 | - package-ecosystem: "gradle" 10 | directory: "/" 11 | schedule: 12 | interval: "weekly" 13 | day: friday 14 | -------------------------------------------------------------------------------- /.github/workflows/build.yaml: -------------------------------------------------------------------------------- 1 | name: build 2 | 3 | on: 4 | push: 5 | branches: 6 | - '**' 7 | paths-ignore: 8 | - 'gradle.properties' 9 | - 'LICENSE' 10 | - 'README.md' 11 | - 'docs/**' 12 | tags-ignore: 13 | - 'v*' 14 | workflow_dispatch: 15 | 16 | jobs: 17 | 18 | test: 19 | name: test 20 | 21 | runs-on: ${{ matrix.os }} 22 | strategy: 23 | matrix: 24 | os: [ubuntu-latest, windows-latest] 25 | 26 | steps: 27 | - name: checkout 28 | uses: actions/checkout@v4 29 | 30 | - name: set up jdk 31 | uses: actions/setup-java@v4 32 | with: 33 | distribution: 'temurin' 34 | java-version: | 35 | 17 36 | 11 37 | 38 | - name: set up gradle 39 | uses: gradle/actions/setup-gradle@v4 40 | 41 | - name: run tests 42 | run: | 43 | ./gradlew check 44 | 45 | - name: archive test results 46 | uses: actions/upload-artifact@v4 47 | if: always() 48 | with: 49 | name: test-results-${{ matrix.os }} 50 | path: build/reports 51 | 52 | publish: 53 | if: ${{ github.actor == 'hauner' }} 54 | 55 | needs: [test] 56 | name: publish snapshot 57 | 58 | runs-on: ubuntu-latest 59 | 60 | steps: 61 | - name: checkout 62 | uses: actions/checkout@v4 63 | 64 | - name: set up jdk 65 | uses: actions/setup-java@v4 66 | with: 67 | distribution: 'temurin' 68 | java-version: | 69 | 17 70 | 11 71 | 72 | - name: set up gradle 73 | uses: gradle/actions/setup-gradle@v4 74 | 75 | - name: publish snapshot 76 | env: 77 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 78 | SIGN_KEY: ${{ secrets.SIGN_KEY_ORG }} 79 | SIGN_PWD: ${{ secrets.SIGN_PWD }} 80 | PUBLISH_USER: ${{ secrets.PUBLISH_USER }} 81 | PUBLISH_KEY: ${{ secrets.PUBLISH_KEY }} 82 | run: | 83 | ./gradlew publishToSonatype --stacktrace 84 | 85 | - name: archive test results 86 | uses: actions/upload-artifact@v4 87 | if: always() 88 | with: 89 | name: publish results 90 | path: build 91 | 92 | sonar: 93 | needs: [publish] 94 | name: sonar 95 | 96 | runs-on: ubuntu-latest 97 | 98 | steps: 99 | - name: checkout 100 | uses: actions/checkout@v4 101 | with: 102 | fetch-depth: 0 103 | 104 | - name: cache sonar 105 | uses: actions/cache@v4 106 | with: 107 | path: | 108 | ~/.sonar/cache 109 | key: ${{ runner.os }}-sonar 110 | restore-keys: | 111 | ${{ runner.os }}-sonar 112 | 113 | - name: set up jdk 114 | uses: actions/setup-java@v4 115 | with: 116 | distribution: 'temurin' 117 | java-version: | 118 | 11 119 | 17 120 | 121 | - name: set up gradle 122 | uses: gradle/actions/setup-gradle@v4 123 | 124 | - name: run sonar 125 | env: 126 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 127 | SONAR_TOKEN: ${{ secrets.SONAR_TOKEN }} 128 | run: | 129 | ./gradlew check sonar 130 | 131 | - name: archive sonar results 132 | uses: actions/upload-artifact@v4 133 | if: always() 134 | with: 135 | name: sonar results 136 | path: build 137 | -------------------------------------------------------------------------------- /.github/workflows/release.yaml: -------------------------------------------------------------------------------- 1 | name: release 2 | 3 | on: 4 | release: 5 | types: [published] 6 | 7 | jobs: 8 | 9 | publish: 10 | name: publish release 11 | 12 | runs-on: ubuntu-latest 13 | 14 | steps: 15 | - name: checkout 16 | uses: actions/checkout@v4 17 | 18 | - name: set up jdk 19 | uses: actions/setup-java@v4 20 | with: 21 | distribution: 'temurin' 22 | java-version: | 23 | 11 24 | 17 25 | 26 | - name: set up gradle 27 | uses: gradle/actions/setup-gradle@v4 28 | 29 | - name: publish release 30 | env: 31 | PUBLISH_USER: ${{ secrets.PUBLISH_USER }} 32 | PUBLISH_KEY: ${{ secrets.PUBLISH_KEY }} 33 | SIGN_KEY: ${{ secrets.SIGN_KEY_ORG }} 34 | SIGN_PWD: ${{ secrets.SIGN_PWD }} 35 | run: | 36 | ./gradlew publishToSonatype closeAndReleaseSonatypeStagingRepository 37 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | out 2 | build 3 | .gradle 4 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # contributing to openapi-processor 2 | 3 | see [CONTRIBUTING.md](https://github.com/openapi-processor/openapi-processor-base/blob/main/CONTRIBUTING.md) in the openapi-processor-base repository. 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![][badge-ci]][workflow-ci] 2 | [![][sonar-tecdebt]][sonar] 3 | [![][badge-central]][oap-central] 4 | 5 | ![openapi-processor-spring logo](images/openapi-processor-spring@1280x200.png) 6 | 7 | # openapi-processor-spring 8 | 9 | an [OpenAPI][openapi] interface only & model java code generator for [Spring Boot][springboot]. 10 | 11 | 12 | # documentation 13 | 14 | See [here][oap-docs]. 15 | 16 | # snapshot repository 17 | 18 | to use snapshot versions add `https://oss.sonatype.org/content/repositories/snapshots` as maven repository to your build file. 19 | 20 | 21 | [oap-central]: https://search.maven.org/search?q=io.openapiprocessor 22 | [badge-central]: https://img.shields.io/maven-central/v/io.openapiprocessor/openapi-processor-spring?label=Maven%20Central 23 | [badge-license]: https://img.shields.io/badge/License-Apache%202.0-blue.svg?labelColor=313A42 24 | [badge-ci]: https://github.com/openapi-processor/openapi-processor-spring/workflows/build/badge.svg 25 | [oap-license]: https://github.com/openapi-processor/openapi-processor-spring/blob/master/LICENSE 26 | [workflow-ci]: https://github.com/openapi-processor/openapi-processor-spring/actions?query=workflow%3Abuild 27 | [sonar-coverage]: https://sonarcloud.io/api/project_badges/measure?project=openapi-processor_openapi-processor-spring&metric=coverage 28 | [sonar-tecdebt]: https://sonarcloud.io/api/project_badges/measure?project=openapi-processor_openapi-processor-spring&metric=sqale_index 29 | [sonar]: https://sonarcloud.io/dashboard?id=openapi-processor_openapi-processor-spring 30 | [oap-docs]: https://docs.openapiprocessor.io 31 | [openapi]: https://www.openapis.org/ 32 | [springboot]: https://spring.io/projects/spring-boot 33 | -------------------------------------------------------------------------------- /buildSrc/src/main/groovy/VersionPlugin.groovy: -------------------------------------------------------------------------------- 1 | import org.gradle.api.Action 2 | import org.gradle.api.Plugin 3 | import org.gradle.api.Project 4 | 5 | /** 6 | * provides a "generateVersion" task to a create a simple Version.java class: 7 | * 8 | *
{@code
 9 |  * package io.openapiprocessor.spring;
10 |  *
11 |  * public class Version {
12 |  *     public static final String version = "${project.version}";
13 |  * }
14 |  * }
15 | * 16 | * 17 | * The io/openapiprocessor/spring/Version.java file is generated to: 18 | * 19 | * $(project.buildDir}/main/java 20 | * 21 | * Add it as a source directory to include it in compilation. 22 | */ 23 | class VersionPlugin implements Plugin { 24 | 25 | void apply(Project project) { 26 | project.afterEvaluate (new Action () { 27 | 28 | @Override 29 | void execute (Project prj) { 30 | prj.tasks.register ('generateVersion', VersionTask , new Action() { 31 | 32 | @Override 33 | void execute (VersionTask task) { 34 | task.targetDir = prj.buildDir 35 | task.version = prj.version 36 | } 37 | 38 | }) 39 | } 40 | 41 | }) 42 | } 43 | 44 | } 45 | -------------------------------------------------------------------------------- /buildSrc/src/main/groovy/VersionTask.groovy: -------------------------------------------------------------------------------- 1 | import org.gradle.api.DefaultTask 2 | import org.gradle.api.tasks.Internal 3 | import org.gradle.api.tasks.OutputDirectory 4 | import org.gradle.api.tasks.TaskAction 5 | 6 | import java.nio.file.Files 7 | import java.nio.file.Path 8 | import java.time.Instant 9 | 10 | /** 11 | * simple task to create a Version class. 12 | */ 13 | class VersionTask extends DefaultTask { 14 | 15 | /** 16 | * Target directory for the generated version class. 17 | * 18 | * Used by gradle for the up-to-date check. 19 | */ 20 | @OutputDirectory 21 | String targetDir 22 | 23 | @Internal 24 | String version 25 | 26 | /** 27 | * generate the version class. 28 | */ 29 | @TaskAction 30 | void generateVersion () { 31 | def path = Path.of (targetDir, "version", "io", "openapiprocessor", "spring") 32 | Files.createDirectories(path) 33 | 34 | def target = path.resolve ("Version.java") 35 | 36 | target.text = """\ 37 | /* 38 | * DO NOT MODIFY - this file was auto generated by buildSrc/src/main/groovy/VersionPlugin.groovy 39 | * 40 | * ${Instant.now ().toString ()} 41 | */ 42 | 43 | package io.openapiprocessor.spring; 44 | 45 | public class Version { 46 | public static final String version = "${version}"; 47 | } 48 | 49 | """ 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /docs/README.adoc: -------------------------------------------------------------------------------- 1 | = Writer Notes 2 | 3 | == build with theme locally 4 | 5 | antora antora-playbook.yml --ui-bundle-url ../openapi-processor-site-ui/build/ui-bundle.zip 6 | 7 | == pitfalls 8 | 9 | * partials but `include::partial$ ...` !! 10 | 11 | -------------------------------------------------------------------------------- /docs/antora.yml: -------------------------------------------------------------------------------- 1 | name: spring 2 | title: oap-spring 3 | version: master 4 | #start_page: ROOT:index.adoc 5 | nav: 6 | - modules/ROOT/nav.adoc 7 | -------------------------------------------------------------------------------- /docs/modules/ROOT/images/openapi-processor-spring-at-1280x200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openapi-processor/openapi-processor-spring/6687d7b56d1921110fc2b1cea70279877ae18141/docs/modules/ROOT/images/openapi-processor-spring-at-1280x200.png -------------------------------------------------------------------------------- /docs/modules/ROOT/images/openapi-processor-spring@1280x200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openapi-processor/openapi-processor-spring/6687d7b56d1921110fc2b1cea70279877ae18141/docs/modules/ROOT/images/openapi-processor-spring@1280x200.png -------------------------------------------------------------------------------- /docs/modules/ROOT/nav.adoc: -------------------------------------------------------------------------------- 1 | * xref:index.adoc[Introduction] 2 | * xref:processor/index.adoc[The Processor] 3 | ** xref:processor/configuration.adoc[Configuration] 4 | ** xref:processor/endpoint-interface.adoc[Endpoint Grouping] 5 | ** xref:processor/endpoint-content.adoc[Endpoint Content Type] 6 | ** xref:processor/models.adoc[Models] 7 | ** xref:processor/parameter.adoc[Parameter] 8 | ** xref:processor/requestbody.adoc[Request Body] 9 | ** xref:processor/response.adoc[Responses] 10 | ** xref:processor/enums.adoc[Enums] 11 | ** xref:processor/one-of-interface.adoc[oneOf] 12 | ** xref:processor/deprecated.adoc[Deprecated] 13 | ** xref:processor/identifier.adoc[Identifier] 14 | ** xref:processor/bean-validation.adoc[Bean Validation] 15 | ** xref:processor/server-url.adoc[Server Url] 16 | * xref:mapping/index.adoc[Type Mapping] 17 | ** xref:mapping/structure.adoc[Type Mapping Structure] 18 | ** xref:mapping/basic.adoc[Default Type Mapping] 19 | ** xref:mapping/global.adoc[Global Type Mapping] 20 | ** xref:mapping/schema.adoc[Global Schema Mapping] 21 | ** xref:mapping/parameter.adoc[Global Parameter Mapping] 22 | ** xref:mapping/response.adoc[Global Response Mapping] 23 | ** xref:mapping/result.adoc[Global Result Mapping] 24 | ** xref:mapping/result-status.adoc[Global Result Status Mapping] 25 | ** xref:mapping/result-style.adoc[Global Result Style] 26 | ** xref:mapping/single-multi.adoc[Global Single & Multi Mapping] 27 | ** xref:mapping/endpoint.adoc[Endpoint Mapping] 28 | ** xref:mapping/annotation.adoc[Annotation Mapping] 29 | ** xref:mapping/extension.adoc[Extension Mapping] 30 | ** xref:mapping/null.adoc[Null Mapping] 31 | ** xref:mapping/package-name.adoc[package-name Mapping] 32 | ** xref:mapping/logging.adoc[log Mapping lookup] 33 | * xref:gradle.adoc[Gradle Integration] 34 | * xref:links.adoc[Links] 35 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/data.adoc: -------------------------------------------------------------------------------- 1 | 2 | //:page-layout: oap 3 | 4 | Huhu! 5 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/gradle.adoc: -------------------------------------------------------------------------------- 1 | = Gradle Plugin 2 | include::partial$links.adoc[] 3 | 4 | The xref:gradle::index.adoc[openapi-processor-gradle] gradle plugin can run any of the **openapi-processor**'s. 5 | 6 | To use it in a Gradle project, the Gradle file of the project requires a few additional instructions. The following sections describe how to activate & configure **openapi-processor-spring** in a `build.gradle` file. 7 | 8 | 9 | == adding the plugin 10 | 11 | To activate the plugin add it to (like any other gradle plugin) the `plugins` configuration: 12 | 13 | [source,groovy] 14 | ---- 15 | plugins { 16 | .... 17 | // add openapi-processor-gradle plugin 18 | id 'io.openapiprocessor.openapi-processor' version '' 19 | } 20 | ---- 21 | 22 | == configuring processor-spring 23 | 24 | The plugin will add an `openapiProcessor` configuration block that is used to configure the processors. Configuration for a specific processor belongs inside it with the processor name as configuration block name. 25 | 26 | [source,groovy] 27 | ---- 28 | openapiProcessor { 29 | 30 | spring { 31 | processor 'io.openapiprocessor:openapi-processor-spring:' 32 | apiPath "$projectDir/src/api/openapi.yaml" 33 | targetDir "$projectDir/build/openapi" 34 | mapping "$projectDir/mapping.yaml" 35 | parser "INTERNAL" 36 | } 37 | 38 | } 39 | ---- 40 | 41 | * `processor`: (**required**) the processor dependency. This works in the same way as adding a dependency to a configuration in the gradle `dependencies` block. It is given here to avoid unwanted side effects on the build dependencies of the project. 42 | 43 | * `apiPath`: (**required**) the path to the `openapi.yaml` file and the main input for the processor. If set in the top-level block, it will be used for all configured processors. 44 | 45 | * `targetDir`: (**required**) the output folder for generating interfaces and models. This is the parent of the `packageName` folder tree. It is recommended to set this to a subfolder of gradle's standard `build` directory, so it is cleared by the `clean` task and does not pollute the `sources` 46 | directory. 47 | + 48 | See <> how to include the `targetDir` in compilation and packing. 49 | 50 | * `mapping`: (**required**) provides the processor mapping options. This is a path to the YAML file. See xref:processor/configuration.adoc[Configuration] for a description of the mapping YAML. This replaces the `typeMappings` option. 51 | 52 | * `showWarnings`: (**optional**) `true` to show warnings from the open api parser or `false` (default) to show no warnings (this option has currently no effect). 53 | 54 | * `parser`: (**optional**), sets the openapi parser used to read the OpenAPI description. Available values are `SWAGGER`, `OPENAPI4J` or `INTERNAL` (default). 55 | ** `INTERNAL`: link:{openapi-parser}[internal OpenAPI parser, window="_blank"], supports *OpenAPI 3.0.x* & *OpenAPI 3.1.0*. 56 | ** `SWAGGER`: link:{swagger-parser}[Swagger OpenAPI parser, window="_blank"], supports *OpenAPI 3.0.x* 57 | ** `OPENAPI4J`: link:{openapi4j}[openapi4j OpenAPI parser, window="_blank"], supports *OpenAPI 3.0.x*. It provides better validation than `SWAGGER`, unfortunately it is no longer maintained and is deprecated. 58 | *** the parser provides JSON schema validation. 59 | 60 | == running processor-spring 61 | 62 | The plugin will add a gradle task `processSpring` to run the processor. 63 | 64 | To automatically generate & compile the processor output two additional configurations are necessary. 65 | 66 | * the `sourceSets` are extended to include the processor output (assuming a java project): 67 | + 68 | [source,groovy] 69 | ---- 70 | sourceSets { 71 | main { 72 | java { 73 | // add generated files 74 | srcDir 'build/openapi' 75 | } 76 | } 77 | } 78 | ---- 79 | 80 | * and the `compileJava` task gets a dependency on `processSpring`, so it runs before compilation (again, assuming a java project): 81 | + 82 | [source,groovy] 83 | ---- 84 | // generate api before compiling 85 | compileJava.dependsOn ('processSpring') 86 | ---- 87 | 88 | Adding automatic compilation in this way will also automatically include the generated files into the `jar` build artifact. 89 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/index.adoc: -------------------------------------------------------------------------------- 1 | :author: Martin Hauner 2 | :page-title: openapi-processor-spring 3 | //:page-aliases: latest@spring:ROOT:index.adoc 4 | include::partial$links.adoc[] 5 | 6 | // 7 | // content 8 | // 9 | image:openapi-processor-spring-at-1280x200.png[openapi-processor-spring] 10 | 11 | // badges 12 | link:{oaps-ci}[image:{badge-ci}[]] 13 | link:{oaps-license}[image:{badge-license}[]] 14 | link:{oap-central}[image:{badge-central}[]] 15 | 16 | 17 | *openapi-processor-spring* is an link:{openapi}[OpenAPI] interface & dto java code generator for link:{springboot}[Spring Boot]. 18 | 19 | It supports an approach where you explicitly define and document your Service API (using OpenAPI)with the Interface to the outside and its usage in mind before you implement it. You do not derive the API later from the implementation and its implicit design. (of course, adapt as you go...) 20 | 21 | The advantages are: 22 | 23 | * you have a single place to maintain the api which makes it easier to create a consistent api and keep the overview. 24 | * it is easy to document in plain text. You can use Markdown in the OpenAPI `description` properties. 25 | 26 | The processor generates java interfaces based on the endpoint description of the API and simple POJO classes or records for parameter or response schemas defined in the API. The processor adds all the required spring & jackson annotations to the interface, and all that is left to *you* is the implementation of the generated interfaces in any way you like. 27 | 28 | The interfaces will help to keep the implementation in sync with the API. If anything relevant changes in the API, the interface changes and the compiler will warn that the interface is not implemented correctly. 29 | 30 | The target programming language is Java. Therefore, the generated code is usable from most JVM languages. 31 | 32 | See the xref:processor/index.adoc[processor intro] for a short example. 33 | 34 | == Playground 35 | 36 | openapi-processor has a link:{oap-playground}[playground, window="_blank"] application. You can try out some samples or your own YAML and view the code that the processor generates. 37 | 38 | == Features 39 | 40 | - generates **only java interfaces and java dto classes** (get/set POJOs or records) for all defined endpoints and schemas to allow full control of the endpoint implementation. It does not generate any other file. See xref:processor/index.adoc[processor]. 41 | 42 | - **powerful type mappings with generic support** to map schemas defined in the openapi.yaml to existing java types. 43 | + 44 | For example, to map the openapi `array` type to a different java collection or to map paging parameters and results to th Spring types like `Page<>` & `Pageable`. 45 | + 46 | mappings can be defined globally, for a specific response, parameter or endpoint and or http method (of an endpoint). See xref:mapping/index.adoc[type mapping]. 47 | 48 | - Annotation-based **WebFlux support**. Actually, there is *no* explicit WebFlux support, but the mapping allows defining a *single*, and a *multi* wrapper class. *single* wraps non-array like result types(e.g. `Mono<>`). *multi* replaces array like result types or parameters with the given multi mapping. For example, it will replace `List` with `Flux` if the multi mapping contains the fully qualified `Flux` type. + 49 | //[.badge .badge-since]+since 1.0.0.M13+ 50 | 51 | - generates **human-readable code**. 52 | 53 | - add **additional parameters** to an endpoint which are not defined in the OpenAPI description. For 54 | example to pass a `HttpServletRequest` to the endpoint implementation. + 55 | //[.badge .badge-since]+since 1.0.0.M6+ 56 | 57 | - supports **bean validations**. The constraints of the openapi description map to java bean validation annotations. 58 | //[.badge .badge-since]+since 1.0.0.M6+ 59 | 60 | - allows **excluding endpoints** from generation. This is useful if the processor does not create the 61 | correct code for an endpoint. That way the processor can still be used for all the other endpoints. + 62 | //[.badge .badge-since]+since 1.0.0.M6+ 63 | 64 | - handle **multiple responses** by generating one endpoint method for each response content type. + 65 | //[.badge .badge-since]+since 1.0.0.M11+ 66 | 67 | - the generated code does not use swagger annotations. There is no need to generate the 68 | documentation from the code when the code originates from the documentation (i.e., an openapi.yaml). 69 | 70 | - *maven & gradle support* The plugin docs show how to run a processor and how to add the generated sources to the build. 71 | 72 | ** xref:maven::index.adoc[openapi-processor-maven] plugin. 73 | ** xref:gradle::index.adoc[openapi-processor-gradle] plugin. 74 | 75 | == Releases 76 | 77 | See the link:{oaps-releases}[release notes, window="_blank"] to find the latest release. The full 78 | artifact name is: 79 | 80 | .in gradle short notation 81 | ---- 82 | io.openapiprocessor:openapi-processor-spring: 83 | ---- 84 | 85 | == Feedback 86 | 87 | In case some feature is missing, or the generated code is not 100% what you would expect, create an link:{oaps-issues}[issue]. Preferably with a test case. Providing a test case will help significantly. :-) 88 | 89 | A _perfect_ test case is a single folder with two subfolders containing the source files and the expected output files: 90 | 91 | my-test-case 92 | +--- inputs.yaml 93 | |--- inputs 94 | | +--- mapping.yaml 95 | | \--- openapi.yaml 96 | +--- generated.yaml 97 | \--- generated 98 | +--- api 99 | | \--- Api.java 100 | \--- model 101 | \--- Foo.java 102 | 103 | `inputs.yaml` and `generated.yaml` use the same simple format: 104 | 105 | items: 106 | - inputs/openapi.yaml 107 | - inputs/mapping.yaml 108 | 109 | or 110 | 111 | items: 112 | - generated/api/Api.java 113 | - generated/model/Foo.java 114 | 115 | 116 | The link:{oapc-inttests}[core project], and the link:{oaps-inttests}[spring processor] have a number of existing integration tests that can be used as examples. 117 | 118 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/links.adoc: -------------------------------------------------------------------------------- 1 | = Links 2 | include::partial$links.adoc[] 3 | 4 | == OpenAPI 5 | 6 | * link:{openapi}[OpenAPI, window="_blank"] Homepage of the OpenAPI Initiative. 7 | * link:{openapi-spec}[OpenAPI specification, window="_blank"] Github repository of the OpenAPI specification. 8 | * link:{openapi-tools}[OpenAPI tools, window="_blank"] List of OpenAPI tools. 9 | * link:{openapi-generator}[OpenAPI generator, window="_blank"] Generate clients, servers and documentation from OpenAPI 2.0/3.x documents. 10 | * link:{swagger-parser}:[Swagger OpenAPI parser, window="_blank"] Java parser for OpenAPI specs. 11 | * link:{openapi4j}[openapi4j OpenAPI parser, window="_blank"] Alternative Java parser for OpenAPI specs. 12 | 13 | == Articles 14 | 15 | * link:https://dev.to/hauner/openapi-processor-spring-368k[openapi-processor-spring] an intro to openapi-processor-spring. 16 | * link:https://dev.to/hauner/openapi-processor-spring-type-mapping-4ecj[openapi-processor-spring type mapping] an intro to openapi-processor-spring type mapping. 17 | * link:https://dev.to/hauner/configuring-openapi-processor-maven-39pf[openapi-processor-maven] an intro to the maven plugin. 18 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/mapping/basic.adoc: -------------------------------------------------------------------------------- 1 | = Basic (primitive) mappings 2 | include::partial$links.adoc[] 3 | 4 | The OpenAPI specification defines a couple of basic link:{openapi-spec-types}[data types]. The basic data types are built-in into the processor. That means it will map the basic types automatically to a corresponding java type. There is no explicit type mapping required. 5 | 6 | The types with no default mapping can be mapped to a java type using the mapping configuration. 7 | 8 | == OpenAPI to Java type mapping 9 | 10 | The following table shows the automatic mapping of OpenAPIs primitive types to Java. 11 | 12 | 13 | |=== 14 | | `type` | `format` | java type 15 | 16 | | `boolean` 17 | | 18 | | `java.lang.Boolean` 19 | 20 | | `integer` 21 | | 22 | | `java.lang.Integer` 23 | 24 | | `integer` 25 | | `int32` 26 | | `java.lang.Integer` 27 | 28 | | `integer` 29 | | `int64` 30 | | `java.lang.Long` 31 | 32 | | `number` 33 | | 34 | | `java.lang.Float` 35 | 36 | | `number` 37 | | `float` 38 | | `java.lang.Float` 39 | 40 | | `number` 41 | | `double` 42 | | `java.lang.Double` 43 | 44 | | `string` 45 | | 46 | | `java.lang.String` 47 | 48 | | `string` 49 | | `binary` 50 | | no default mapping 51 | 52 | | `string` 53 | | `byte` 54 | | no default mapping 55 | 56 | | `string` 57 | | `date` 58 | | `java.time.LocalDate` 59 | 60 | | `string` 61 | | `date-time` 62 | | `java.time.OffsetDataTime` (better would be `java.time.Instant`) 63 | 64 | | `string` 65 | | `password` 66 | | no default mapping 67 | 68 | |=== 69 | 70 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/mapping/endpoint.adoc: -------------------------------------------------------------------------------- 1 | = Endpoint mappings 2 | include::partial$links.adoc[] 3 | include::partial$vars.adoc[] 4 | 5 | The global mapping variations are also available as explicit endpoint mappings. Instead of adding the mapping in the global sections `map/types`, `map/parameters` and `map/responses` they can be placed in the `map/paths` section as properties to an endpoint given by its path. 6 | 7 | [source,yaml] 8 | ---- 9 | map: 10 | 11 | # path mappings, only valid for the given path 12 | paths: 13 | 14 | # a path 15 | /foo: 16 | 17 | # path-specific result wrapper 18 | result: {target type} 19 | 20 | # path-specific single wrapper 21 | single: {target type} 22 | 23 | # path-specific multi wrapper 24 | multi: {target type} 25 | 26 | # list of path-specific mappings 27 | types: 28 | - from: {source type} => {target type} 29 | 30 | # list of path-specific parameter mappings, mapped by parameter name 31 | parameters: 32 | - name: {parameter name} => {target type} 33 | 34 | # add a (usually technical) parameter that is not described in the OpenAPI 35 | - add: {parameter name} => {target type} 36 | 37 | # add an extra annotation to parameters of type source type 38 | - type: {source type} @ {annotation type} 39 | 40 | # list of path-specific content mappings, mapped by content type 41 | responses: 42 | - content: {content type} => {target type} 43 | 44 | # another path 45 | /foobar: 46 | # excluding an endpoint 47 | exclude: true 48 | 49 | # path-specific result wrapper 50 | result: {target type} 51 | 52 | # path-specific single wrapper 53 | single: {target type} 54 | 55 | # path-specific multi wrapper 56 | multi: {target type} 57 | 58 | # list of path-specific mappings 59 | types: 60 | - from: {source type} => {target type} 61 | 62 | # list of path-specific parameter mappings, mapped by parameter name 63 | parameters: 64 | - name: {parameter name} => {target type} 65 | 66 | # add a (usually technical) parameter not described in the OpenAPI 67 | - add: {parameter name} => {target type} 68 | 69 | # add an extra annotation to parameters of type source type 70 | - type: {source type} @ {annotation type} 71 | 72 | # list of path-specific content mappings, mapped by content type 73 | responses: 74 | - content: {content type} => {target type} 75 | ---- 76 | 77 | The mappings defined as properties of an endpoint will be used only for this endpoint. They don't 78 | have any effect on other endpoints. 79 | 80 | == Endpoint mappings by http method 81 | 82 | It is possible to add mappings that apply only to a specific http method. The motivation for this is limiting the mapping only to the place where it is necessary. Http method mappings have priority over other mappings. In general, the most specific mapping is used. 83 | 84 | Here are a few examples of possible http endpoint mappings: 85 | 86 | [source,yaml,subs="attributes"] 87 | ---- 88 | openapi-processor-mapping: {var-mapping-version} 89 | 90 | map: 91 | 92 | paths: 93 | /foo: 94 | 95 | # normal endpoint mappings apply to all http methods (behaves exactly as before) 96 | types: 97 | - type: Foo => java.util.Collection 98 | 99 | # endpoint http method mappings apply only the specified http method 100 | get: 101 | result: org.springframework.http.ResponseEntity 102 | 103 | post: 104 | parameters: 105 | - add: request => javax.servlet.http.HttpServletRequest 106 | 107 | patch: 108 | null: org.openapitools.jackson.nullable.JsonNullable = JsonNullable.undefined() 109 | ---- 110 | 111 | The structure follows the OpenAPI, i.e. the http methods (or OpenAPI operations) are properties of the endpoint path. 112 | 113 | An http method mapping allows the same mappings as the endpoint mapping without http method, i.e. `exclude`, `result`, `single`, `multi`, `null`, `types`, `parameters` and `responses` (see the link:{json-schema}[json schema]). 114 | 115 | The last example is using the a `null` mapping that may only be interesting for the `PATCH` http method because there is no need for `nullable` properties for `GET` or `PUT`. 116 | 117 | Note that it is **not** possible to use different `null` mappings (or one http mapping with `null` and one without) on the **same** model schema. The processor generates only a *single* class for model schemas and with two different and ambiguous mappings the result is (currently) undefined. It is recommended to use two different schemas if the `null` mapping should only apply to a single method. 118 | 119 | == excluding endpoints 120 | 121 | //[.badge .badge-since]+since 1.0.0.M6+ 122 | 123 | It is possible to exclude endpoints from generation to make it easier to provide a hand written 124 | interface for the excluded endpoint. 125 | 126 | Excluding does not completely ignore the endpoint. Instead of generating it into the normal 127 | interface it is generated to a new interface with `Excluded` attached to its name. Type mappings 128 | still apply. 129 | 130 | That way the generated code is still available for reference, but it can be skipped by not 131 | implementing the `Excluded` interface. 132 | 133 | [source,yaml] 134 | ---- 135 | map: 136 | /foo: 137 | # excluding an endpoint 138 | exclude: true 139 | ---- 140 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/mapping/extension.adoc: -------------------------------------------------------------------------------- 1 | = Extension mapping 2 | include::partial$links.adoc[] 3 | include::partial$vars.adoc[] 4 | 5 | [.badge .badge-since]+since 2024.1+ 6 | 7 | This is part of annotation mapping. See xref:mapping/annotation.adoc[Annotation mapping] that shows more details about the annotation mapping format. 8 | 9 | Apart from the annotation mapping by an OpenAPI type, openapi-processor can use `x-` tension properties in the OpenAPI to add additional annotations to a schema property. 10 | 11 | Here is a simple schema that has `x-` tensions on the `bar` property. 12 | 13 | [source,yaml,subs="attributes"] 14 | ---- 15 | openapi: {var-openapi-version} 16 | # ... 17 | components: 18 | schemas: 19 | Foo: 20 | type: object 21 | properties: 22 | bar: 23 | type: string 24 | x-foo: single 25 | x-bar: 26 | - listA 27 | - listB 28 | ---- 29 | 30 | In general openapi-processor will ignore the `x-` tension properties unless we map the `x-` tensions/values to annotations like this: 31 | 32 | [source,yaml,subs="attributes"] 33 | ---- 34 | openapi-processor-mapping: {var-mapping-version} 35 | options: 36 | # ... 37 | 38 | map: 39 | extensions: 40 | x-foo: single @ io.oap.FooA(value = "any") # <1> 41 | x-bar: 42 | - listA @ io.oap.FooB # <2> 43 | - listB @ io.oap.FooC 44 | ---- 45 | 46 | NOTE: openapi-processor will only recognize *string* values of an extension. It will ignore any other type. 47 | 48 | The mapping allows two variations: 49 | 50 | <1> in case the `x-` tension property has only a single value we can directly map that value to an annotation. 51 | <2> in case the `x-` tension property has a list of values we can map each value to a different annotation. 52 | 53 | 54 | With this mapping the generated Dto class will have the additional annotations on the property. 55 | 56 | [source,java] 57 | ---- 58 | package generated.model; 59 | 60 | import com.fasterxml.jackson.annotation.JsonProperty; 61 | import generated.support.Generated; 62 | import io.oap.FooA; 63 | import io.oap.FooB; 64 | import io.oap.FooC; 65 | 66 | @Generated(value = "openapi-processor-core") 67 | public class Foo { 68 | 69 | @FooA(value = "any") 70 | @FooB 71 | @FooC 72 | @JsonProperty("bar") 73 | private String bar; 74 | 75 | public String getBar() { 76 | return bar; 77 | } 78 | 79 | public void setBar(String bar) { 80 | this.bar = bar; 81 | } 82 | 83 | } 84 | ---- 85 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/mapping/index.adoc: -------------------------------------------------------------------------------- 1 | = Type mapping 2 | 3 | Type mapping is an important feature of the processor and helps it to create the expected code. 4 | 5 | Using type mapping, we can tell the processor to map types (schemas) from an openapi.yaml description to a specific existing java type instead of generating a model class from the source OpenAPI type. 6 | 7 | This can be a type created by us or a type from a framework. For example, to map paging parameters and result to the Spring types `Page<>` & `Pageable`. 8 | 9 | It can also be used to map the OpenAPI `array` type to a different java collection type if the default does not fulfill our needs. 10 | 11 | Type mapping is very flexible. It is possible to define the mapping globally, globally for a specific response or parameter or limited to a specific endpoint or even http method for an endpoint. 12 | 13 | Type mapping also supports (nested) generic parameters to the target type. One level. 14 | 15 | Type mapping works best with named schemas (i.e., schemas `$ref` erenced by their name). 16 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/mapping/logging.adoc: -------------------------------------------------------------------------------- 1 | = log mapping lookup 2 | include::partial$vars.adoc[] 3 | 4 | It is possible to let a processor log all the mapping lookups. It *may* be useful to understand why mapping does not work. 5 | 6 | If a mapping doesn't work, the first step is to check if the processor is applying it or if it ignores it. The mapping logging will report which mappings were selected for an OpenAPI type. 7 | 8 | If the processor doesn't select the mapping, there may be something wrong with the mapping. Maybe a typo. 9 | 10 | In case the processor uses the mapping, but it still doesn't behave as expected, it may be a bug. 11 | 12 | To control the logging, there are two new xref:processor/configuration.adoc#_logging[logging options]. 13 | 14 | [source,yaml,subs="attributes"] 15 | ---- 16 | openapi-processor-mapping: {var-mapping-version} 17 | options: 18 | package-name: io.openapiprocessor.generated 19 | 20 | map: 21 | # ... 22 | 23 | logging: 24 | mapping: true # <1> 25 | mapping-target: stdout #<2> 26 | ---- 27 | 28 | <1> apart from enabling logging of the mapping lookups in the `mapping.yaml` you may want to set the `mapping-target`. 29 | 30 | <2> If set to `logger` the mapping lookup gets logged at `info` level to link:https://www.slf4j.org/[slf4j]. If set to `stdout` the mapping lookup gets written directly to `stdout` without slf4j. 31 | 32 | Enabling the logging will produce many blocks similar to: 33 | 34 | ---- 35 | looking for any type mapping of name: 'foo2' path: GET '/fooA' type: 'array' A 36 | + global 37 | - parameters (type) 38 | - name: foo2 => java.util.List 39 | - name: bar => io.openapiprocessor.Bar1 40 | - name: param @ io.openapiprocessor.ParamAnnotation 41 | - type: Bar @ io.openapiprocessor.ParamAnnotation 42 | + parameters (name) 43 | + name: foo2 => java.util.List 44 | - name: bar => io.openapiprocessor.Bar1 45 | - name: param @ io.openapiprocessor.ParamAnnotation 46 | - type: Bar @ io.openapiprocessor.ParamAnnotation 47 | ---- 48 | 49 | It always starts with a `looking for ..` followed by what it is looking for and the OpenAPI name or type to find, related to which path and its type. Then it lists all mappings checked with their location in the mapping file. 50 | 51 | In this case it looks for *any* mapping of `foo2`. *any* means that it is looking for any mapping, by testing all mappings by priority. More specific mappings have a higher priority and win. 52 | 53 | It did not find a mapping by its type (`array` in this case), but it found a name mapping for `foo2`, indicated by the `+` sign. The mappings that do not match get marked with a `-` sign. 54 | 55 | Here is a snippet from the OpenAPI yaml that is processed here. We have an endpoint `fooA` with a query parameter `foo2` of type `array`. 56 | 57 | [source,yaml] 58 | ---- 59 | /fooA: 60 | get: 61 | summary: foo A summary. 62 | description: foo A endpoint 63 | tags: [foo] 64 | parameters: 65 | - in: query 66 | name: foo1 67 | # ... 68 | - in: query 69 | name: foo2 70 | description: parameter foo2 71 | schema: 72 | type: array 73 | items: 74 | type: string 75 | - in: query 76 | name: bar 77 | # ... 78 | responses: 79 | '200': 80 | # ... 81 | ---- 82 | 83 | The example is a small part of the link:https://github.com/openapi-processor/openapi-processor-base/tree/main/openapi-processor-core/src/testInt/resources/tests/map-many[`map many`] integration test. 84 | 85 | 86 | == maven 87 | 88 | Maven can handle both mapping targets. If the `mapping-target` is set to `logger` it is necessary to enable the mapping logger `io.openapiprocessor.core.converter.mapping` to see any output. 89 | 90 | For example, by running `maven` with: 91 | 92 | ---- 93 | ./mvnw compile -Dorg.slf4j.simpleLogger.log.io.openapiprocessor.core.converter.mapping=info 94 | ---- 95 | 96 | If the `mapping-target` is `stdout` the processor output will be written without the usual log level prefix. 97 | 98 | 99 | === summary 100 | 101 | to enable logging with maven use: 102 | 103 | [source,yaml,subs="attributes"] 104 | ---- 105 | openapi-processor-mapping: {var-mapping-version} 106 | options: 107 | package-name: ... 108 | 109 | map: 110 | # ... 111 | 112 | logging: 113 | mapping: true 114 | mapping-target: stdout 115 | ---- 116 | 117 | to get the simple output, or 118 | 119 | [source,yaml,subs="attributes"] 120 | ---- 121 | openapi-processor-mapping: {var-mapping-version} 122 | options: 123 | package-name: ... 124 | 125 | map: 126 | # ... 127 | 128 | logging: 129 | mapping: true 130 | ---- 131 | 132 | to get the log-level-based output. Remember to enable the logger in this case as described above. 133 | 134 | == gradle 135 | 136 | Gradle requires the `mapping-target` to be `stdout`. Gradle can only globally enable log levels, which is deafening. The best option to log the mapping lookups is simply to write them to `stdout`. 137 | 138 | === summary 139 | 140 | to enable logging with gradle use: 141 | 142 | [source,yaml,subs="attributes"] 143 | ---- 144 | openapi-processor-mapping: {var-mapping-version} 145 | options: 146 | package-name: ... 147 | 148 | map: 149 | # ... 150 | 151 | logging: 152 | mapping: true 153 | mapping-target: stdout 154 | ---- 155 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/mapping/null.adoc: -------------------------------------------------------------------------------- 1 | include::partial$vars.adoc[] 2 | :nullable: https://github.com/OpenAPITools/jackson-databind-nullable 3 | 4 | = Null mapping 5 | 6 | The `null` mapping is used to map (or better wrap) OpenAPI `nullable` properties to link:{nullable}[jackson-databind-nullable]. 7 | 8 | This is useful to implement a link:{https://tools.ietf.org/html/rfc7386}[json merge patch] api that needs to know if a property was not set at all or explicitly set to *"null"* ("null" means to clear the property value). 9 | 10 | After the (standard jackson) binding of the request payload to pojos there is no way to distinguish between `null`, and non-existing properties in Java. That's where link:{nullable}[jackson-databind-nullable] comes into play. 11 | 12 | It provides a wrapper type that distinguishes between `null` and non-existent. 13 | 14 | For example, the `/foo` api endpoint uses the following schema as the request body 15 | 16 | [source,yaml] 17 | ---- 18 | components: 19 | schemas: 20 | 21 | Foo: 22 | description: a Foo 23 | type: object 24 | properties: 25 | bar: 26 | nullable: true 27 | type: string 28 | ---- 29 | 30 | Normally the processor would generate a simple pojo with a `String` property. 31 | 32 | By adding a `null` mapping for the `/foo` endpoint (this does work only on the endpoint level. A global null mapping gets ignored): 33 | 34 | [source,yaml,subs="attributes"] 35 | ---- 36 | openapi-processor-mapping: {var-mapping-version} 37 | 38 | map: 39 | paths: 40 | /foo: 41 | null: org.openapitools.jackson.nullable.JsonNullable 42 | # with initialization: 43 | # null: org.openapitools.jackson.nullable.JsonNullable = JsonNullable.undefined() 44 | 45 | # or even better, limiting it to the patch http method 46 | /bar: 47 | patch: 48 | null: org.openapitools.jackson.nullable.JsonNullable 49 | # with initialization: 50 | # null: org.openapitools.jackson.nullable.JsonNullable = JsonNullable.undefined() 51 | ---- 52 | 53 | it will generate the following code: 54 | 55 | [source,java] 56 | ---- 57 | import com.fasterxml.jackson.annotation.JsonProperty; 58 | import org.openapitools.jackson.nullable.JsonNullable; 59 | 60 | public class Foo { 61 | 62 | @JsonProperty("bar") 63 | private JsonNullable bar; 64 | // with initialization: 65 | // private JsonNullable bar = JsonNullable.undefined(); 66 | 67 | public JsonNullable getBar() { 68 | return bar; 69 | } 70 | 71 | public void setBar(JsonNullable bar) { 72 | this.bar = bar; 73 | } 74 | 75 | } 76 | ---- 77 | 78 | It is now possible to check if a property was explicitly set to `null` or if it was not set at all. 79 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/mapping/package-name.adoc: -------------------------------------------------------------------------------- 1 | = package-name mapping 2 | 3 | The type mapping (v2 and up) configuration allows to reference the target `package-name` in generic parameters using the `+{package-name}+` expression. This makes it possible to adjust the `package-name` without touching the mapping, and it does reduce duplication. 4 | 5 | [source,yaml,subs="attributes"] 6 | ---- 7 | openapi-processor-mapping: {var-mapping-version} 8 | options: 9 | package-name: io.openapiprocessor.generated 10 | 11 | map: 12 | types: 13 | - type: FooPage => org.springframework.data.domain.Page<{package-name}.model.Foo> 14 | ---- 15 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/mapping/response.adoc: -------------------------------------------------------------------------------- 1 | = (global) Response mappings 2 | 3 | Global response mapping will replace the result type of the endpoint in the api description based on its **content type** to the given java type. 4 | 5 | It is defined like below, and it should be added to the `map/responses` section in the mapping.yaml which is a list of global response mappings. 6 | 7 | A single global response mapping can have the following properties: 8 | 9 | [source,yaml] 10 | ---- 11 | - content: {content type} => {target type} 12 | generics: 13 | - {a generic type} 14 | - {another generic type} 15 | ---- 16 | 17 | * **content** is required. 18 | 19 | ** **{content type}** is the content type of the endpoint response that should be replaced by **{target type}**. 20 | 21 | ** **{target type}** is the fully qualified class name of the java type that should be used for all endpoint content types **{content type}**. 22 | 23 | * **generics** defines the list of types that should be used as generic type parameters to the java type given by **{target type}**. 24 | 25 | [CAUTION] 26 | ==== 27 | Since the processor will simply match the content type string, take care that all responses of this content type should really use the same type! 28 | 29 | This is probably only useful for vendor content types. Globally mapping the content type for example of `application/json` does not look like a good idea. 30 | ==== 31 | 32 | == Example 33 | 34 | Given the following (global) response mapping 35 | 36 | [source,yaml] 37 | ---- 38 | map: 39 | 40 | # list of global response mappings, mapped by content type 41 | responses: 42 | - content: application/vnd.something => io.openapiprocessor.Something 43 | ---- 44 | 45 | and an openapi.yaml with multiple endpoints returning their result as content type `application/vnd.something` 46 | 47 | [source,yaml,subs="attributes"] 48 | ---- 49 | openapi: {var-openapi-version} 50 | info: 51 | title: global response content type mapping example 52 | version: 1.0.0 53 | 54 | paths: 55 | /do-something: 56 | get: 57 | responses: 58 | '200': 59 | description: response 60 | content: 61 | application/vnd.something: 62 | schema: 63 | type: string 64 | 65 | /do-something-else: 66 | get: 67 | responses: 68 | '200': 69 | description: response 70 | content: 71 | application/vnd.something: 72 | schema: 73 | type: string 74 | ---- 75 | 76 | the processor will use `io.openapiprocessor.Something` as the java type for **all** responses with the content type `application/vnd.something`. 77 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/mapping/result-status.adoc: -------------------------------------------------------------------------------- 1 | include::partial$links.adoc[] 2 | include::partial$vars.adoc[] 3 | 4 | = Result Status 5 | 6 | [.badge .badge-since]+since 2025.3+ 7 | 8 | The `result-status` configuration controls if the processor adds a `@ResponseStatus` annotation. If enabled (default) it will automatically add a `@ResponseStatus` annotation if the OpenAPI endpoint has a success result code not equal to 200 OK. I.e, 2xx != 200. 9 | 10 | The default response status of Spring Boot is 200, so the processor will not add an unnecessary annotation for 200. 11 | 12 | [NOTE] 13 | ==== 14 | This will conflict with manually added `@ResponseStatus` annotations. 15 | 16 | To keep the old behavior, i.e., no automatically added `@ResponseStatus` annotations, set `result-status: false` on the global mapping level. 17 | ==== 18 | 19 | It is configured by adding it to the mapping section of the configuration file. It is available on all levels, i.e., global, endpoint and endpoint method. 20 | 21 | [source,yaml] 22 | ---- 23 | openapi-processor-mapping: {var-mapping-version} 24 | 25 | options: 26 | # ... 27 | 28 | map: 29 | # result-status: true is the default 30 | # setting it to false on the global level disables it 31 | result-status: false 32 | 33 | paths: 34 | # enable it for a specific endpoint 35 | /foo: 36 | result-status: true 37 | 38 | # ... or for a specific method of an endpoint 39 | #get: 40 | # result-status: true 41 | ---- 42 | 43 | * **result-status** (optional). 44 | 45 | ** `true`: add a `@ResponseStatus` annotation if the response status of a response is a success code not equal to 200. 46 | 47 | ** `false` (default before 2025.3): do not generate any `@ResponseStatus` annotation. 48 | 49 | ** `result-status` is available at the endpoint & http method level. 50 | 51 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/mapping/result-style.adoc: -------------------------------------------------------------------------------- 1 | = Result Style 2 | include::partial$links.adoc[] 3 | 4 | The `result-style` configuration controls how the processor handles the return type of endpoint success and error response types if both are defined in the OpenAPI. 5 | 6 | [source,yaml] 7 | ---- 8 | # mapping.yaml header 9 | # ... 10 | 11 | map: 12 | #result-style: success # use the success result type, this is the default 13 | result-style: all # use an Object return type 14 | 15 | # result-style is available at the endpoint & http method level. 16 | /foo: 17 | #result-style: success 18 | 19 | get: 20 | result-style: success 21 | ---- 22 | 23 | * **result-style** (optional). 24 | 25 | ** `success` ([.badge .badge-since]+default since 2021.5+): generates endpoint methods with the success response type even if it has error responses. This assumes that the errors are reported by exceptions. 26 | 27 | ** `all` (default before 2021.5): generates endpoint methods with an `Object` return type if it has error responses. 28 | 29 | ** [.badge .badge-since]+since 2025.2+ `result-style` is available at the endpoint & http method level. 30 | 31 | 32 | See xref:processor/endpoint-content.adoc[endpoint content types] for a more detailed description. 33 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/mapping/result.adoc: -------------------------------------------------------------------------------- 1 | = Result mapping 2 | include::partial$links.adoc[] 3 | 4 | A link:{spring-responseentity}[`ResponseEntity<>`] allows an endpoint implementation full control of the response. 5 | 6 | Here is a super simple example: 7 | 8 | [source,java] 9 | ---- 10 | public ResponseEntity getFoo() { 11 | return ResponseEntity.ok("foo"); 12 | } 13 | ---- 14 | 15 | To enable a result wrapper set the `result` mapping in the mapping yaml to a fully qualified java type. 16 | 17 | [source,yaml] 18 | ---- 19 | map: 20 | result: org.springframework.http.ResponseEntity 21 | ---- 22 | 23 | NOTE: The processor expects that it takes a single generic parameter. 24 | 25 | Depending on the number of defined response content types the parameter of the `ResponseEntity<>` will be either the java type or the *unknown type*. 26 | 27 | |=== 28 | |responses | ResponseEntity<> 29 | 30 | |one 31 | |`ResponseEntity` 32 | 33 | |multiple 34 | |`ResponseEntity` 35 | |=== 36 | 37 | NOTE: prior to 1.0.0.M13 all results were auto-wrapped with `ResponseEntity<>`. 38 | 39 | 40 | == Limit to Endpoint 41 | 42 | The `result` mapping works as endpoint-specific mapping too. That way it is possible to use the `ResponseEntity<>` only on single endpoints. 43 | 44 | 45 | So a mapping like this: 46 | 47 | [source,yaml] 48 | ---- 49 | map: 50 | 51 | /foo: 52 | result: org.springframework.http.ResponseEntity 53 | ---- 54 | 55 | will only wrap the result of the endpoint `/foo`. 56 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/mapping/schema.adoc: -------------------------------------------------------------------------------- 1 | = (global) Schema mappings 2 | include::partial$vars.adoc[] 3 | 4 | [.badge .badge-since]+since 2025.1+ 5 | 6 | Schema mappings add a new (global) mapping level. They apply only to (object, i.e., dto) schema properties. 7 | 8 | That means the mappings are only used when the source type is used as a property type in a generated dto class. 9 | 10 | [NOTE] 11 | ==== 12 | This is (currently) only supported on the global level and only for xref::mapping/annotation.adoc[]. 13 | ==== 14 | 15 | Schema mappings try to solve the case where a type should be annotated, but *only* if it is used in a generated dto object. Especially it should *not* annotate parameters. 16 | 17 | The example will make this more clear. 18 | 19 | == Example 20 | 21 | In the example OpenAPI below is a year value (<1>) that is used on the response schema and as query parameter. 22 | 23 | [source,yaml,subs="attributes"] 24 | ---- 25 | openapi: {var-openapi-version} 26 | info: 27 | title: schema mapping 28 | version: 1.0.0 29 | 30 | paths: 31 | 32 | /foo: 33 | get: 34 | parameters: 35 | - name: year 36 | description: year parameter 37 | in: query 38 | required: true 39 | schema: 40 | type: integer # <1> 41 | format: year 42 | responses: 43 | '200': 44 | description: the foo result 45 | content: 46 | application/json: 47 | schema: 48 | $ref: '#/components/schemas/Foo' 49 | 50 | components: 51 | schemas: 52 | 53 | Foo: 54 | type: object 55 | properties: 56 | year: 57 | type: integer # <1> 58 | format: year 59 | ---- 60 | 61 | Using a typical mapping the processor will use `java.time.Year` instead of a simple `Integer` type in the generated code. 62 | 63 | [source,yaml,subs="attributes"] 64 | ---- 65 | openapi-processor-mapping: {var-mapping-version} 66 | 67 | options: 68 | package-name: generated 69 | format-code: false 70 | 71 | map: 72 | types: 73 | - type: integer:year => java.time.Year 74 | ---- 75 | 76 | Spring (with Jackson) may not serialize the type in the expected format by default. In case of `java.time.Year` it will be `String` and not a number. 77 | 78 | To change serialization, Jackson provides the `JsonFormat` annotation: 79 | 80 | @JsonFormat(JsonFormat.Shape.NUMBER_INT) 81 | 82 | would change serialization of `java.time.Year` to a number. 83 | 84 | 85 | Adding the annotation mapping for this at the global type level 86 | 87 | [source,yaml,subs="attributes"] 88 | ---- 89 | openapi-processor-mapping: {var-mapping-version} 90 | 91 | options: 92 | package-name: generated 93 | format-code: false 94 | 95 | map: 96 | types: 97 | - type: integer:year => java.time.Year 98 | - type: integer:year @ com.fasterxml.jackson.annotation.JsonFormat(shape = com.fasterxml.jackson.annotation.JsonFormat.Shape.NUMBER_INT) 99 | ---- 100 | 101 | would add the annotation, but not only in the dto, as wanted 102 | 103 | [source,java] 104 | ---- 105 | package generated.model; 106 | 107 | import com.fasterxml.jackson.annotation.JsonFormat; 108 | import com.fasterxml.jackson.annotation.JsonProperty; 109 | import generated.support.Generated; 110 | import java.time.Year; 111 | 112 | @Generated(value = "openapi-processor-core", version = "latest") 113 | public class Foo { 114 | 115 | @JsonFormat(shape = JsonFormat.Shape.NUMBER_INT) 116 | @JsonProperty("year") 117 | private Year year; 118 | 119 | // ... 120 | } 121 | ---- 122 | 123 | but also at the method parameter of the generated interface: 124 | 125 | [source,java] 126 | ---- 127 | package generated.api; 128 | 129 | import com.fasterxml.jackson.annotation.JsonFormat; 130 | import generated.model.Foo; 131 | import generated.support.Generated; 132 | import java.time.Year; 133 | import org.springframework.web.bind.annotation.GetMapping; 134 | import org.springframework.web.bind.annotation.RequestParam; 135 | 136 | @Generated(value = "openapi-processor-core", version = "test") 137 | public interface Api { 138 | 139 | @GetMapping(path = "/foo", produces = {"application/json"}) 140 | Foo getFoo(@RequestParam(name = "year", required = false) @JsonFormat(shape = JsonFormat.Shape.NUMBER_INT) Year year); 141 | 142 | } 143 | ---- 144 | 145 | That is not wanted. To avoid it, the annotation mapping should be added to the new `schemas` mapping level: 146 | 147 | [source,yaml,subs="attributes"] 148 | ---- 149 | openapi-processor-mapping: {var-mapping-version} 150 | 151 | options: 152 | package-name: generated 153 | format-code: false 154 | 155 | map: 156 | types: 157 | - type: integer:year => java.time.Year 158 | 159 | schemas: 160 | - type: integer:year @ com.fasterxml.jackson.annotation.JsonFormat(shape = com.fasterxml.jackson.annotation.JsonFormat.Shape.NUMBER_INT) 161 | ---- 162 | 163 | This tells the processor to add it only to the generated dto class and not to the interface. 164 | 165 | [source,java] 166 | ---- 167 | package generated.api; 168 | 169 | import generated.model.Foo; 170 | import generated.support.Generated; 171 | import java.time.Year; 172 | import org.springframework.web.bind.annotation.GetMapping; 173 | import org.springframework.web.bind.annotation.RequestParam; 174 | 175 | @Generated(value = "openapi-processor-core", version = "test") 176 | public interface Api { 177 | 178 | @GetMapping(path = "/foo", produces = {"application/json"}) 179 | Foo getFoo(@RequestParam(name = "year", required = false) Year year); 180 | 181 | } 182 | ---- 183 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/mapping/single-multi.adoc: -------------------------------------------------------------------------------- 1 | :responseentity: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/ResponseEntity.html 2 | include::partial$vars.adoc[] 3 | 4 | = (global) Single & Multi mapping 5 | 6 | == single & multi wrapper 7 | 8 | //[.badge .badge-since]+since 1.0.0.M13+ 9 | 10 | When using WebFlux we like to wrap certain parameters & results types in reactive types like `Mono<>` or `Flux<>`. 11 | 12 | To achieve this, the processor knows two special mappings: 13 | 14 | * `single`: to wrap a non-array like type (i.e., not a collection) 15 | * `multi`: to wrap an array like type (i.e., a collection) 16 | 17 | 18 | === multi 19 | 20 | [source,yaml] 21 | ---- 22 | map: 23 | multi: reactor.core.publisher.Flux 24 | ---- 25 | 26 | Which will use `Flux<>` as collection wrapper instead of the original java collection type for all 27 | list *responses* (or *parameters*). `multi` does not affect collections in model types. 28 | 29 | === single 30 | 31 | To map non-array like responses to a `Mono<>` set the `single` mapping: 32 | 33 | [source,yaml] 34 | ---- 35 | map: 36 | single: reactor.core.publisher.Mono 37 | ---- 38 | 39 | The processor will now wrap all non-array like response types with the given `single` mapping. 40 | 41 | == endpoint-specific mapping 42 | 43 | it is also possible to configure `single` & `multi` on the xref:mapping/endpoint.adoc[endpoint level]. 44 | 45 | 46 | == single & multi with result mapping 47 | 48 | It is possible to use `single` & `multi` mappings together with the `result` mapping, i.e. `ResponseEntity`. 49 | 50 | `result` will wrap `single` 51 | 52 | [source, java] 53 | ---- 54 | ResponseEntity> 55 | ---- 56 | 57 | and `multi` 58 | 59 | [source, java] 60 | ---- 61 | ResponseEntity> 62 | ---- 63 | 64 | Unfortunately, if you need the reactive result to modify the http response, something like this: 65 | 66 | [source, java] 67 | ---- 68 | // does not work 69 | public ResponseEntity> someEndpoint() { 70 | return someBean.getResult() 71 | .map(r -> ResponseEntity 72 | .ok() 73 | .eTag(r.eTag()) 74 | .body(Mono.just(r))); 75 | } 76 | ---- 77 | 78 | it will not work because the result type of the statement is `Mono>>` and not the expected `ResponseEntity>`. This can be fixed by modifying the `result` mapping to 79 | 80 | [source, yaml,,subs="attributes"] 81 | ---- 82 | openapi-processor-mapping: {var-mapping-version} 83 | 84 | options: 85 | # ... 86 | 87 | map: 88 | # wrap the ResponseEntity with Mono 89 | result: reactor.core.publisher.Mono 90 | 91 | single: reactor.core.publisher.Mono 92 | multi: reactor.core.publisher.Flux 93 | ---- 94 | 95 | which will now generate the endpoint signature as 96 | 97 | [source, java] 98 | ---- 99 | public Mono>> someEndpoint() { 100 | // ... 101 | } 102 | ---- 103 | 104 | and the above code will now work. 105 | 106 | It is recommended to configure this on the endpoint level if you just need this for a few endpoints. 107 | 108 | See also Spring link:{responseentity}[`ResponseEntity`] documentation. 109 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/mapping/structure.adoc: -------------------------------------------------------------------------------- 1 | = type mapping structure 2 | include::partial$links.adoc[] 3 | include::partial$vars.adoc[] 4 | 5 | The type mapping is part of the mapping YAML (see xref:processor/configuration.adoc[Configuration]) and configured under the `map` key. The `map` key contains multiple sections to define the different kinds of type mappings. 6 | 7 | All sections are optional. 8 | 9 | == type mapping structure 10 | 11 | //[.badge .badge-since]+since 1.0.0.M15+ 12 | 13 | [IMPORTANT] 14 | ==== 15 | The mapping file needs the following key on the top-level. Best place is the first line of the `mapping.yaml` file. 16 | 17 | [source,yaml,subs="attributes"] 18 | ---- 19 | openapi-processor-mapping: {var-mapping-version} 20 | ---- 21 | ==== 22 | 23 | The version increases from time to time when openapi-processor requires a new or changed configuration. In case the version changes, it is mentioned in the release notes. 24 | 25 | 26 | === basic mapping 27 | 28 | To map a source type to a destination type, it is using an `=>` arrow as a *mapping operator* instead of individual keywords: 29 | 30 | [source,yaml] 31 | ---- 32 | 33 | some-key: {source type} => {target type} 34 | 35 | ---- 36 | 37 | *source type* is usually a name (or type) from the OpenAPI description and *target type* is usually a java type. 38 | 39 | === full structure 40 | 41 | The full structure of the mapping looks like this (a real mapping file will usually use just a few of the possible keys): 42 | 43 | [source,yaml] 44 | ---- 45 | map: 46 | # global mappings, applies to all paths/endpoints 47 | 48 | # result wrapper, e.g. org.springframework.http.ResponseEntity 49 | result: {target type} 50 | 51 | # result-style: 52 | result-style: {success|all} 53 | 54 | # result status annotation (default true) 55 | result-status: {true|false} 56 | 57 | # single wrapper, e.g. reactor.core.publisher.Mono 58 | single: {target type} 59 | 60 | # multi wrapper, e.g. reactor.core.publisher.Flux 61 | multi: {target type} 62 | 63 | # list of global mappings 64 | types: 65 | # replace a source type with the given target type 66 | - type: {source type} => {target type} 67 | 68 | # add an extra annotation to the source type 69 | - type: {source type} @ {target type} 70 | 71 | # list of global schema mappings 72 | schemas: 73 | # add an extra annotation to the source type, but only on object properties 74 | - type: {source type} @ {target type} 75 | 76 | # list of global parameter mappings 77 | parameters: 78 | - name: {parameter name} => {target type} 79 | 80 | # add a (usually technical) parameter not described in the OpenAPI 81 | - add: {parameter name} => {target type} 82 | 83 | # add an extra annotation to parameters of the source type 84 | - type: {source type} @ {annotation type} 85 | 86 | # list of global content mappings, mapped by content type 87 | responses: 88 | - content: {content type} => {target type} 89 | 90 | # path-, endpoint-, method-specific mappings 91 | paths: 92 | 93 | # a path 94 | /foo: 95 | # exclude endpoint 96 | exclude: {true|false} 97 | 98 | # path-specific result wrapper 99 | result: {target type} 100 | 101 | # result-style: 102 | result-style: {success|all} 103 | 104 | # result status annotation (default true) 105 | result-status: {true|false} 106 | 107 | # path-specific single wrapper 108 | single: {target type} 109 | 110 | # path-specific multi wrapper 111 | multi: {target type} 112 | 113 | # nullable mapping with optional initial value 114 | null: {target type} (= {initializer}) 115 | 116 | # list of path-specific mappings 117 | types: 118 | # replace the source type with the given target type 119 | - from: {source type} => {target type} 120 | 121 | # add an extra annotation to the source type 122 | - type: {source type} @ {target type} 123 | 124 | # list of path-specific parameter mappings 125 | parameters: 126 | - name: {parameter name} => {target type} 127 | 128 | # add a (usually technical) parameter not described in the OpenAPI 129 | - add: {parameter name} => {target type} 130 | 131 | # add an extra annotation to parameters of source type 132 | - type: {source type} @ {annotation type} 133 | 134 | # list of path-specific content mappings, mapped by content type 135 | responses: 136 | - content: {content type} => {target type} 137 | 138 | # limit mapping to a specific http method (all methods are allowed) 139 | get: 140 | # ... allows any of the above keys below the endpoint path (except http methods) 141 | 142 | patch: 143 | # ... 144 | 145 | 146 | ---- 147 | 148 | The structure below `paths` is similar to an OpenAPI YAML file to make it easier to locate a specific mapping. 149 | 150 | == json schema 151 | 152 | Some IDEs support JSON schemas to provide editing support, (auto-completion and validation) for text-based files. To support this, openapi-processor provides JSON schemas for the mapping formats at link:{json-schema-site}[`https://openapiprocessor.io/schemas/mapping/mapping-v++{version}++.json`]. 153 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/processor/bean-validation.adoc: -------------------------------------------------------------------------------- 1 | = Bean Validation 2 | include::partial$links.adoc[] 3 | 4 | == WebFlux 5 | 6 | The position of the `@Valid` annotation on reactive types has changed in 2024.2. Until then the `@Valid` was placed on the generic type of the reactive wrapper, like this: 7 | 8 | [source,java] 9 | ---- 10 | @PostMapping(path = "/foo-flux") 11 | void postFooFlux(@Parameter Flux<@Valid Bar> body); 12 | ---- 13 | 14 | Unfortunately, validation did not happen. Spring needs the `@Valid` annotation on the reactive wrapper to trigger the validation. Therefore `@Valid` is placed by default on the reactive wrapper: 15 | 16 | [source,java] 17 | ---- 18 | @PostMapping(path = "/foo-flux") 19 | void postFooFlux(@Parameter @Valid Flux body); 20 | ---- 21 | 22 | To keep the old behavior see xref:processor/configuration.adoc#_compatibility[compatibility]. 23 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/processor/deprecated.adoc: -------------------------------------------------------------------------------- 1 | = Deprecated items 2 | 3 | OpenAPI allows adding `deprecated: true` at several places. openapi-processor-spring translates them to Java's `@Deprecated` annotation. 4 | 5 | 6 | == deprecated endpoint 7 | 8 | *OpenAPI* 9 | [source,yaml] 10 | ---- 11 | /foo: 12 | get: 13 | deprecated: true # <1> 14 | ---- 15 | 16 | *Java* 17 | [source,java] 18 | ---- 19 | @Deprecated // <2> 20 | @GetMapping("/foo") 21 | /*...*/ getFoo(); 22 | ---- 23 | 24 | <1> a deprecated endpoint 25 | <2> the generated endpoint method with a `@Deprecated` annotation 26 | 27 | == deprecated parameter 28 | 29 | *OpenAPI* 30 | [source,yaml] 31 | ---- 32 | /foo: 33 | get: 34 | parameters: 35 | - name: bar 36 | deprecated: true # <1> 37 | in: query 38 | schema: 39 | type: string 40 | ---- 41 | 42 | *Java* 43 | [source,java] 44 | ---- 45 | @GetMapping("/foo") 46 | /* ... */ getFoo(@Deprecated String bar); // <2> 47 | ---- 48 | 49 | <1> a deprecated parameter 50 | <2> the generated endpoint method with a `@Deprecated` annotation on the `bar` parameter. 51 | 52 | == deprecated schema 53 | 54 | *OpenAPI* 55 | [source,yaml] 56 | ---- 57 | Bar: 58 | type: object 59 | deprecated: true # <1> 60 | properties: 61 | foobar: 62 | type: string 63 | 64 | ---- 65 | 66 | *Java* 67 | [source,java] 68 | ---- 69 | @Deprecated // <2> 70 | public class Bar { 71 | /* ... */ 72 | } 73 | ---- 74 | 75 | <1> a deprecated schema 76 | <2> the generated model class with a `@Deprecated` annotation. 77 | 78 | 79 | == deprecated schema property 80 | 81 | *OpenAPI* 82 | [source,yaml] 83 | ---- 84 | Bar: 85 | type: object 86 | properties: 87 | foobar: 88 | deprecated: true # <1> 89 | type: string 90 | ---- 91 | 92 | *Java* 93 | [source,java] 94 | ---- 95 | public class Bar { 96 | 97 | @Deprecated // <2> 98 | @JsonProperty("foobar") 99 | private String foobar; 100 | 101 | @Deprecated // <2> 102 | public String getFoobar() { 103 | return foobar; 104 | } 105 | 106 | @Deprecated // <2> 107 | public void setFoobar(String foobar) { 108 | this.foobar = foobar; 109 | } 110 | 111 | } 112 | ---- 113 | 114 | <1> a deprecated schema property 115 | <2> the generated model class with `@Deprecated` annotations at the property, getter and setter. (the annotated property may be a bit too much... ) 116 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/processor/endpoint-interface.adoc: -------------------------------------------------------------------------------- 1 | include::partial$vars.adoc[] 2 | 3 | = Endpoint interface grouping 4 | 5 | The processor groups endpoints based on their _first_ tag. Using the `/ping` example again its first (and only) tag is **ping**: 6 | 7 | [source,yaml,subs="attributes"] 8 | ---- 9 | openapi: {var-openapi-version} 10 | info: 11 | title: openapi-processor-spring sample api 12 | version: 1.0.0 13 | 14 | paths: 15 | /ping: 16 | get: 17 | tags: 18 | - ping 19 | summary: returns a single "pong" string. 20 | description: very simple sample endpoint 21 | responses: 22 | '200': 23 | description: pong 24 | content: 25 | text/plain: 26 | schema: 27 | type: string 28 | ---- 29 | 30 | The interface name used for this api will be `PingApi`. `Ping` because `ping` is the tags name and`Api` is a fixed string added to `Ping`. 31 | 32 | In case no tags are available, all endpoints will be added to an `Api` interface. 33 | 34 | The package name gets created from the configurable `packageName` parameter of the processor, and a sub package named `api`. 35 | 36 | If the `packageName` is configured as `io.openapiprocessor` the final package name for the interface is `io.openapiprocessor.api`, and the full class & package name is `io.openapiprocessor.api.PingApi`. 37 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/processor/enums.adoc: -------------------------------------------------------------------------------- 1 | include::partial$links.adoc[] 2 | include::partial$vars.adoc[] 3 | 4 | = Enums 5 | 6 | [#_default] 7 | == default 8 | 9 | By default, openapi-processor creates a java `Enum` from an OpenAPI schema that's using the `enum` keyword.Something like this: 10 | 11 | [source,yaml,title=OpenAPI enum] 12 | ---- 13 | components: 14 | schemas: 15 | Type: 16 | type: string 17 | enum: 18 | - one 19 | - two 20 | ---- 21 | 22 | [source,java,title=generated Java enum] 23 | ---- 24 | package io.openapiprocessor.openapi2.model; 25 | 26 | import com.fasterxml.jackson.annotation.JsonCreator; 27 | import com.fasterxml.jackson.annotation.JsonValue; 28 | import io.openapiprocessor.openapi2.support.Generated; 29 | 30 | @Generated(value = "openapi-processor-spring", version = "latest") 31 | public enum Type { 32 | ONE("one"), 33 | TWO("two"); 34 | 35 | private final String value; 36 | 37 | Type(String value) { 38 | this.value = value; 39 | } 40 | 41 | @JsonValue 42 | public String getValue() { 43 | return this.value; 44 | } 45 | 46 | @JsonCreator 47 | public static Type fromValue(String value) { 48 | for (Type val : Type.values()) { 49 | if (val.value.equals(value)) { 50 | return val; 51 | } 52 | } 53 | throw new IllegalArgumentException(value); 54 | } 55 | } 56 | ---- 57 | 58 | This works without issues if used as part of a request payload. 59 | 60 | Unfortunately, it may cause an error, like the following if the enum is used as a query parameter: 61 | 62 | ==== 63 | Failed to convert value of type `'java.lang.String'` to required type `'io.openapiprocessor.openapi.model.Type'`; Failed to convert from type [`java.lang.String`] to type [`@org.springframework.web.bind.annotation.RequestParam io.openapiprocessor.openapi.model.Type`] for value [`one`] 64 | ==== 65 | 66 | The reason is, that Spring uses `org.springframework.core.convert.converter.Converter` implementations to deserialize parameters and the default enum deserialization expects the incoming string value to exactly match an enum value. 67 | 68 | That is, to successfully convert to the enum value `ONE` the incoming value string has to be `ONE`. It will not accept the lowercase `one`. 69 | 70 | The converter doesn't use jackson, so it won't use the `@JsonCreator` method to convert from the incoming lowercase value to the corresponding enum value. 71 | 72 | To handle this issue we can either use the `enum-type` <<_enum_type_string>> or <<_enum_type_framework>>. 73 | 74 | [#_enum_type_string] 75 | == string 76 | 77 | Do not create Java enum classes for OpenAPI enums and simply use `java.lang.String`. 78 | 79 | [source,yaml,title=mapping.yaml,subs="attributes"] 80 | ---- 81 | openapi-processor-mapping: {var-mapping-version} 82 | 83 | options: 84 | enum-type: string 85 | ---- 86 | 87 | This is an alternative to generating enum classes. It will pass the enum value string as given in the api request to avoid the issue described in the <<_default>> section. 88 | 89 | [source,java,title=api interface] 90 | ---- 91 | public interface FooApi { 92 | 93 | @PostMapping(path = "/foo", produces = {"application/json"}) 94 | Foo postFoo(@RequestParam(name = "enum", required = false) String aEnum); 95 | 96 | } 97 | ---- 98 | 99 | In this simple form it doesn't provide any help to make sure that the incoming values is a valid value as described in the OpenAPI. 100 | 101 | By enabling bean-validation, the processor will generate and use a custom validation annotation to check that the incoming string is an allowed value. 102 | 103 | [source,yaml,title=mapping.yaml,subs="attributes"] 104 | ---- 105 | openapi-processor-mapping: {var-mapping-version} 106 | 107 | options: 108 | bean-validation: jakarta 109 | enum-type: string 110 | ---- 111 | 112 | [source,java,title=api interface with validation] 113 | ---- 114 | public interface FooApi { 115 | 116 | @PostMapping(path = "/foo", produces = {"application/json"}) 117 | Foo postFoo(@RequestParam(name = "enum", required = false) @Values(values = {"one", "two"}) String aEnum); 118 | 119 | } 120 | ---- 121 | 122 | [NOTE] 123 | ==== 124 | make sure you annotate the controller with `@Validated` to run the `@Values` check. 125 | 126 | [source,java,title=api interface with validation] 127 | ---- 128 | @Validated 129 | @RestController 130 | public class ApiController implements FooApi { 131 | // ... 132 | } 133 | ---- 134 | ==== 135 | 136 | [#_enum_type_framework] 137 | == framework 138 | 139 | This is another alternative to the <<_default>> enum classes to avoid the issue described above. 140 | 141 | It creates Java enum classes and a Spring `ConverterFactory` with the name `\{package-name}.spring.StringToEnumConverterFactory` that does create enum converters for all generated enums.The enum converters convert incoming strings to their enum by comparing with the OpenAPI enum values. 142 | 143 | [source,yaml,title=mapping.yaml,subs="attributes"] 144 | ---- 145 | openapi-processor-mapping: {var-mapping-version} 146 | 147 | options: 148 | enum-type: framework 149 | ---- 150 | 151 | To enable the converter factory use a `WebMvcConfigurer` (or `WebFluxConfigurer`) like the code below: 152 | 153 | [source,java,title=enable enum converter factory] 154 | ---- 155 | package io.openapiprocessor.samples; 156 | 157 | import io.openapiprocessor.openapi.spring.StringToEnumConverterFactory; 158 | import org.springframework.context.annotation.Configuration; 159 | import org.springframework.format.FormatterRegistry; 160 | import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; 161 | 162 | @Configuration 163 | public class WebConfig implements WebMvcConfigurer { 164 | 165 | @SuppressWarnings("rawtypes") 166 | @Override 167 | public void addFormatters(FormatterRegistry registry) { 168 | registry.addConverterFactory(new StringToEnumConverterFactory()); 169 | } 170 | } 171 | 172 | ---- 173 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/processor/identifier.adoc: -------------------------------------------------------------------------------- 1 | = Identifiers 2 | 3 | :java-char-start: https://docs.oracle.com/en/java/javase/13/docs/api/java.base/java/lang/Character.html#isJavaIdentifierStart(char) 4 | :java-char-part: https://docs.oracle.com/en/java/javase/13/docs/api/java.base/java/lang/Character.html#isJavaIdentifierPart(char) 5 | :jackson-json-property: https://fasterxml.github.io/jackson-annotations/javadoc/2.8/com/fasterxml/jackson/annotation/JsonProperty.html 6 | 7 | == general 8 | 9 | The processor will map identifiers used in the OpenAPI description (i.e. `yaml` file) to valid Java identifiers. 10 | 11 | The Java identifiers will use camel case, starting with an upper case letter if it is a type name and a lower case letter if it is a variable name. 12 | 13 | Camel case will be produced by detecting word breaks on special characters and using an upper case first letter on the next word. The special characters are: 14 | 15 | * characters that are not allowed in java identifiers, (for example, a `-` (minus)). This is checked 16 | by using link:{java-char-start}[`Character.isJavaIdentifierStart()`] and 17 | link:{java-char-part}[`Character.isJavaIdentifierPart()`] 18 | 19 | * `_` (underscore). The underscore is possible in java identifiers but usually not used apart from enums. 20 | 21 | * a change from letter to number. (see table below). 22 | 23 | given an identifier from the OpenAPI description, the processor would generate the following names for different kinds of identifiers: 24 | 25 | |=== 26 | | | OpenAPI | camel case | variable | class | enum 27 | 28 | | since 2024.2 29 | | foo2Bar 30 | | foo2**B**ar 31 | | foo2**B**ar 32 | | Foo2**B**ar 33 | | FOO2_BAR 34 | 35 | | before 2024.2 36 | | foo2Bar 37 | | foo2bar 38 | | foo2bar 39 | | Foo2bar 40 | | FOO2BAR 41 | |=== 42 | 43 | == model 44 | 45 | For properties of model classes, the properties will be annotated with `@JsonProperty` to provide the mapping from the OpenAPI identifier to the Java identifier. 46 | 47 | [source,java] 48 | ---- 49 | class Example { 50 | 51 | @JsonProperty("foo-bar") 52 | private String fooBar; 53 | 54 | // ... 55 | } 56 | ---- 57 | 58 | [NOTE] 59 | ==== 60 | The `@JsonProperty(...)` annotations are necessary in case a json property name is not a valid java identifier. 61 | 62 | Any JSON identifier gets converted to a valid java identifier. If it differs from the JSON identifier, Spring (jackson) would be unable to correctly map the properties. 63 | 64 | To avoid this issue, the processor adds the annotation. 65 | ==== 66 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/processor/index.adoc: -------------------------------------------------------------------------------- 1 | include::partial$links.adoc[] 2 | include::partial$vars.adoc[] 3 | 4 | = the processor 5 | 6 | == a quick example 7 | 8 | The processor will only generate java interfaces describing the endpoints and the required model(POJOs) classes from an link:{openapi}[OpenAPI] YAML document. 9 | 10 | Let's take a look at a basic example. The following OpenAPI YAML describes a single endpoint. A call to the `/ping` endpoint will simply respond with a plain text string result. 11 | 12 | [source,yaml,subs="attributes"] 13 | ---- 14 | openapi: {var-openapi-version} 15 | info: 16 | title: openapi-processor-spring sample 17 | version: 1.0.0 18 | 19 | paths: 20 | /ping: 21 | get: 22 | tags: 23 | - ping 24 | summary: returns a single "pong" string. 25 | description: very simple sample endpoint. 26 | responses: 27 | '200': 28 | description: pong 29 | content: 30 | text/plain: 31 | schema: 32 | type: string 33 | ---- 34 | 35 | Running the processor on this openapi yaml will create the following java interface: 36 | 37 | [source,java] 38 | ---- 39 | package com.github.hauner.openapi.api; 40 | 41 | import org.springframework.http.ResponseEntity; 42 | import org.springframework.web.bind.annotation.GetMapping; 43 | 44 | public interface PingApi { 45 | 46 | @GetMapping(path = "/ping", produces = {"text/plain"}) 47 | String getPing(); 48 | 49 | } 50 | ---- 51 | 52 | It is now up to you to implement the interface e.g., like this: 53 | 54 | [source,java] 55 | ---- 56 | package com.github.hauner.openapi; 57 | 58 | import com.github.hauner.openapi.api.PingApi; 59 | import org.springframework.http.ResponseEntity; 60 | import org.springframework.stereotype.Controller; 61 | 62 | @RestController 63 | public class PingController implements PingApi { 64 | 65 | @Override 66 | public String getPing () { 67 | return "pong"; 68 | } 69 | 70 | } 71 | ---- 72 | 73 | That's it. 74 | 75 | Of course, the processor is capable of handling more interesting endpoint descriptions. The other sections provide some more detail about what is generated from which input. 76 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/processor/models.adoc: -------------------------------------------------------------------------------- 1 | include::partial$vars.adoc[] 2 | 3 | = Models 4 | 5 | The processor will create simple POJOs classes for the `object` schemas used in the OpenAPI description. A POJO will only have (annotated) properties and get/set methods for its properties. 6 | 7 | The following api describes two endpoints: 8 | 9 | - the first one `/book-inline` defines the response **schema** *inline*. This is interesting because the api does not provide a `schema` name. 10 | 11 | - the second one `/book` references a **named schema**. 12 | 13 | [source,yaml,subs="attributes"] 14 | ---- 15 | openapi: {var-openapi-version} 16 | info: 17 | title: model example 18 | version: 1.0.0 19 | 20 | paths: 21 | /book-inline: 22 | get: 23 | responses: 24 | '200': 25 | description: none 26 | content: 27 | application/json: 28 | schema: 29 | type: object 30 | properties: 31 | isbn: 32 | type: string 33 | title: 34 | type: string 35 | 36 | /book: 37 | get: 38 | responses: 39 | '200': 40 | description: none 41 | content: 42 | application/json: 43 | schema: 44 | $ref: '#/components/schemas/Book' 45 | 46 | components: 47 | schemas: 48 | Book: 49 | type: object 50 | properties: 51 | isbn: 52 | type: string 53 | title: 54 | type: string 55 | ---- 56 | 57 | The second endpoint uses a **schema** with a name, so the processor can simply create a POJO using the name as the Java class name. 58 | 59 | [source,java] 60 | ---- 61 | package generated.model; 62 | 63 | import com.fasterxml.jackson.annotation.JsonProperty; 64 | 65 | public class Book { 66 | 67 | @JsonProperty("isbn") 68 | private String isbn; 69 | 70 | @JsonProperty("title") 71 | private String title; 72 | 73 | public String getIsbn () { 74 | return isbn; 75 | } 76 | 77 | public void setIsbn (String isbn) { 78 | this.isbn = isbn; 79 | } 80 | 81 | public String getTitle () { 82 | return title; 83 | } 84 | 85 | public void setTitle (String title) { 86 | this.title = title; 87 | } 88 | 89 | } 90 | ---- 91 | 92 | The first endpoint has no name, and the processor invents a name based on the endpoint description. In this case the name will be `BookInlineResponse200`. To create a unique name and avoid name collisions with other inline objects, it is created by concatenating: 93 | 94 | * the path of the endpoint, `/book-inline` is mapped to `BookInline` 95 | * `Response`, because it is an inline object described under `responses:` 96 | * `200`, which is the http status code of the response 97 | 98 | which is finally the bulky `BookInlineResponse200`. 99 | 100 | Apart from the generated name it will have exactly the same content (i.e., properties and setter/getter) since the schema description is identical. 101 | 102 | == `readOnly`/`writeOnly` 103 | 104 | Using `readOnly`/`writeOnly` on object schema properties 105 | 106 | [source,yaml] 107 | ---- 108 | Foo: 109 | type: object 110 | properties: 111 | barRead: 112 | readOnly: true 113 | allOf: 114 | - $ref: '#/components/schemas/Bar' 115 | barWrite: 116 | writeOnly: true 117 | allOf: 118 | - $ref: '#/components/schemas/Bar' 119 | ---- 120 | 121 | will translate to `@JsonProperty` annotations with read-only or write-only `access`: 122 | 123 | [source,java] 124 | ---- 125 | public class Foo { 126 | 127 | @JsonProperty(value = "barRead", access = JsonProperty.Access.READ_ONLY) 128 | private Bar barRead; 129 | 130 | @JsonProperty(value = "barWrite", access = JsonProperty.Access.WRITE_ONLY) 131 | private Bar barWrite; 132 | 133 | // .... 134 | } 135 | ---- 136 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/processor/one-of-interface.adoc: -------------------------------------------------------------------------------- 1 | include::partial$vars.adoc[] 2 | 3 | = oneOf 4 | 5 | Generating model classes from an `oneOf` has the challenge to handle a number of usually unrelated objects with different properties. 6 | 7 | Java has no way to define a class member that can have multiple unrelated types (e.g. it can be of class `Foo` or class `Bar`), except using `Object`. 8 | 9 | This is the default behavior of the processor. 10 | 11 | The problem with `Object` is that it doesn't provide any information at all. You have to know (from the OpenAPI) what that `Object` could be. 12 | 13 | To improve usability, the processor is able to generate marker interfaces to provide a bit more information than `Object`. 14 | 15 | [#_marker_interfaces] 16 | == marker interfaces 17 | 18 | [.badge .badge-since]+since 2022.3+ 19 | 20 | Generation of marker interfaces is enabled by setting the `one-of-interface` option to `true` (See xref:processor/configuration.adoc[configuration]). For backward compatibility it is `false` by default. 21 | 22 | [source,yaml,subs="attributes"] 23 | ---- 24 | openapi-processor-mapping: {var-mapping-version} 25 | 26 | options: 27 | one-of-interface: true 28 | ---- 29 | 30 | The processor will now create a marker interface for a `oneOf` of `object` s that is implemented by all `object` s in the `oneOf` list. 31 | 32 | Here is an example. The response is an object `Foo` with a `foo` property that can have the type `Foo` or `Bar`. 33 | 34 | [source,yaml,subs="attributes"] 35 | ---- 36 | openapi: {var-openapi-version} 37 | info: 38 | title: oneOf marker interface 39 | version: 1.0.0 40 | 41 | paths: 42 | /foo: 43 | get: 44 | responses: 45 | '200': 46 | description: oneOf 47 | content: 48 | application/json: 49 | schema: 50 | $ref: '#/components/schemas/Foo' 51 | 52 | components: 53 | schemas: 54 | 55 | Foo: 56 | type: object 57 | properties: 58 | foo: 59 | $ref: '#/components/schemas/FooOneOf' 60 | 61 | FooOneOf: 62 | oneOf: 63 | - $ref: '#/components/schemas/Foo' 64 | - $ref: '#/components/schemas/Bar' 65 | 66 | # omitted description of Foo & Bar 67 | ---- 68 | 69 | The processor generates the class `Foo` as 70 | 71 | [source,java] 72 | ---- 73 | // simplified 74 | public class Foo { 75 | private FooOneOf foo; 76 | } 77 | ---- 78 | 79 | with the type `FooOneOf` instead of `Object`. `FooOneOf` is the marker interface: 80 | 81 | [source,java] 82 | ---- 83 | public interface FooOneOf {} 84 | ---- 85 | 86 | The two model classes `Foo` & `Bar` implement the marker interface: 87 | 88 | [source,java] 89 | ---- 90 | // simplified 91 | public class Foo implements FooOneOf { /* ... */ } 92 | ---- 93 | 94 | [source,java] 95 | ---- 96 | // simplified 97 | public class Bar implements FooOneOf { /* ... */ } 98 | ---- 99 | 100 | Which is better than having `foo` just as `Object`. The marker interface helps to find the possible types of `foo`. 101 | 102 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/processor/parameter.adoc: -------------------------------------------------------------------------------- 1 | = Parameters 2 | 3 | == query parameters 4 | 5 | Query parameters get converted to method parameters with a `@RequestParam()` annotation. 6 | 7 | For example the following query parameter description: 8 | 9 | [source, yaml] 10 | ---- 11 | paths: 12 | /endpoint: 13 | get: 14 | parameters: 15 | - name: foo 16 | description: simple query parameter with default value 17 | in: query 18 | required: false 19 | schema: 20 | type: string 21 | default: 'not set' 22 | responses: 23 | '204': 24 | description: empty 25 | ---- 26 | 27 | will generate the following interface method: 28 | 29 | [source,java] 30 | ---- 31 | @GetMapping(path = "/endpoint") 32 | ResponseEntity getEndpoint(@RequestParam(name = "foo", required = false, defaultValue = "not set") String foo); 33 | ---- 34 | 35 | == path parameters 36 | 37 | Path parameters get converted to method parameters with a `@PathVariable()` annotation. 38 | 39 | For example the following endpoint description: 40 | 41 | 42 | [source, yaml] 43 | ---- 44 | paths: 45 | /endpoint/{foo}: 46 | get: 47 | parameters: 48 | - name: foo 49 | description: a path parameter 50 | in: path 51 | required: true 52 | schema: 53 | type: string 54 | responses: 55 | '200': 56 | description: plain text response 57 | content: 58 | plain/text: 59 | schema: 60 | type: string 61 | ---- 62 | 63 | 64 | will generate the following interface method: 65 | 66 | [source,java] 67 | ---- 68 | @GetMapping( 69 | path = "/path/{foo}", 70 | produces = {"plain/text"}) 71 | String getEndpointFoo(@PathVariable(name = "foo") String foo); 72 | ---- 73 | 74 | == header parameters 75 | 76 | Header parameters use the `@RequestHeader` annotation. 77 | 78 | Given the following endpoint description: 79 | 80 | [source, yaml] 81 | ---- 82 | paths: 83 | /endpoint: 84 | get: 85 | tags: 86 | - endpoint 87 | parameters: 88 | - name: x-foo 89 | description: a header parameter 90 | in: header 91 | required: true 92 | schema: 93 | type: string 94 | responses: 95 | '204': 96 | description: empty 97 | ---- 98 | 99 | the processor generates the following interface method: 100 | 101 | [source,java] 102 | ---- 103 | @GetMapping(path = "/endpoint") 104 | void getEndpoint(@RequestHeader(name = "x-foo") String xFoo); 105 | ---- 106 | 107 | == cookie parameters 108 | 109 | Cookie parameters use the `@CookieValue` annotation. 110 | 111 | Given the following endpoint description: 112 | 113 | 114 | [source, yaml] 115 | ---- 116 | paths: 117 | /endpoint: 118 | 119 | get: 120 | parameters: 121 | - name: foo 122 | description: a cookie 123 | in: cookie 124 | required: true 125 | schema: 126 | type: string 127 | responses: 128 | '204': 129 | description: empty 130 | ---- 131 | 132 | the processor generates the following interface method: 133 | 134 | [source,java] 135 | ---- 136 | @GetMapping(path = "/endpoint") 137 | void getEndpoint(@CookieValue(name = "foo") String foo); 138 | ---- 139 | 140 | == additional parameters 141 | 142 | Sometimes it may be useful to have an additional endpoint parameter that is required for the implementation, but it should not be part of the OpenAPI description. Think of an `HttpServletRequest` or a custom Spring `HandlerMethodArgumentResolver`. 143 | 144 | Such an additional parameter can be described in the mappings as an endpoint parameter. Assuming there is an endpoint `/foo` defined in the OpenAPI interfaces it is possible to add extra parameters by using an `add ` `as ` entry. 145 | 146 | [source, yaml] 147 | ---- 148 | map: 149 | paths: 150 | /foo: 151 | 152 | parameters: 153 | - add: request => javax.servlet.http.HttpServletRequest 154 | ---- 155 | 156 | will add the *additional* parameter to the generated interface method. 157 | 158 | [source, java] 159 | ---- 160 | @GetMapping(path = "/foo") 161 | ResponseEntity getFoo(@RequestParam(name = "bar") String bar, HttpServletRequest request); 162 | ---- 163 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/processor/requestbody.adoc: -------------------------------------------------------------------------------- 1 | include::partial$vars.adoc[] 2 | 3 | = Request Body 4 | 5 | This OpenAPI describes an endpoint with `requestBody`: 6 | 7 | [source,yaml,subs="attributes"] 8 | ---- 9 | openapi: {var-openapi-version} 10 | info: 11 | title: request body 12 | version: 1.0.0 13 | 14 | paths: 15 | /book: 16 | post: 17 | requestBody: 18 | content: 19 | application/json: 20 | schema: 21 | $ref: '#/components/schemas/Book' 22 | required: true 23 | responses: 24 | '201': 25 | description: created book 26 | content: 27 | application/json: 28 | schema: 29 | $ref: '#/components/schemas/Book' 30 | 31 | components: 32 | schemas: 33 | Book: 34 | type: object 35 | properties: 36 | isbn: 37 | type: string 38 | title: 39 | type: string 40 | ---- 41 | 42 | that the processor will convert into the interface: 43 | 44 | [source,java] 45 | ---- 46 | package generated.api; 47 | 48 | import generated.model.Book; 49 | import org.springframework.http.ResponseEntity; 50 | import org.springframework.web.bind.annotation.PostMapping; 51 | import org.springframework.web.bind.annotation.RequestBody; 52 | 53 | public interface Api { 54 | 55 | @PostMapping(path = "/book", consumes = {"application/json"}, produces = {"application/json"}) 56 | ResponseEntity postBook(@RequestBody Book body); 57 | 58 | } 59 | ---- 60 | 61 | and a `Book` pojo. 62 | 63 | == multipart/form-data 64 | 65 | For file uploads, where the `content` of the `requestBody` is `multipart/form-data`, the resulting code looks a bit different and requires a specific type mapping. 66 | 67 | The file upload in OpenAPI is described like this: 68 | 69 | [source,yaml] 70 | ---- 71 | /file-upload: 72 | summary: upload a file 73 | post: 74 | requestBody: 75 | content: 76 | multipart/form-data: 77 | schema: 78 | type: object 79 | properties: 80 | file: 81 | type: string 82 | format: binary 83 | ---- 84 | 85 | (See also xref:oap:openapi:file_upload.adoc[OpenAPI - describing a file upload endpoint]) 86 | 87 | The `schema` must be of type `object` defining a property for each part in the multipart body. 88 | 89 | Instead of generating a `@RequestBody` parameter for the `object` schema the processor creates a parameter for each property of the object annotated with `@RequestParam`: 90 | 91 | [source,java] 92 | ---- 93 | package generated.api; 94 | 95 | import org.springframework.http.ResponseEntity; 96 | import org.springframework.web.bind.annotation.PostMapping; 97 | import org.springframework.web.bind.annotation.RequestParam; 98 | import org.springframework.web.multipart.MultipartFile; 99 | 100 | public interface Api { 101 | 102 | @PostMapping(path = "/file-upload") 103 | ResponseEntity postFileUpload(@RequestParam(name = "file") MultipartFile file); 104 | 105 | } 106 | ---- 107 | 108 | Note that the `file` property (type `string` format `binary`) is mapped to Springs `MultipartFile` type. 109 | 110 | The processor does not have a default mapping for the `binary` format so to get the code above, we have to configure it in the type mapping YAML: 111 | 112 | [source,yaml] 113 | ---- 114 | map: 115 | paths: 116 | /file-upload: 117 | types: 118 | - type: string:binary => org.springframework.web.multipart.MultipartFile 119 | ---- 120 | 121 | To upload multiple files we can define the body `object` with an `array` property: 122 | 123 | [source,yaml] 124 | ---- 125 | type: object 126 | properties: 127 | files: 128 | type: array 129 | items: 130 | type: string 131 | format: binary 132 | ---- 133 | 134 | to get the following code: 135 | 136 | [source,java] 137 | ---- 138 | @PostMapping(path = "/file-upload") 139 | ResponseEntity postFileUpload(@RequestParam(name = "files") MultipartFile[] files); 140 | ---- 141 | 142 | === multi-part encoding 143 | 144 | [.badge .badge-since]+since 2021.4+ 145 | 146 | the parsing step extracts the `encoding/contentType` of a multipart content. This allows a processor to consider the encoding content type when selecting the annotation for the part. 147 | 148 | the Spring processor uses this to select between `@RequestPart` and `@RequestParam` annotation. If an `encoding/contentType` is available it will use `@RequestPart`, if no `encoding/contentType` is available it will use `@RequestParam`. 149 | 150 | [source,yaml,subs="attributes"] 151 | ---- 152 | openapi: {var-openapi-version} 153 | info: 154 | title: params-request-body-multipart 155 | version: 1.0.0 156 | 157 | paths: 158 | /multipart: 159 | post: 160 | requestBody: 161 | required: true 162 | content: 163 | multipart/form-data: 164 | schema: 165 | type: object 166 | properties: 167 | file: 168 | type: string 169 | format: binary 170 | json: 171 | type: object 172 | properties: 173 | foo: 174 | type: string 175 | bar: 176 | type: string 177 | encoding: 178 | file: 179 | contentType: application/octet-stream 180 | json: 181 | contentType: application/json 182 | responses: 183 | '204': 184 | description: empty 185 | ---- 186 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/processor/response.adoc: -------------------------------------------------------------------------------- 1 | include::partial$links.adoc[] 2 | 3 | = Responses 4 | 5 | All generated endpoints return their java result type by default. This may be to simple for some endpoint implementations. 6 | 7 | There are two mappings available to customize the result type: 8 | 9 | . If, for example, the response needs some customization, we would like to use a `ResponseEntity<>` to modify it. This is possible using the *result* mapping. 10 | 11 | . Another case is WebFlux, where we need the result to be either a `Flux<>` in case of an array type, or a `Mono<>` in case it is not an array type. This is possible using the *single* mapping. 12 | 13 | NOTE: *single* and *result* mappings are _independent_, i.e., both mappings can be used at the same time. For example, it is possible to create a `Mono<>` result and modify the response using `ResponseEntity<>`. The response type would be `ResponseEntity>`. 14 | 15 | == result wrapper 16 | 17 | //[.badge .badge-since]+since 1.0.0.M13+ 18 | 19 | A link:{spring-responseentity}[`ResponseEntity<>`] allows an endpoint implementation full control of the response. 20 | 21 | Here is a super simple example: 22 | 23 | [source,java] 24 | ---- 25 | public ResponseEntity getFoo() { 26 | return ResponseEntity.ok("foo"); 27 | } 28 | ---- 29 | 30 | To enable a result wrapper set the `result` mapping in the mapping yaml to a fully qualified java type. 31 | 32 | [source,yaml] 33 | ---- 34 | map: 35 | result: org.springframework.http.ResponseEntity 36 | ---- 37 | 38 | NOTE: The processor expects that it takes a single generic parameter. 39 | 40 | Depending on the number of defined response content types the parameter of the `ResponseEntity<>` will be either the java type or the *unknown type*. 41 | 42 | |=== 43 | |responses | ResponseEntity<> 44 | 45 | |one 46 | |`ResponseEntity` 47 | 48 | |multiple 49 | |`ResponseEntity` 50 | |=== 51 | 52 | NOTE: prior to 1.0.0.M13 all results were auto-wrapped with `ResponseEntity<>`. 53 | 54 | See also xref:mapping/result.adoc[result mapping]. 55 | 56 | == single & multi wrapper 57 | 58 | //[.badge .badge-since]+since 1.0.0.M13+ 59 | 60 | When using WebFlux, we want to wrap certain parameters and results types in reactive types like `Mono<>` or `Flux<>`. 61 | 62 | To achieve this, the processor knows two special mappings: 63 | 64 | * single: to wrap non-array types (i.e., not a collection) 65 | * multi: to wrap array types (i.e., a collection) 66 | 67 | === multi 68 | 69 | [source,yaml] 70 | ---- 71 | map: 72 | multi: reactor.core.publisher.Flux 73 | ---- 74 | 75 | Which will use `Flux<>` as collection wrapper instead of the original java collection type for all list *responses* (or *parameters*). The `multi` does not affect collections in model types. 76 | 77 | === single 78 | 79 | To map non-array like responses to a `Mono<>` set the `single` mapping: 80 | 81 | [source,yaml] 82 | ---- 83 | map: 84 | single: reactor.core.publisher.Mono 85 | ---- 86 | 87 | The processor will now wrap all non-array like response types with the given `single` mapping. 88 | -------------------------------------------------------------------------------- /docs/modules/ROOT/pages/processor/server-url.adoc: -------------------------------------------------------------------------------- 1 | include::partial$vars.adoc[] 2 | 3 | = Server Url 4 | 5 | == base path 6 | 7 | OpenAPI offers a `servers` section to describe the server urls available to access the api. 8 | 9 | openapi-processor has (simple) support to generate a resource property file with the path of a server url. The processor will resolve all variables with their default value and extracts the urls path. 10 | 11 | Given an OpenAPI description with a servers key: 12 | 13 | [source,yaml,subs="attributes"] 14 | ---- 15 | openapi: {var-openapi-version} 16 | info: 17 | title: server url example 18 | version: 1.0.0 19 | 20 | servers: 21 | - url: "https://openapiprocessor.io/{path}" 22 | variables: 23 | path: 24 | default: api 25 | # ... 26 | ---- 27 | 28 | and a mapping 29 | 30 | [source,yaml,subs="attributes"] 31 | ---- 32 | openapi-processor-mapping: {var-mapping-version} 33 | options: 34 | server-url: true 35 | ---- 36 | 37 | it will generate a simple resource properties file with a single property `openapi.base.path`: 38 | 39 | [source,properties] 40 | ---- 41 | # api.properties 42 | openapi.base.path = /api 43 | ---- 44 | 45 | If there are multiple servers, its index can be used to select it: 46 | 47 | [source,yaml,subs="attributes"] 48 | ---- 49 | openapi-processor-mapping: {var-mapping-version} 50 | options: 51 | server-url: 0 # same as true 52 | # server-url: 1 53 | # server-url: 2 54 | ---- 55 | 56 | == using the generated properties file 57 | 58 | The properties file is used to configure Spring Boots `server.servlet.context-path`: 59 | 60 | [source,properties] 61 | ---- 62 | # application.properties 63 | 64 | #spring.config.import = api.properties 65 | server.servlet.context-path=${openapi.base.path} 66 | ---- 67 | 68 | While it is possible to import the generated properties file, it is probably better to simply use the generated properties as an additional profile. 69 | 70 | == name of the properties file 71 | 72 | The default name of the generated properties file is `api.properties`. it is configurable using the xref:processor/configuration.adoc#_basepath_propertiesname[server-url:properties-name] option. 73 | 74 | [source,yaml,subs="attributes"] 75 | ---- 76 | openapi-processor-mapping: {var-mapping-version} 77 | 78 | options: 79 | server-url: 80 | properties-name: base-path.properties 81 | ---- 82 | 83 | == destination directory 84 | 85 | By default, the processor will generate the java package structure directly below the `targetDir`. To create the resource file, it needs a second (`resources`) directory as target directory. 86 | 87 | This is handled by a new xref:processor/configuration.adoc#_target_dirlayout[option] to set the layout of the `targetDir`. Setting it to `standard` 88 | 89 | [source,yaml,subs="attributes"] 90 | ---- 91 | openapi-processor-mapping: {var-mapping-version} 92 | 93 | options: 94 | target-dir: 95 | layout: standard 96 | ---- 97 | 98 | will create the following directory layout: 99 | 100 | targetDir 101 | +--- java 102 | | \--- io 103 | | \--- openapiprocessor 104 | | +--- api 105 | | \--- model 106 | \--- resources 107 | 108 | and write the properties file to the `resources` directory. 109 | 110 | [NOTE] 111 | To have a destination directory for generating the resource file, setting `server-url` to a truthy value will *automatically* enable the xref:processor/configuration.adoc#_target_dirlayout[`standard`] target dir layout. It is still recommended to set it explicitly for documentation. 112 | 113 | == build configuration 114 | 115 | The consequence of the new layout is that for compilation it is necessary to update the configuration of the sources and resources directories in the build configuration. 116 | 117 | For example, with gradle the `sourceSets` configuration would change to something like this: 118 | 119 | [source,kotlin] 120 | ---- 121 | sourceSets { 122 | main { 123 | java { 124 | // add generated files 125 | srcDir(layout.buildDirectory.dir("openapi/java")) 126 | } 127 | resources { 128 | // add generated resources 129 | srcDir(layout.buildDirectory.dir("openapi/resources")) 130 | } 131 | } 132 | } 133 | ---- 134 | 135 | == options summary 136 | 137 | Here is a short snippet of a `mapping.yaml` as a summary of the options used to configure the base path property file generation. 138 | 139 | [source,yaml,subs="attributes"] 140 | ---- 141 | openapi-processor-mapping: {var-mapping-version} 142 | 143 | options: 144 | # ... other options 145 | 146 | target-dir: 147 | layout: standard 148 | 149 | base-path: 150 | server-url: 0 151 | properties-name: base-path.properties 152 | ---- 153 | -------------------------------------------------------------------------------- /docs/modules/ROOT/partials/links.adoc: -------------------------------------------------------------------------------- 1 | // 2 | // external openapi-processor links 3 | // 4 | :badge-license: https://img.shields.io/badge/License-Apache%202.0-blue.svg?labelColor=313A42 5 | :badge-ci: https://github.com/openapi-processor/openapi-processor-spring/workflows/build/badge.svg 6 | :oaps-ci: https://github.com/openapi-processor/openapi-processor-spring/actions?query=workflow%3Abuild 7 | :oaps-issues: https://github.com/openapi-processor/openapi-processor-spring/issues 8 | :oaps-inttests: https://github.com/openapi-processor/openapi-processor-spring/tree/master/src/testInt/resources/tests 9 | :oaps-license: https://github.com/openapi-processor/openapi-processor-spring/blob/master/LICENSE 10 | :oaps-releases: https://github.com/openapi-processor/openapi-processor-spring/releases 11 | :oaps-bintray: https://bintray.com/openapi-processor/openapi-processor 12 | :oap-gradle: https://github.com/openapi-processor/openapi-processor-gradle 13 | :oap-playground: https://playground.openapiprocessor.io 14 | :oap-samples: https://github.com/openapi-processor/openapi-processor-samples 15 | :oap-central: https://search.maven.org/search?q=io.openapiprocessor 16 | :badge-central: https://img.shields.io/maven-central/v/io.openapiprocessor/openapi-processor-spring?label=Maven%20Central 17 | :oapc-inttests: https://github.com/openapi-processor/openapi-processor-core/tree/master/src/testInt/resources/tests 18 | :json-schema: https://github.com/openapi-processor/openapi-processor-core/blob/master/src/main/resources/mapping/v2/mapping.yaml.json 19 | :json-schema-site: https://openapiprocessor.io/schemas/mapping/mapping-{var-mapping-version}.json 20 | 21 | // 22 | // other external links 23 | // 24 | :openapi: https://www.openapis.org/ 25 | :openapi-spec: https://github.com/OAI/OpenAPI-Specification 26 | :openapi-spec-types: https://github.com/OAI/OpenAPI-Specification/blob/master/versions/3.0.2.md#dataTypes 27 | :openapi-generator: https://openapi-generator.tech/ 28 | :openapi-tools: https://openapi.tools/ 29 | 30 | :swagger-parser: https://github.com/swagger-api/swagger-parser 31 | :openapi4j: https://github.com/openapi4j/openapi4j 32 | :openapi-parser: https://github.com/openapi-processor/openapi-parser 33 | 34 | :springboot: https://spring.io/projects/spring-boot 35 | :license: http://www.apache.org/licenses/LICENSE-2.0.txt 36 | :bean-validation: https://beanvalidation.org/ 37 | 38 | :spring-responseentity: https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/http/ResponseEntity.html 39 | -------------------------------------------------------------------------------- /docs/modules/ROOT/partials/vars.adoc: -------------------------------------------------------------------------------- 1 | :var-mapping-version: v13 2 | :var-openapi-version: 3.1.0 3 | -------------------------------------------------------------------------------- /gradle.properties: -------------------------------------------------------------------------------- 1 | projectVersion=2025.3-SNAPSHOT 2 | projectGroupId=io.openapiprocessor 3 | 4 | projectUrl=https://openapiprocessor.io 5 | projectTitle=openapi-processor 6 | projectDesc=OpenAPI Processor Spring 7 | 8 | projectGithubRepo=openapi-processor/openapi-processor-spring 9 | 10 | kotlin.jvm.target.validation.mode=ignore 11 | -------------------------------------------------------------------------------- /gradle/libs.versions.toml: -------------------------------------------------------------------------------- 1 | [versions] 2 | kotlin = "2.1.21" 3 | build-jdk = "11" 4 | test-jdk = "17" 5 | 6 | api = "2024.2" 7 | base = "2025.3-SNAPSHOT" 8 | 9 | junit = "5.9.3" 10 | jacoco = "0.8.7" 11 | 12 | spring-web = "5.3.39" 13 | spring-data = "2.7.18" 14 | 15 | # although testInt builds with jdk 17 it is complaing that the dependecies need jdk 17 16 | #spring-web = "6.2.7" 17 | #spring-data = "3.5.0" 18 | 19 | [libraries] 20 | openapi-processor-api = { module = "io.openapiprocessor:openapi-processor-api", version.ref = "api" } 21 | openapi-processor-test-base = { module = "io.openapiprocessor:openapi-processor-test", version.ref = "base" } 22 | openapi-processor-test-api = { module = "io.openapiprocessor:openapi-processor-test-api", version.ref = "base" } 23 | openapi-processor-core = { module = "io.openapiprocessor:openapi-processor-core", version.ref = "base" } 24 | openapi-processor-parser-api = { module = "io.openapiprocessor:openapi-processor-core-parser-api", version.ref = "base" } 25 | openapi-processor-parser-swagger = { module = "io.openapiprocessor:openapi-processor-core-parser-swagger", version.ref = "base" } 26 | openapi-processor-parser-openapi4j = { module ="io.openapiprocessor:openapi-processor-core-parser-openapi4j", version.ref = "base" } 27 | 28 | spring-web = { module ="org.springframework:spring-web", version.ref = "spring-web" } 29 | spring-data = { module ="org.springframework.data:spring-data-commons", version.ref = "spring-data" } 30 | 31 | checker = "org.checkerframework:checker:3.49.3" 32 | jimfs = "com.google.jimfs:jimfs:1.3.0" 33 | slf4j = "org.slf4j:slf4j-api:2.0.17" 34 | logback = "ch.qos.logback:logback-classic:1.5.18" 35 | 36 | kotest-bom = "io.kotest:kotest-bom:5.9.1" 37 | kotest-runner = { module = "io.kotest:kotest-runner-junit5" } 38 | kotest-datatest = { module = "io.kotest:kotest-framework-datatest" } 39 | mockk = "io.mockk:mockk:1.14.2" 40 | 41 | groovy-bom = "org.apache.groovy:groovy-bom:4.0.27" 42 | spock = "org.spockframework:spock-core:2.3-groovy-4.0" 43 | 44 | plugin-kotlin = { module = "org.jetbrains.kotlin:kotlin-gradle-plugin", version.ref = "kotlin" } 45 | plugin-checker = "org.checkerframework:checkerframework-gradle-plugin:0.6.5" 46 | plugin-outdated = "com.github.ben-manes:gradle-versions-plugin:0.52.0" 47 | 48 | [plugins] 49 | kotlin = { id = "org.jetbrains.kotlin.jvm", version.ref = "kotlin" } 50 | nexus = { id = "io.github.gradle-nexus.publish-plugin", version = "2.0.0" } 51 | jacoco = { id = "org.barfuin.gradle.jacocolog", version = "3.1.0" } 52 | versions = { id = "com.github.ben-manes.versions", version = "0.52.0" } 53 | sonar = { id = "org.sonarqube", version = "6.2.0.5505" } 54 | -------------------------------------------------------------------------------- /gradle/publishing.gradle: -------------------------------------------------------------------------------- 1 | configure(project.rootProject) { 2 | ext { 3 | publishUser = getBuildProperty ('PUBLISH_USER') 4 | publishKey = getBuildProperty ('PUBLISH_KEY') 5 | 6 | signKey = getBuildProperty ('SIGN_KEY') 7 | signPwd = getBuildProperty ('SIGN_PWD') 8 | 9 | isReleaseVersion = !version.endsWith("SNAPSHOT") 10 | } 11 | } 12 | 13 | publishing { 14 | publications { 15 | processorSpring (MavenPublication) { 16 | groupId = project.group 17 | artifactId = project.name 18 | version = project.version 19 | 20 | from components.java 21 | 22 | pom { 23 | name = project.projectTitle 24 | description = project.projectDesc 25 | url = project.projectUrl 26 | 27 | licenses { 28 | license { 29 | name = 'The Apache Software License, Version 2.0' 30 | url = 'https://www.apache.org/licenses/LICENSE-2.0.txt' 31 | distribution = "repo" 32 | } 33 | } 34 | 35 | developers { 36 | developer { 37 | id = 'hauner' 38 | name = 'Martin Hauner' 39 | } 40 | } 41 | 42 | scm { 43 | url = "https://github.com/${project.projectGithubRepo}".toString () 44 | } 45 | } 46 | 47 | } 48 | } 49 | 50 | repositories { 51 | maven { 52 | def releaseRepository = uri("https://oss.sonatype.org/service/local/staging/deploy/maven2") 53 | def snapshotRepository = uri("https://oss.sonatype.org/content/repositories/snapshots") 54 | url = project.isReleaseVersion ? releaseRepository : snapshotRepository 55 | 56 | credentials { 57 | username = publishUser 58 | password = publishKey 59 | } 60 | } 61 | } 62 | } 63 | 64 | signing { 65 | setRequired({ gradle.taskGraph.hasTask("${project.path}:publishToSonatype") }) 66 | 67 | useInMemoryPgpKeys(signKey, signPwd) 68 | 69 | signing { 70 | sign publishing.publications.processorSpring 71 | } 72 | } 73 | 74 | nexusPublishing { 75 | repositories { 76 | sonatype() { 77 | username = publishUser 78 | password = publishKey 79 | } 80 | } 81 | } 82 | 83 | // helper 84 | 85 | String getBuildProperty(String property) { 86 | project.findProperty (property) ?: System.getenv (property) ?: 'n/a' 87 | } 88 | -------------------------------------------------------------------------------- /gradle/publishing.tasks.gradle.kts: -------------------------------------------------------------------------------- 1 | registerPublishTask("snapshot") { hasSnapshotVersion() } 2 | registerPublishTask("release") { !hasSnapshotVersion() } 3 | 4 | fun registerPublishTask(type: String, condition: () -> Boolean) { 5 | tasks.register("publish${type.capitalize()}") { 6 | group = "publishing" 7 | description = "Publish only if the current version is a ${type.capitalize()} version" 8 | 9 | if (condition()) { 10 | println("enabling $type publishing") 11 | dependsOn(tasks.withType()) 12 | } else { 13 | doLast { 14 | println("skipping - no $type version") 15 | } 16 | } 17 | } 18 | } 19 | 20 | fun hasSnapshotVersion(): Boolean { 21 | return version.toString().endsWith("-SNAPSHOT") 22 | } 23 | -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openapi-processor/openapi-processor-spring/6687d7b56d1921110fc2b1cea70279877ae18141/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.14.1-bin.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /images/openapi-processor-spring@1280x200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openapi-processor/openapi-processor-spring/6687d7b56d1921110fc2b1cea70279877ae18141/images/openapi-processor-spring@1280x200.png -------------------------------------------------------------------------------- /images/openapi-processor-spring@1280x640.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openapi-processor/openapi-processor-spring/6687d7b56d1921110fc2b1cea70279877ae18141/images/openapi-processor-spring@1280x640.png -------------------------------------------------------------------------------- /images/openapi-processor-spring@400x200.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/openapi-processor/openapi-processor-spring/6687d7b56d1921110fc2b1cea70279877ae18141/images/openapi-processor-spring@400x200.png -------------------------------------------------------------------------------- /justfile: -------------------------------------------------------------------------------- 1 | default: 2 | @just --list --unsorted 3 | 4 | # update gradle wrapper 5 | wrapper version="8.14.1": 6 | ./gradlew wrapper --gradle-version={{version}} 7 | -------------------------------------------------------------------------------- /settings.gradle: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("com.gradle.enterprise") version("3.14.1") 3 | } 4 | 5 | gradleEnterprise { 6 | if (System.getenv("CI") != null) { 7 | buildScan { 8 | publishAlways() 9 | termsOfServiceUrl = "https://gradle.com/terms-of-service" 10 | termsOfServiceAgree = "yes" 11 | } 12 | } 13 | } 14 | 15 | rootProject.name = 'openapi-processor-spring' 16 | 17 | //includeBuild '../openapi-processor-base' 18 | -------------------------------------------------------------------------------- /src/main/kotlin/io/openapiprocessor/spring/model/parameters/MultipartParameter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2021 https://github.com/openapi-processor/openapi-processor-spring 3 | * PDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package io.openapiprocessor.spring.model.parameters 7 | 8 | import io.openapiprocessor.core.model.datatypes.DataType 9 | import io.openapiprocessor.core.model.parameters.MultipartParameter as CoreMultipartParameter 10 | 11 | class MultipartParameter( 12 | name: String, 13 | dataType: DataType, 14 | required: Boolean = false, 15 | deprecated: Boolean = false, 16 | description: String? = null, 17 | val contentType: String? = null 18 | ): CoreMultipartParameter(name, dataType, required, deprecated, description) 19 | -------------------------------------------------------------------------------- /src/main/kotlin/io/openapiprocessor/spring/model/parameters/QueryParameter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 https://github.com/openapi-processor/openapi-processor-spring 3 | * PDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package io.openapiprocessor.spring.model.parameters 7 | 8 | import io.openapiprocessor.core.model.datatypes.* 9 | import io.openapiprocessor.core.model.parameters.ParameterBase 10 | 11 | val Maps = listOf( 12 | Map::class.java.name, 13 | "org.springframework.util.MultiValueMap" 14 | ) 15 | 16 | /** 17 | * OpenAPI query parameter. 18 | */ 19 | class QueryParameter( 20 | name: String, 21 | dataType: DataType, 22 | required: Boolean = false, 23 | deprecated: Boolean = false, 24 | description: String? = null 25 | ): ParameterBase(name, dataType, required, deprecated, description) { 26 | 27 | /** 28 | * controls if a parameter should have a {@code @RequestParam} annotation. 29 | */ 30 | override val withAnnotation: Boolean 31 | get() { 32 | // Map should be annotated 33 | if (isMappedMap) { 34 | return true 35 | } 36 | 37 | // Pojo's should NOT be annotated 38 | if (dataType is ObjectDataType) { 39 | return false 40 | } 41 | 42 | // Mapped should NOT be annotated if it was object schema 43 | // Mapped should be annotated if it was a simple schema 44 | if (isMappedObject) { 45 | return false 46 | } 47 | 48 | return true 49 | } 50 | 51 | /** 52 | * controls if a {@code @RequestParam} should have any parameters. 53 | */ 54 | override val withParameters: Boolean 55 | get() { 56 | // Map should not have parameters 57 | if (isMappedMap) { 58 | return false 59 | } 60 | 61 | // Pojo should not have parameters 62 | if (dataType is ObjectDataType) { 63 | return false 64 | } 65 | 66 | return true 67 | } 68 | 69 | private val isMappedMap: Boolean 70 | get() { 71 | if (dataType !is MappedDataType) { 72 | return false 73 | } 74 | 75 | val type = dataType.getImports().first() 76 | return Maps.contains(type) 77 | } 78 | 79 | private val isMappedObject: Boolean 80 | get() { 81 | if (dataType !is SourceDataType) { 82 | return false 83 | } 84 | 85 | val mapped = dataType as SourceDataType 86 | return mapped.sourceDataType is ObjectDataType 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /src/main/kotlin/io/openapiprocessor/spring/processor/ProcessingException.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 https://github.com/openapi-processor/openapi-processor-spring 3 | * PDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package io.openapiprocessor.spring.processor 7 | 8 | class ProcessingException(ex: Exception): RuntimeException(ex) { 9 | 10 | override val message: String 11 | get() = "failed to run openapi-processor-spring!" 12 | } 13 | -------------------------------------------------------------------------------- /src/main/kotlin/io/openapiprocessor/spring/processor/SpringFramework.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 https://github.com/openapi-processor/openapi-processor-spring 3 | * PDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package io.openapiprocessor.spring.processor 7 | 8 | import io.openapiprocessor.core.framework.FrameworkBase 9 | import io.openapiprocessor.core.model.datatypes.DataType 10 | import io.openapiprocessor.core.model.parameters.Parameter 11 | import io.openapiprocessor.core.parser.MultipartParameter as MultipartParserParameter 12 | import io.openapiprocessor.core.parser.Parameter as ParserParameter 13 | import io.openapiprocessor.spring.model.parameters.MultipartParameter 14 | import io.openapiprocessor.spring.model.parameters.QueryParameter 15 | 16 | /** 17 | * Spring model factory. 18 | * 19 | * @author Martin Hauner 20 | */ 21 | class SpringFramework: FrameworkBase() { 22 | 23 | @Override 24 | override fun createQueryParameter(parameter: ParserParameter, dataType: DataType): Parameter { 25 | return QueryParameter ( 26 | parameter.getName(), 27 | dataType, 28 | parameter.isRequired(), 29 | parameter.isDeprecated()) 30 | } 31 | 32 | override fun createMultipartParameter(parameter: ParserParameter, dataType: DataType): Parameter { 33 | val mpp = parameter as MultipartParserParameter 34 | 35 | return MultipartParameter( 36 | parameter.getName(), 37 | dataType, 38 | parameter.isRequired(), 39 | parameter.isDeprecated(), 40 | parameter.description, 41 | mpp.contentType 42 | ) 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/kotlin/io/openapiprocessor/spring/processor/SpringService.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 https://github.com/openapi-processor/openapi-processor-spring 3 | * PDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | @file:Suppress("DEPRECATION") 7 | 8 | package io.openapiprocessor.spring.processor 9 | 10 | /** 11 | * Entry point of openapi-processor-spring loaded via [java.util.ServiceLoader] by the v1 interface 12 | * [io.openapiprocessor.api.v1.OpenApiProcessor]. 13 | */ 14 | class SpringService(private val testMode: Boolean = false): 15 | io.openapiprocessor.api.v1.OpenApiProcessor, 16 | io.openapiprocessor.api.OpenApiProcessor 17 | { 18 | override fun getName(): String { 19 | return "spring" 20 | } 21 | 22 | override fun run(processorOptions: MutableMap) { 23 | try { 24 | val processor = SpringProcessor() 25 | if (testMode) { 26 | processor.enableTestMode() 27 | } 28 | processor.run(processorOptions) 29 | 30 | } catch (ex: Exception) { 31 | throw ex 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/main/kotlin/io/openapiprocessor/spring/processor/SpringServiceV2.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 https://github.com/openapi-processor/openapi-processor-spring 3 | * PDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | //@file:Suppress("DEPRECATION") 7 | 8 | package io.openapiprocessor.spring.processor 9 | 10 | import io.openapiprocessor.api.v2.Version 11 | import io.openapiprocessor.core.version.GitHubVersionException 12 | import io.openapiprocessor.core.version.GitHubVersionProvider 13 | import io.openapiprocessor.test.api.OpenApiProcessorTest 14 | import org.slf4j.Logger 15 | import org.slf4j.LoggerFactory 16 | 17 | /** 18 | * Entry point of openapi-processor-spring loaded via [java.util.ServiceLoader]. by the v2 interface 19 | * [io.openapiprocessor.api.v1.OpenApiProcessor]. 20 | * 21 | * the v2 interfaces *must* be implemented by its own class and not by [SpringService] to be downward 22 | * compatible with gradle/maven plugin versions that do not know the v2 interfaces. 23 | */ 24 | class SpringServiceV2( 25 | private val provider: GitHubVersionProvider = GitHubVersionProvider("openapi-processor-spring"), 26 | private val testMode: Boolean = false): 27 | io.openapiprocessor.api.v2.OpenApiProcessor, 28 | io.openapiprocessor.api.v2.OpenApiProcessorVersion, 29 | OpenApiProcessorTest 30 | { 31 | private val log: Logger = LoggerFactory.getLogger(this.javaClass.name) 32 | 33 | private var sourceRoot: String? = null 34 | private var resourceRoot: String? = null 35 | 36 | override fun getName(): String { 37 | return "spring" 38 | } 39 | 40 | override fun run(processorOptions: MutableMap) { 41 | try { 42 | val processor = SpringProcessor() 43 | if (testMode) { 44 | processor.enableTestMode() 45 | } 46 | processor.run(processorOptions) 47 | 48 | sourceRoot = processor.sourceRoot 49 | resourceRoot = processor.resourceRoot 50 | } catch (ex: Exception) { 51 | throw ex 52 | } 53 | } 54 | 55 | override fun getVersion(): String { 56 | return io.openapiprocessor.spring.Version.version 57 | } 58 | 59 | override fun getLatestVersion(): Version { 60 | return provider.getVersion() 61 | } 62 | 63 | override fun hasNewerVersion(): Boolean { 64 | try { 65 | val version = version 66 | val latest = latestVersion 67 | 68 | if (latest.name > version) { 69 | log.info("openapi-processor-spring version ${latest.name} is available! I'm version ${version}.") 70 | return true 71 | } 72 | 73 | return false 74 | } catch (ex: GitHubVersionException) { 75 | // just ignore, do not complain 76 | return false 77 | } 78 | } 79 | 80 | override fun getSourceRoot(): String? { 81 | return sourceRoot 82 | } 83 | 84 | override fun getResourceRoot(): String? { 85 | return resourceRoot 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /src/main/kotlin/io/openapiprocessor/spring/writer/java/AdditionalEnumWriter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 https://github.com/openapi-processor/openapi-processor-spring 3 | * PDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package io.openapiprocessor.spring.writer.java 7 | 8 | import io.openapiprocessor.core.converter.ApiOptions 9 | import io.openapiprocessor.core.writer.SourceFormatter 10 | import io.openapiprocessor.core.writer.WriterFactory 11 | import java.io.StringWriter 12 | import java.io.Writer 13 | 14 | class AdditionalEnumWriter { 15 | 16 | fun write(options: ApiOptions, formatter: SourceFormatter, factory: WriterFactory) { 17 | if (options.enumType != "framework") { 18 | return 19 | } 20 | 21 | val piWriter = createPackageInfoWriter(options, factory) 22 | writePackageInfo(options, piWriter, formatter) 23 | piWriter.close() 24 | 25 | val writer = createFactoryWriter(options, factory) 26 | writeEnumConverterFactory(options, writer, formatter) 27 | writer.close() 28 | } 29 | 30 | private fun writePackageInfo(options: ApiOptions, writer: Writer, formatter: SourceFormatter) { 31 | val raw = StringWriter() 32 | PackageInfoWriter(options).writePackageInfo(raw) 33 | writer.write(formatter.format(raw.toString())) 34 | } 35 | 36 | private fun writeEnumConverterFactory(options: ApiOptions, writer: Writer, formatter: SourceFormatter) { 37 | val raw = StringWriter() 38 | EnumConverterFactoryWriter(options).writeConverterFactory(raw) 39 | writer.write(formatter.format(raw.toString())) 40 | } 41 | 42 | private fun createFactoryWriter(options: ApiOptions, writerFactory: WriterFactory): Writer { 43 | return writerFactory.createWriter( 44 | "${options.packageName}.spring", 45 | "StringToEnumConverterFactory") 46 | } 47 | 48 | private fun createPackageInfoWriter(options: ApiOptions, writerFactory: WriterFactory): Writer { 49 | return writerFactory.createWriter( 50 | "${options.packageName}.spring", 51 | "package-info") 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /src/main/kotlin/io/openapiprocessor/spring/writer/java/EnumConverterFactoryWriter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 https://github.com/openapi-processor/openapi-processor-spring 3 | * PDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package io.openapiprocessor.spring.writer.java 7 | 8 | import io.openapiprocessor.core.converter.ApiOptions 9 | import java.io.Writer 10 | 11 | class EnumConverterFactoryWriter(val options: ApiOptions) { 12 | 13 | fun writeConverterFactory(target: Writer) { 14 | target.write(""" 15 | package ${options.packageName}.spring; 16 | 17 | import org.springframework.core.convert.converter.Converter; 18 | import org.springframework.core.convert.converter.ConverterFactory; 19 | 20 | import java.util.EnumSet; 21 | import java.util.function.Supplier; 22 | 23 | public class StringToEnumConverterFactory & Supplier> 24 | implements ConverterFactory { 25 | 26 | @Override 27 | @SuppressWarnings({"unchecked", "rawtypes"}) 28 | public Converter getConverter(Class targetType) { 29 | return new StringToEnumConverter(targetType); 30 | } 31 | 32 | static class StringToEnumConverter & Supplier> implements Converter { 33 | 34 | private final Class enumType; 35 | 36 | public StringToEnumConverter(Class enumType) { 37 | this.enumType = enumType; 38 | } 39 | 40 | public T convert(String source) { 41 | String sourceValue = source.trim(); 42 | 43 | for (T e : EnumSet.allOf(enumType)) { 44 | if (e.get().equals(sourceValue)) { 45 | return e; 46 | } 47 | } 48 | 49 | throw new IllegalArgumentException(String.format("No enum constant of %s has the value %s", 50 | enumType.getCanonicalName(), 51 | sourceValue)); 52 | } 53 | } 54 | } 55 | """.trimIndent()) 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/kotlin/io/openapiprocessor/spring/writer/java/MappingAnnotationWriter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 https://github.com/openapi-processor/openapi-processor-spring 3 | * PDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package io.openapiprocessor.spring.writer.java 7 | 8 | import io.openapiprocessor.core.model.Endpoint 9 | import io.openapiprocessor.core.model.EndpointResponse 10 | import io.openapiprocessor.spring.processor.SpringFrameworkAnnotations 11 | import java.io.Writer 12 | import io.openapiprocessor.core.writer.java.MappingAnnotationWriter as CoreMappingAnnotationWriter 13 | 14 | /** 15 | * spring mapping annotation writer 16 | */ 17 | class MappingAnnotationWriter(private val annotations: SpringFrameworkAnnotations): CoreMappingAnnotationWriter { 18 | 19 | override fun write(target: Writer, endpoint: Endpoint, endpointResponse: EndpointResponse) { 20 | target.write(createAnnotation(endpoint, endpointResponse)) 21 | } 22 | 23 | private fun createAnnotation(endpoint: Endpoint, endpointResponse: EndpointResponse): String { 24 | val annotation = annotations.getAnnotation(endpoint.method) 25 | 26 | var mapping = annotation.annotationName 27 | mapping += "(" 28 | mapping += "path = " + quote(endpoint.path) 29 | 30 | val consumes = endpoint.getConsumesContentTypes() 31 | if (consumes.isNotEmpty()) { 32 | mapping += ", " 33 | mapping += "consumes = {" 34 | mapping += consumes.joinToString(", ") { 35 | quote(it) 36 | } 37 | mapping += '}' 38 | } 39 | 40 | val produces = endpointResponse.contentTypes 41 | if (produces.isNotEmpty()) { 42 | mapping += ", " 43 | mapping += "produces = {" 44 | 45 | mapping += produces.joinToString(", ") { 46 | quote(it) 47 | } 48 | 49 | mapping += "}" 50 | } 51 | 52 | annotation.parameters.forEach { 53 | mapping += ", " 54 | mapping += "${it.key} = ${it.value.value}" 55 | } 56 | 57 | mapping += ")" 58 | return mapping 59 | } 60 | 61 | private fun quote(content: String): String { 62 | return '"' + content + '"' 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /src/main/kotlin/io/openapiprocessor/spring/writer/java/PackageInfoWriter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 https://github.com/openapi-processor/openapi-processor-spring 3 | * PDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package io.openapiprocessor.spring.writer.java 7 | 8 | import io.openapiprocessor.core.converter.ApiOptions 9 | import java.io.Writer 10 | 11 | class PackageInfoWriter(val options: ApiOptions) { 12 | 13 | fun writePackageInfo(target: Writer) { 14 | target.write(""" 15 | @org.springframework.lang.NonNullApi 16 | @org.springframework.lang.NonNullFields 17 | package ${options.packageName}.spring; 18 | """.trimIndent()) 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/kotlin/io/openapiprocessor/spring/writer/java/ParameterAnnotationWriter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2019 https://github.com/openapi-processor/openapi-processor-spring 3 | * PDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package io.openapiprocessor.spring.writer.java 7 | 8 | import io.openapiprocessor.core.framework.FrameworkAnnotations 9 | import io.openapiprocessor.core.model.RequestBody 10 | import io.openapiprocessor.core.model.parameters.Parameter 11 | import io.openapiprocessor.core.writer.java.ParameterAnnotationWriter as CoreParameterAnnotationWriter 12 | import java.io.Writer 13 | 14 | /** 15 | * spring parameter annotation writer 16 | */ 17 | class ParameterAnnotationWriter(private val annotations: FrameworkAnnotations) 18 | : CoreParameterAnnotationWriter { 19 | 20 | override fun write(target: Writer, parameter: Parameter) { 21 | if (parameter is RequestBody) { 22 | target.write(createRequestBodyAnnotation(parameter)) 23 | } else { 24 | target.write(createParameterAnnotation(parameter)) 25 | } 26 | } 27 | 28 | private fun createRequestBodyAnnotation(requestBody: RequestBody): String { 29 | var annotation = getAnnotationName(requestBody) 30 | 31 | // required is default, so add required only if the parameter is not required 32 | if (!requestBody.required) { 33 | annotation += "(required = false)" 34 | } 35 | 36 | return annotation 37 | } 38 | 39 | private fun createParameterAnnotation(parameter: Parameter): String { 40 | if (! parameter.withAnnotation) { 41 | return "" 42 | } 43 | 44 | var annotation = getAnnotationName (parameter) 45 | 46 | if (! parameter.withParameters) { 47 | return annotation 48 | } 49 | 50 | annotation += "(" 51 | annotation += "name = " + quote (parameter.name) 52 | 53 | // required is the default, add required only if the parameter is not required 54 | if (!parameter.required) { 55 | annotation += ", " 56 | annotation += "required = false" 57 | } 58 | 59 | if (!parameter.required && hasDefault (parameter)) { 60 | annotation += ", " 61 | annotation += "defaultValue = ${getDefault(parameter)}" 62 | } 63 | 64 | annotation += ")" 65 | return annotation 66 | } 67 | 68 | private fun getAnnotationName(parameter: Parameter): String { 69 | return annotations.getAnnotation (parameter).annotationName 70 | } 71 | 72 | private fun hasDefault(parameter: Parameter): Boolean { 73 | return parameter.constraints.hasDefault() 74 | } 75 | 76 | private fun getDefault(parameter: Parameter): String { 77 | return quote(parameter.constraints.default.toString()) 78 | } 79 | 80 | private fun quote(content: String): String { 81 | return '"' + content + '"' 82 | } 83 | 84 | } 85 | -------------------------------------------------------------------------------- /src/main/kotlin/io/openapiprocessor/spring/writer/java/SpringWriterFactory.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2023 https://github.com/openapi-processor/openapi-processor-spring 3 | * PDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package io.openapiprocessor.spring.writer.java 7 | 8 | import io.openapiprocessor.core.converter.ApiOptions 9 | import io.openapiprocessor.core.writer.DefaultWriterFactory 10 | import java.nio.file.Path 11 | 12 | class SpringWriterFactory(options: ApiOptions): DefaultWriterFactory(options) { 13 | 14 | override fun initAdditionalPackages(options: ApiOptions): Map { 15 | val pkgPaths = HashMap() 16 | 17 | if (options.enumType == "framework") { 18 | val (name, path) = initTargetPackage("spring") 19 | pkgPaths[name] = path 20 | } 21 | 22 | return pkgPaths 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/main/kotlin/io/openapiprocessor/spring/writer/java/StatusAnnotationWriter.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 https://github.com/openapi-processor/openapi-processor-spring 3 | * PDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package io.openapiprocessor.spring.writer.java 7 | 8 | import io.openapiprocessor.core.framework.FrameworkAnnotations 9 | import io.openapiprocessor.core.model.Endpoint 10 | import io.openapiprocessor.core.model.EndpointResponse 11 | import io.openapiprocessor.core.model.EndpointResponseStatus 12 | import java.io.Writer 13 | import io.openapiprocessor.core.writer.java.StatusAnnotationWriter as CoreStatusAnnotationWriter 14 | 15 | class StatusAnnotationWriter(private val annotations: FrameworkAnnotations): CoreStatusAnnotationWriter { 16 | 17 | override fun write( 18 | target: Writer, 19 | endpoint: Endpoint, 20 | endpointResponse: EndpointResponse 21 | ) { 22 | target.write(createStatusAnnotation(endpointResponse)) 23 | } 24 | 25 | private fun createStatusAnnotation(status: EndpointResponseStatus): String { 26 | val data = annotations.getAnnotation(status) 27 | val status = data.parameters["code"] 28 | 29 | var annotation = data.annotationName 30 | annotation += "(${status!!.value})" 31 | return annotation 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/com.github.hauner.openapi.api.OpenApiProcessor: -------------------------------------------------------------------------------- 1 | io.openapiprocessor.spring.processor.SpringService 2 | 3 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/io.openapiprocessor.api.OpenApiProcessor: -------------------------------------------------------------------------------- 1 | io.openapiprocessor.spring.processor.SpringService 2 | 3 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/io.openapiprocessor.api.v1.OpenApiProcessor: -------------------------------------------------------------------------------- 1 | io.openapiprocessor.spring.processor.SpringService 2 | -------------------------------------------------------------------------------- /src/main/resources/META-INF/services/io.openapiprocessor.api.v2.OpenApiProcessor: -------------------------------------------------------------------------------- 1 | io.openapiprocessor.spring.processor.SpringServiceV2 2 | -------------------------------------------------------------------------------- /src/test/groovy/io/openapiprocessor/spring/writer/java/CookieParameterAnnotationWriterSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 the original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.openapiprocessor.spring.writer.java 18 | 19 | import io.openapiprocessor.spring.processor.SpringFrameworkAnnotations 20 | import io.openapiprocessor.core.model.datatypes.DataTypeConstraints 21 | import io.openapiprocessor.core.model.datatypes.StringDataType 22 | import io.openapiprocessor.core.model.parameters.CookieParameter 23 | import spock.lang.Specification 24 | 25 | class CookieParameterAnnotationWriterSpec extends Specification { 26 | def writer = new ParameterAnnotationWriter(new SpringFrameworkAnnotations()) 27 | def target = new StringWriter() 28 | 29 | void "write simple (required) cookie parameter" () { 30 | def param = new CookieParameter( 31 | 'foo', new StringDataType(), true, false, null) 32 | 33 | when: 34 | writer.write (target, param) 35 | 36 | then: 37 | target.toString () == '@CookieValue(name = "foo")' 38 | } 39 | 40 | void "write simple (optional) cookie parameter" () { 41 | def param = new CookieParameter( 42 | 'foo', new StringDataType(), false, false, null) 43 | 44 | when: 45 | writer.write (target, param) 46 | 47 | then: 48 | target.toString () == '@CookieValue(name = "foo", required = false)' 49 | } 50 | 51 | void "write simple (optional with default) cookie parameter" () { 52 | def param = new CookieParameter('foo', 53 | new StringDataType('string', 54 | createConstraints ('bar'), 55 | false, null), 56 | false, false, null) 57 | 58 | when: 59 | writer.write (target, param) 60 | 61 | then: 62 | target.toString () == '@CookieValue(name = "foo", required = false, defaultValue = "bar")' 63 | } 64 | 65 | DataTypeConstraints createConstraints(def defaultValue) { 66 | def c = new DataTypeConstraints() 67 | c.defaultValue = defaultValue 68 | c 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/test/groovy/io/openapiprocessor/spring/writer/java/HeaderParameterAnnotationWriterSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 the original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.openapiprocessor.spring.writer.java 18 | 19 | import io.openapiprocessor.spring.processor.SpringFrameworkAnnotations 20 | import io.openapiprocessor.core.model.datatypes.DataTypeConstraints 21 | import io.openapiprocessor.core.model.datatypes.StringDataType 22 | import io.openapiprocessor.core.model.parameters.HeaderParameter 23 | import spock.lang.Specification 24 | 25 | class HeaderParameterAnnotationWriterSpec extends Specification { 26 | def writer = new ParameterAnnotationWriter(new SpringFrameworkAnnotations()) 27 | def target = new StringWriter() 28 | 29 | void "write simple (required) header parameter" () { 30 | def param = new HeaderParameter( 31 | 'foo', new StringDataType(), true, false, null) 32 | 33 | when: 34 | writer.write (target, param) 35 | 36 | then: 37 | target.toString () == '@RequestHeader(name = "foo")' 38 | } 39 | 40 | void "write simple (optional) header parameter" () { 41 | def param = new HeaderParameter( 42 | 'foo', new StringDataType(), false, false, null) 43 | 44 | when: 45 | writer.write (target, param) 46 | 47 | then: 48 | target.toString () == '@RequestHeader(name = "foo", required = false)' 49 | } 50 | 51 | void "write simple (optional with default) header parameter" () { 52 | def param = new HeaderParameter('foo', 53 | new StringDataType('string', 54 | createConstraints ('bar'), 55 | false, null), 56 | false, false, null) 57 | 58 | when: 59 | writer.write (target, param) 60 | 61 | then: 62 | target.toString () == '@RequestHeader(name = "foo", required = false, defaultValue = "bar")' 63 | } 64 | 65 | DataTypeConstraints createConstraints(def defaultValue) { 66 | def c = new DataTypeConstraints() 67 | c.defaultValue = defaultValue 68 | c 69 | } 70 | 71 | } 72 | -------------------------------------------------------------------------------- /src/test/groovy/io/openapiprocessor/spring/writer/java/ParameterAnnotationWriterSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 the original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.openapiprocessor.spring.writer.java 18 | 19 | import io.openapiprocessor.core.model.datatypes.DataTypeName 20 | import io.openapiprocessor.spring.processor.SpringFrameworkAnnotations 21 | import io.openapiprocessor.core.model.RequestBody 22 | import io.openapiprocessor.core.model.datatypes.DataTypeConstraints 23 | import io.openapiprocessor.core.model.datatypes.LongDataType 24 | import io.openapiprocessor.core.model.datatypes.ObjectDataType 25 | import io.openapiprocessor.core.model.datatypes.StringDataType 26 | import io.openapiprocessor.core.model.parameters.CookieParameter 27 | import io.openapiprocessor.core.model.parameters.HeaderParameter 28 | import io.openapiprocessor.core.model.parameters.PathParameter 29 | import io.openapiprocessor.spring.model.parameters.QueryParameter 30 | import spock.lang.Specification 31 | import spock.lang.Unroll 32 | 33 | class ParameterAnnotationWriterSpec extends Specification { 34 | def writer = new ParameterAnnotationWriter(new SpringFrameworkAnnotations()) 35 | def target = new StringWriter() 36 | 37 | @Unroll 38 | void "writes simple (optional) query parameter with quoted string default value" () { 39 | def param = clazz.newInstance('foo', 40 | new StringDataType( 41 | 'string', 42 | createConstraints ('bar'), false, null), 43 | false, false, null) 44 | 45 | when: 46 | writer.write (target, param) 47 | 48 | then: 49 | target.toString () == """${annotation}(name = "foo", required = false, defaultValue = "bar")""" 50 | 51 | where: 52 | clazz | annotation 53 | QueryParameter | "@RequestParam" 54 | PathParameter | "@PathVariable" 55 | CookieParameter | "@CookieValue" 56 | HeaderParameter | "@RequestHeader" 57 | } 58 | 59 | void "writes simple (optional) query parameter with quoted number default value" () { 60 | def param = clazz.newInstance ('foo', 61 | new LongDataType ( 62 | 'integer:int64', 63 | createConstraints (5), false, null), 64 | false, false, null) 65 | 66 | when: 67 | writer.write (target, param) 68 | 69 | then: 70 | target.toString () == """${annotation}(name = "foo", required = false, defaultValue = "5")""" 71 | 72 | where: 73 | clazz | annotation 74 | QueryParameter | "@RequestParam" 75 | PathParameter | "@PathVariable" 76 | CookieParameter | "@CookieValue" 77 | HeaderParameter | "@RequestHeader" 78 | } 79 | 80 | void "writes required request body parameter" () { 81 | def body = new RequestBody ( 82 | 'body', 'application/json', 83 | new ObjectDataType (new DataTypeName('FooRequestBody', 'FooRequestBody'), '', 84 | ['foo': new StringDataType ()], null, false, null), 85 | true, false, null) 86 | 87 | when: 88 | writer.write (target, body) 89 | 90 | then: 91 | target.toString () == "@RequestBody" 92 | } 93 | 94 | void "writes optional request body parameter" () { 95 | def body = new RequestBody ( 96 | 'body', 'application/json', 97 | new ObjectDataType (new DataTypeName('FooRequestBody', 'FooRequestBody'), '', 98 | ['foo': new StringDataType ()], null, false, null), 99 | false, false, null) 100 | 101 | when: 102 | writer.write (target, body) 103 | 104 | then: 105 | target.toString () == "@RequestBody(required = false)" 106 | } 107 | 108 | DataTypeConstraints createConstraints(def defaultValue) { 109 | def c = new DataTypeConstraints() 110 | c.defaultValue = defaultValue 111 | c 112 | } 113 | 114 | } 115 | -------------------------------------------------------------------------------- /src/test/groovy/io/openapiprocessor/spring/writer/java/PathParameterAnnotationWriterSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 the original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.openapiprocessor.spring.writer.java 18 | 19 | import io.openapiprocessor.spring.processor.SpringFrameworkAnnotations 20 | import io.openapiprocessor.core.model.datatypes.DataTypeConstraints 21 | import io.openapiprocessor.core.model.datatypes.StringDataType 22 | import io.openapiprocessor.core.model.parameters.PathParameter 23 | import spock.lang.Specification 24 | 25 | class PathParameterAnnotationWriterSpec extends Specification { 26 | def writer = new ParameterAnnotationWriter(new SpringFrameworkAnnotations()) 27 | def target = new StringWriter() 28 | 29 | void "write simple (required) path parameter" () { 30 | def param = new PathParameter( 31 | 'foo', new StringDataType(), true, false, null) 32 | 33 | when: 34 | writer.write (target, param) 35 | 36 | then: 37 | target.toString () == '@PathVariable(name = "foo")' 38 | } 39 | 40 | void "write simple (optional) path parameter" () { 41 | def param = new PathParameter( 42 | 'foo', new StringDataType(), false, false, null) 43 | 44 | when: 45 | writer.write (target, param) 46 | 47 | then: 48 | target.toString () == '@PathVariable(name = "foo", required = false)' 49 | } 50 | 51 | void "write simple (optional with default) path parameter" () { 52 | def param = new PathParameter('foo', 53 | new StringDataType( 54 | 'string', 55 | createConstraints ('bar'), 56 | false, null), 57 | false, false, null) 58 | 59 | when: 60 | writer.write (target, param) 61 | 62 | then: 63 | target.toString () == '@PathVariable(name = "foo", required = false, defaultValue = "bar")' 64 | } 65 | 66 | DataTypeConstraints createConstraints(def defaultValue) { 67 | def c = new DataTypeConstraints() 68 | c.defaultValue = defaultValue 69 | c 70 | } 71 | 72 | } 73 | -------------------------------------------------------------------------------- /src/test/groovy/io/openapiprocessor/spring/writer/java/QueryParameterAnnotationWriterSpec.groovy: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2020 the original authors 3 | * 4 | * Licensed under the Apache License, Version 2.0 (the "License"); 5 | * you may not use this file except in compliance with the License. 6 | * You may obtain a copy of the License at 7 | * 8 | * http://www.apache.org/licenses/LICENSE-2.0 9 | * 10 | * Unless required by applicable law or agreed to in writing, software 11 | * distributed under the License is distributed on an "AS IS" BASIS, 12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | * See the License for the specific language governing permissions and 14 | * limitations under the License. 15 | */ 16 | 17 | package io.openapiprocessor.spring.writer.java 18 | 19 | import io.openapiprocessor.core.model.datatypes.DataTypeConstraints 20 | import io.openapiprocessor.core.model.datatypes.DataTypeName 21 | import io.openapiprocessor.core.model.datatypes.ObjectDataType 22 | import io.openapiprocessor.core.model.datatypes.StringDataType 23 | import io.openapiprocessor.spring.model.parameters.QueryParameter 24 | import io.openapiprocessor.spring.processor.SpringFrameworkAnnotations 25 | import spock.lang.Specification 26 | 27 | class QueryParameterAnnotationWriterSpec extends Specification { 28 | def writer = new ParameterAnnotationWriter(new SpringFrameworkAnnotations()) 29 | def target = new StringWriter() 30 | 31 | void "write simple (required) query parameter" () { 32 | def param = new QueryParameter( 33 | 'foo', new StringDataType(), true, false, null) 34 | 35 | when: 36 | writer.write (target, param) 37 | 38 | then: 39 | target.toString () == '@RequestParam(name = "foo")' 40 | } 41 | 42 | void "write simple (optional) query parameter" () { 43 | def param = new QueryParameter( 44 | 'foo', new StringDataType(), false, false, null) 45 | 46 | when: 47 | writer.write (target, param) 48 | 49 | then: 50 | target.toString () == '@RequestParam(name = "foo", required = false)' 51 | } 52 | 53 | void "write simple (optional with default) query parameter" () { 54 | def param = new QueryParameter('foo', 55 | new StringDataType( 56 | 'string', 57 | new DataTypeConstraints(defaultValue: 'bar'), 58 | false, null), 59 | false, false, null) 60 | 61 | when: 62 | writer.write (target, param) 63 | 64 | then: 65 | target.toString () == '@RequestParam(name = "foo", required = false, defaultValue = "bar")' 66 | } 67 | 68 | void "writes object query parameter without annotation" () { 69 | def param = new QueryParameter( 70 | 'foo', 71 | new ObjectDataType ( 72 | new DataTypeName('Foo', 'Foo'), '', [ 73 | foo1: new StringDataType (), 74 | foo2: new StringDataType () 75 | ], null, false, null 76 | ), false, false, null 77 | ) 78 | 79 | when: 80 | writer.write (target, param) 81 | 82 | then: 83 | target.toString () == "" 84 | } 85 | 86 | } 87 | -------------------------------------------------------------------------------- /src/test/kotlin/io/openapiprocessor/spring/processor/SpringFrameworkAnnotationsSpec.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 https://github.com/openapi-processor/openapi-processor-spring 3 | * PDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package io.openapiprocessor.spring.processor 7 | 8 | import io.kotest.core.spec.style.StringSpec 9 | import io.kotest.matchers.collections.shouldContainExactly 10 | import io.kotest.matchers.shouldBe 11 | import io.openapiprocessor.core.model.EndpointResponseStatus 12 | import io.openapiprocessor.core.model.HttpStatus 13 | 14 | 15 | class SpringFrameworkAnnotationsSpec : StringSpec({ 16 | 17 | class Status(override val statusCode: HttpStatus) : EndpointResponseStatus 18 | 19 | "provides known HttpStatus annotation with specific status code import" { 20 | val framework = SpringFrameworkAnnotations() 21 | 22 | val annotation200 = framework.getAnnotation(Status("200")) 23 | annotation200.referencedImports shouldContainExactly setOf("org.springframework.http.HttpStatus") 24 | annotation200.parameters["code"]!!.value shouldBe "HttpStatus.OK" 25 | 26 | val annotation400 = framework.getAnnotation(Status("400")) 27 | annotation400.referencedImports shouldContainExactly setOf("org.springframework.http.HttpStatus") 28 | annotation400.parameters["code"]!!.value shouldBe "HttpStatus.BAD_REQUEST" 29 | 30 | val annotation500 = framework.getAnnotation(Status("500")) 31 | annotation500.referencedImports shouldContainExactly setOf("org.springframework.http.HttpStatus") 32 | annotation500.parameters["code"]!!.value shouldBe "HttpStatus.INTERNAL_SERVER_ERROR" 33 | } 34 | }) 35 | -------------------------------------------------------------------------------- /src/test/kotlin/io/openapiprocessor/spring/processor/SpringServiceSpec.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 https://github.com/openapi-processor/openapi-processor-spring 3 | * PDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package io.openapiprocessor.spring.processor 7 | 8 | import io.kotest.core.spec.style.StringSpec 9 | import io.kotest.matchers.booleans.shouldBeFalse 10 | import io.kotest.matchers.booleans.shouldBeTrue 11 | import io.kotest.matchers.shouldBe 12 | import io.mockk.every 13 | import io.mockk.mockk 14 | import io.openapiprocessor.core.version.GitHubVersion 15 | import io.openapiprocessor.core.version.GitHubVersionProvider 16 | import io.openapiprocessor.spring.Version 17 | import java.time.Instant 18 | 19 | class SpringServiceSpec : StringSpec({ 20 | 21 | "get version" { 22 | val service = SpringServiceV2() 23 | service.version.shouldBe(Version.version) 24 | } 25 | 26 | "gets latest version" { 27 | val provider = mockk() 28 | every { provider.getVersion() } returns GitHubVersion("1", Instant.now(), "any") 29 | 30 | val service = SpringServiceV2(provider) 31 | 32 | service.latestVersion.name shouldBe "1" 33 | } 34 | 35 | "checks newer version available" { 36 | val provider = mockk() 37 | every { provider.getVersion() } returns GitHubVersion("3000", Instant.now(), "any") 38 | 39 | val service = SpringServiceV2(provider) 40 | service.hasNewerVersion().shouldBeTrue() 41 | } 42 | 43 | "checks newer version not available" { 44 | val provider = mockk() 45 | every { provider.getVersion() } returns GitHubVersion(Version.version, Instant.now(), "any") 46 | 47 | val service = SpringServiceV2(provider) 48 | service.hasNewerVersion().shouldBeFalse() 49 | } 50 | 51 | }) 52 | -------------------------------------------------------------------------------- /src/test/kotlin/io/openapiprocessor/spring/writer/java/StatusAnnotationWriterSpec.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 https://github.com/openapi-processor/openapi-processor-spring 3 | * PDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package io.openapiprocessor.spring.writer.java 7 | 8 | import io.kotest.core.spec.style.StringSpec 9 | import io.kotest.matchers.equals.shouldBeEqual 10 | import io.openapiprocessor.core.model.Endpoint 11 | import io.openapiprocessor.core.model.Response 12 | import io.openapiprocessor.core.model.datatypes.NoneDataType 13 | import io.openapiprocessor.core.parser.HttpMethod 14 | import io.openapiprocessor.spring.processor.SpringFrameworkAnnotations 15 | import java.io.StringWriter 16 | 17 | 18 | class StatusAnnotationWriterSpec: StringSpec({ 19 | val writer = StatusAnnotationWriter(SpringFrameworkAnnotations()) 20 | val target = StringWriter() 21 | 22 | "write response status annotation" { 23 | val ep = Endpoint( 24 | "/foo", 25 | HttpMethod.GET, 26 | emptyList(), 27 | emptyList(), 28 | mapOf( 29 | "204" to listOf(Response("?", NoneDataType())) 30 | ) 31 | ) 32 | 33 | writer.write(target, ep, ep.endpointResponses.first()) 34 | 35 | target.toString() shouldBeEqual """@ResponseStatus(HttpStatus.NO_CONTENT)""" 36 | } 37 | }) 38 | -------------------------------------------------------------------------------- /src/testInt/kotlin/io/openapiprocessor/spring/CompileExpectedSpec.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2025 https://github.com/openapi-processor/openapi-processor-spring 3 | * PDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package io.openapiprocessor.spring 7 | 8 | import io.kotest.core.spec.style.StringSpec 9 | import io.kotest.engine.spec.tempdir 10 | import io.kotest.matchers.booleans.shouldBeTrue 11 | import io.openapiprocessor.core.parser.ParserType 12 | import io.openapiprocessor.test.* 13 | import kotlin.collections.filter 14 | import kotlin.collections.map 15 | import kotlin.collections.plus 16 | 17 | 18 | class CompileExpectedSpec: StringSpec({ 19 | 20 | for (testSet in sources()) { 21 | "compile - $testSet".config(enabled = true) { 22 | val folder = tempdir() 23 | val reader = ResourceReader(CompileExpectedSpec::class.java) 24 | 25 | val testFiles = TestFilesNative(folder, reader) 26 | 27 | TestSetCompiler(testSet, testFiles) 28 | .run(setOf( 29 | "src/testInt/resources/compile/Generated.java" 30 | )) 31 | .shouldBeTrue() 32 | } 33 | } 34 | }) 35 | 36 | private fun sources(): Collection { 37 | val compile30 = ALL_30.map { 38 | testSet(it.name, ParserType.INTERNAL, it.openapi, model = "default", outputs = it.outputs, expected = it.expected) 39 | } 40 | 41 | val compile31 = ALL_31.map { 42 | testSet(it.name, ParserType.INTERNAL, it.openapi, model = "default", outputs = it.outputs, expected = it.expected) 43 | } 44 | 45 | val compile30r = ALL_30.filter { it.modelTypes.contains(ModelTypes.RECORD) }.map { 46 | testSet(it.name, ParserType.INTERNAL, it.openapi, model = "record", outputs = it.outputs, expected = it.expected) 47 | } 48 | 49 | val compile31r = ALL_31.filter { it.modelTypes.contains(ModelTypes.RECORD) }.map { 50 | testSet(it.name, ParserType.INTERNAL, it.openapi, model = "record", outputs = it.outputs, expected = it.expected) 51 | } 52 | 53 | return compile30 + compile31 + compile30r + compile31r 54 | } 55 | -------------------------------------------------------------------------------- /src/testInt/kotlin/io/openapiprocessor/spring/ProcessorEndToEndJimfsSpec.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 https://github.com/openapi-processor/openapi-processor-spring 3 | * PDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package io.openapiprocessor.spring 7 | 8 | import com.google.common.jimfs.Configuration 9 | import com.google.common.jimfs.Jimfs 10 | import io.kotest.core.spec.style.StringSpec 11 | import io.kotest.matchers.booleans.shouldBeTrue 12 | import io.openapiprocessor.core.parser.ParserType 13 | import io.openapiprocessor.test.* 14 | import io.openapiprocessor.test.TestSet 15 | 16 | /** 17 | * runs integration tests with Jimfs. 18 | */ 19 | class ProcessorEndToEndJimfsSpec: StringSpec({ 20 | 21 | for (testSet in sources()) { 22 | "jimfs - $testSet".config(enabled = true) { 23 | val fs = Jimfs.newFileSystem (Configuration.unix ()) 24 | val reader = ResourceReader(ProcessorEndToEndJimfsSpec::class.java) 25 | 26 | val testFiles = TestFilesJimfs(fs, reader) 27 | val test = Test(testSet, testFiles) 28 | 29 | TestSetRunner(test, testSet) 30 | .run() 31 | .shouldBeTrue() 32 | } 33 | } 34 | 35 | }) 36 | 37 | private fun sources(): Collection { 38 | // the swagger parser does not work with a custom FileSystem 39 | 40 | val openapi4j = ALL_30.map { 41 | testSet(it.name, ParserType.OPENAPI4J, it.openapi, model = "default", outputs = it.outputs, expected = it.expected) 42 | } 43 | 44 | val openapi30 = ALL_30.map { 45 | testSet(it.name, ParserType.INTERNAL, it.openapi, model = "default", outputs = it.outputs, expected = it.expected) 46 | } 47 | 48 | val openapi31 = ALL_31.map { 49 | testSet(it.name, ParserType.INTERNAL, it.openapi, model = "default", outputs = it.outputs, expected = it.expected) 50 | } 51 | 52 | val openapi30r = ALL_30.filter { it.modelTypes.contains(ModelTypes.RECORD) }.map { 53 | testSet(it.name, ParserType.INTERNAL, it.openapi, model = "record", outputs = it.outputs, expected = it.expected) 54 | } 55 | 56 | val openapi31r = ALL_31.filter { it.modelTypes.contains(ModelTypes.RECORD) }.map { 57 | testSet(it.name, ParserType.INTERNAL, it.openapi, model = "record", outputs = it.outputs, expected = it.expected) 58 | } 59 | 60 | return openapi4j + openapi30 + openapi31 + openapi30r + openapi31r 61 | } 62 | -------------------------------------------------------------------------------- /src/testInt/kotlin/io/openapiprocessor/spring/ProcessorEndToEndSpec.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 https://github.com/openapi-processor/openapi-processor-spring 3 | * PDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package io.openapiprocessor.spring 7 | 8 | import io.kotest.core.spec.style.StringSpec 9 | import io.kotest.engine.spec.tempdir 10 | import io.kotest.matchers.booleans.shouldBeTrue 11 | import io.openapiprocessor.core.parser.ParserType 12 | import io.openapiprocessor.test.* 13 | import io.openapiprocessor.test.TestSet 14 | 15 | /** 16 | * run end-to-end integration test. 17 | */ 18 | class ProcessorEndToEndSpec: StringSpec({ 19 | 20 | for (testSet in sources()) { 21 | "native - $testSet".config(enabled = true) { 22 | val folder = tempdir() 23 | val reader = ResourceReader(ProcessorEndToEndSpec::class.java) 24 | 25 | val testFiles = TestFilesNative(folder, reader) 26 | val test = Test(testSet, testFiles) 27 | 28 | TestSetRunner(test, testSet) 29 | .run() 30 | .shouldBeTrue() 31 | } 32 | } 33 | }) 34 | 35 | private fun sources(): Collection { 36 | val swagger = ALL_30.map { 37 | testSet(it.name, ParserType.SWAGGER, it.openapi, model = "default", outputs = it.outputs, expected = it.expected) 38 | } 39 | 40 | val openapi4j = ALL_30.map { 41 | testSet(it.name, ParserType.OPENAPI4J, it.openapi, model = "default", outputs = it.outputs, expected = it.expected) 42 | } 43 | 44 | val openapi30 = ALL_30.map { 45 | testSet(it.name, ParserType.INTERNAL, it.openapi, model = "default", outputs = it.outputs, expected = it.expected) 46 | } 47 | 48 | val openapi31 = ALL_31.map { 49 | testSet(it.name, ParserType.INTERNAL, it.openapi, model = "default", outputs = it.outputs, expected = it.expected) 50 | } 51 | 52 | val openapi30r = ALL_30.filter { it.modelTypes.contains(ModelTypes.RECORD) }.map { 53 | testSet(it.name, ParserType.INTERNAL, it.openapi, model = "record", outputs = it.outputs, expected = it.expected) 54 | } 55 | 56 | val openapi31r = ALL_31.filter { it.modelTypes.contains(ModelTypes.RECORD) }.map { 57 | testSet(it.name, ParserType.INTERNAL, it.openapi, model = "record", outputs = it.outputs, expected = it.expected) 58 | } 59 | 60 | return swagger + openapi4j + openapi30 + openapi31 + openapi30r + openapi31r 61 | } 62 | -------------------------------------------------------------------------------- /src/testInt/kotlin/io/openapiprocessor/spring/ProcessorPendingSpec.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 https://github.com/openapi-processor/openapi-processor-spring 3 | * PDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package io.openapiprocessor.spring 7 | 8 | import io.kotest.core.spec.style.StringSpec 9 | import io.kotest.engine.spec.tempdir 10 | import io.kotest.matchers.booleans.shouldBeTrue 11 | import io.openapiprocessor.core.parser.ParserType.INTERNAL 12 | import io.openapiprocessor.test.* 13 | import io.openapiprocessor.test.API_30 14 | import io.openapiprocessor.test.TestSet 15 | 16 | /** 17 | * helper to run selected integration tests. 18 | */ 19 | class ProcessorPendingSpec: StringSpec({ 20 | 21 | for (testSet in sources()) { 22 | "native - $testSet".config(enabled = true) { 23 | val folder = tempdir() 24 | val reader = ResourceReader(ProcessorPendingSpec::class.java) 25 | 26 | val testFiles = TestFilesNative(folder, reader) 27 | val test = Test(testSet, testFiles) 28 | 29 | TestSetRunner(test, testSet) 30 | .run() 31 | .shouldBeTrue() 32 | } 33 | } 34 | }) 35 | 36 | private fun sources(): Collection { 37 | return listOf( 38 | testSet("params-enum", INTERNAL, API_30, model = "default", outputs = "outputs.yaml", expected = "outputs"), 39 | // testSet("params-request-body-multipart-mapping", INTERNAL, API_30, model = "default", outputs = "outputs.yaml", expected = "outputs"), 40 | // testSet("params-request-body-multipart-mapping", INTERNAL, API_31, model = "default", outputs = "outputs.yaml", expected = "outputs") 41 | ) 42 | } 43 | -------------------------------------------------------------------------------- /src/testInt/kotlin/io/openapiprocessor/spring/ProcessorTestSets.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 https://github.com/openapi-processor/openapi-processor-spring 3 | * PDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package io.openapiprocessor.spring 7 | 8 | import io.openapiprocessor.test.* 9 | 10 | val ALL_30: List = listOf( 11 | test30_D_("endpoint-http-mapping"), 12 | test30_DR("params-complex-data-types"), 13 | test30_D_("params-enum"), 14 | test30_D_("params-pageable-mapping"), 15 | test30_D_("params-path-simple-data-types"), 16 | test30_D_("params-query-annotate-simple-mapping"), 17 | test30_DR("params-request-body"), 18 | test30_D_("params-request-body-multipart-mapping"), 19 | test30_D_("params-simple-data-types"), 20 | test30_D_("response-status") 21 | ) 22 | 23 | val ALL_31: List = listOf( 24 | test31_D_("endpoint-http-mapping"), 25 | test31_DR("params-complex-data-types"), 26 | test31_D_("params-enum"), 27 | test31_D_("params-pageable-mapping"), 28 | test31_D_("params-path-simple-data-types"), 29 | test31_D_("params-query-annotate-simple-mapping"), 30 | test31_DR("params-request-body"), 31 | test31_DR("params-request-body-multipart-mapping"), 32 | test31_D_("params-simple-data-types"), 33 | test31_D_("response-status") 34 | ) 35 | -------------------------------------------------------------------------------- /src/testInt/kotlin/io/openapiprocessor/spring/ProcessorTestSetsSupport.kt: -------------------------------------------------------------------------------- 1 | /* 2 | * Copyright 2022 https://github.com/openapi-processor/openapi-processor-spring 3 | * PDX-License-Identifier: Apache-2.0 4 | */ 5 | 6 | package io.openapiprocessor.spring 7 | 8 | import io.openapiprocessor.core.parser.ParserType 9 | import io.openapiprocessor.spring.processor.SpringServiceV2 10 | import io.openapiprocessor.test.TestSet 11 | 12 | @Suppress("SameParameterValue") 13 | fun testSet( 14 | name: String, 15 | parser: ParserType, 16 | openapi: String = "openapi.yaml", 17 | model: String = "default", 18 | inputs: String = "inputs.yaml", 19 | outputs: String = "generated.yaml", 20 | expected: String = "generated" 21 | ): TestSet { 22 | val testSet = TestSet() 23 | testSet.name = name 24 | testSet.processor = SpringServiceV2(testMode = true) 25 | testSet.parser = parser.name 26 | testSet.modelType = model 27 | testSet.openapi = openapi 28 | testSet.inputs = inputs 29 | testSet.outputs = outputs 30 | testSet.expected = expected 31 | return testSet 32 | } 33 | -------------------------------------------------------------------------------- /src/testInt/resources/compile/Generated.java: -------------------------------------------------------------------------------- 1 | package generated.support; 2 | 3 | import java.lang.annotation.*; 4 | import static java.lang.annotation.ElementType.*; 5 | import static java.lang.annotation.RetentionPolicy.*; 6 | 7 | @Documented 8 | @Retention(CLASS) 9 | @Target({TYPE, METHOD}) 10 | @Generated(value = "openapi-processor-spring", version = "test") 11 | public @interface Generated { 12 | /** 13 | * The name of the source code generator, i.e. openapi-processor-*. 14 | * 15 | * @return name of the generator 16 | */ 17 | String value(); 18 | 19 | /** 20 | * @return version of the generator 21 | */ 22 | String version(); 23 | 24 | /** 25 | * The date & time of generation (ISO 8601) or "-" if no date was set. 26 | * 27 | * @return date of generation 28 | */ 29 | String date() default "-"; 30 | 31 | /** 32 | * The url of the generator. 33 | * 34 | * @return url of generator 35 | */ 36 | String url() default "https://openapiprocessor.io"; 37 | } 38 | -------------------------------------------------------------------------------- /src/testInt/resources/logback-test.xml: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/endpoint-http-mapping/inputs.yaml: -------------------------------------------------------------------------------- 1 | items: 2 | - inputs/openapi30.yaml 3 | - inputs/openapi31.yaml 4 | - inputs/mapping.yaml 5 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/endpoint-http-mapping/inputs/mapping.yaml: -------------------------------------------------------------------------------- 1 | openapi-processor-mapping: v4 2 | 3 | options: 4 | package-name: generated 5 | format-code: false 6 | 7 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/endpoint-http-mapping/inputs/openapi30.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: test endpoint http method mapping 4 | version: 1.0.0 5 | 6 | paths: 7 | /endpoint: 8 | 9 | delete: 10 | tags: 11 | - endpoint 12 | responses: 13 | '204': 14 | description: empty 15 | 16 | get: 17 | tags: 18 | - endpoint 19 | responses: 20 | '204': 21 | description: empty 22 | 23 | head: 24 | tags: 25 | - endpoint 26 | responses: 27 | '204': 28 | description: empty 29 | 30 | options: 31 | tags: 32 | - endpoint 33 | responses: 34 | '204': 35 | description: empty 36 | 37 | patch: 38 | tags: 39 | - endpoint 40 | responses: 41 | '204': 42 | description: empty 43 | 44 | post: 45 | tags: 46 | - endpoint 47 | responses: 48 | '204': 49 | description: empty 50 | 51 | put: 52 | tags: 53 | - endpoint 54 | responses: 55 | '204': 56 | description: empty 57 | 58 | trace: 59 | tags: 60 | - endpoint 61 | responses: 62 | '204': 63 | description: empty 64 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/endpoint-http-mapping/inputs/openapi31.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.1.0 2 | info: 3 | title: test endpoint http method mapping 4 | version: 1.0.0 5 | 6 | paths: 7 | /endpoint: 8 | 9 | 10 | delete: 11 | tags: 12 | - endpoint 13 | responses: 14 | '204': 15 | description: empty 16 | 17 | get: 18 | tags: 19 | - endpoint 20 | responses: 21 | '204': 22 | description: empty 23 | 24 | head: 25 | tags: 26 | - endpoint 27 | responses: 28 | '204': 29 | description: empty 30 | 31 | options: 32 | tags: 33 | - endpoint 34 | responses: 35 | '204': 36 | description: empty 37 | 38 | patch: 39 | tags: 40 | - endpoint 41 | responses: 42 | '204': 43 | description: empty 44 | 45 | post: 46 | tags: 47 | - endpoint 48 | responses: 49 | '204': 50 | description: empty 51 | 52 | put: 53 | tags: 54 | - endpoint 55 | responses: 56 | '204': 57 | description: empty 58 | 59 | trace: 60 | tags: 61 | - endpoint 62 | responses: 63 | '204': 64 | description: empty 65 | 66 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/endpoint-http-mapping/outputs.yaml: -------------------------------------------------------------------------------- 1 | prefix: generated 2 | items: 3 | - outputs/api/EndpointApi.java 4 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/endpoint-http-mapping/outputs/api/EndpointApi.java: -------------------------------------------------------------------------------- 1 | package generated.api; 2 | 3 | import generated.support.Generated; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.web.bind.annotation.DeleteMapping; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.PatchMapping; 8 | import org.springframework.web.bind.annotation.PostMapping; 9 | import org.springframework.web.bind.annotation.PutMapping; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.bind.annotation.RequestMethod; 12 | import org.springframework.web.bind.annotation.ResponseStatus; 13 | 14 | @Generated(value = "openapi-processor-spring", version = "test") 15 | public interface EndpointApi { 16 | 17 | @ResponseStatus(HttpStatus.NO_CONTENT) 18 | @DeleteMapping(path = "/endpoint") 19 | void deleteEndpoint(); 20 | 21 | @ResponseStatus(HttpStatus.NO_CONTENT) 22 | @GetMapping(path = "/endpoint") 23 | void getEndpoint(); 24 | 25 | @ResponseStatus(HttpStatus.NO_CONTENT) 26 | @RequestMapping(path = "/endpoint", method = RequestMethod.HEAD) 27 | void headEndpoint(); 28 | 29 | @ResponseStatus(HttpStatus.NO_CONTENT) 30 | @RequestMapping(path = "/endpoint", method = RequestMethod.OPTIONS) 31 | void optionsEndpoint(); 32 | 33 | @ResponseStatus(HttpStatus.NO_CONTENT) 34 | @PatchMapping(path = "/endpoint") 35 | void patchEndpoint(); 36 | 37 | @ResponseStatus(HttpStatus.NO_CONTENT) 38 | @PostMapping(path = "/endpoint") 39 | void postEndpoint(); 40 | 41 | @ResponseStatus(HttpStatus.NO_CONTENT) 42 | @PutMapping(path = "/endpoint") 43 | void putEndpoint(); 44 | 45 | @ResponseStatus(HttpStatus.NO_CONTENT) 46 | @RequestMapping(path = "/endpoint", method = RequestMethod.TRACE) 47 | void traceEndpoint(); 48 | 49 | } 50 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-complex-data-types/inputs.yaml: -------------------------------------------------------------------------------- 1 | items: 2 | - inputs/openapi30.yaml 3 | - inputs/openapi31.yaml 4 | - inputs/mapping.yaml 5 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-complex-data-types/inputs/mapping.yaml: -------------------------------------------------------------------------------- 1 | openapi-processor-mapping: v4 2 | 3 | options: 4 | package-name: generated 5 | format-code: false 6 | 7 | map: 8 | 9 | paths: 10 | 11 | /endpoint-map: 12 | types: 13 | - type: Props => java.util.Map 14 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-complex-data-types/inputs/openapi30.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: test complex parameters 4 | version: 1.0.0 5 | 6 | paths: 7 | 8 | /endpoint-object: 9 | get: 10 | description: < 11 | should be mapped to a `Props` pojo 12 | 13 | /endpoint-object?prop1=foo&prop2=bar 14 | 15 | parameters: 16 | - name: props 17 | description: query parameter object 18 | in: query 19 | schema: 20 | $ref: '#/components/schemas/Props' 21 | responses: 22 | '204': 23 | description: empty 24 | 25 | /endpoint-map: 26 | get: 27 | description: < 28 | should be mapped to a `Map` (from mapping) 29 | parameters: 30 | - name: props 31 | description: query parameter object 32 | in: query 33 | schema: 34 | $ref: '#/components/schemas/Props' 35 | responses: 36 | '204': 37 | description: empty 38 | 39 | components: 40 | 41 | schemas: 42 | 43 | Props: 44 | type: object 45 | properties: 46 | prop1: 47 | type: string 48 | prop2: 49 | type: string 50 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-complex-data-types/inputs/openapi31.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: test complex parameters 4 | version: 1.0.0 5 | 6 | paths: 7 | 8 | /endpoint-object: 9 | get: 10 | description: < 11 | should be mapped to a `Props` pojo 12 | 13 | /endpoint-object?prop1=foo&prop2=bar 14 | 15 | parameters: 16 | - name: props 17 | description: query parameter object 18 | in: query 19 | schema: 20 | $ref: '#/components/schemas/Props' 21 | responses: 22 | '204': 23 | description: empty 24 | 25 | /endpoint-map: 26 | get: 27 | description: < 28 | should be mapped to a `Map` (from mapping) 29 | parameters: 30 | - name: props 31 | description: query parameter object 32 | in: query 33 | schema: 34 | $ref: '#/components/schemas/Props' 35 | responses: 36 | '204': 37 | description: empty 38 | 39 | components: 40 | 41 | schemas: 42 | 43 | Props: 44 | type: object 45 | properties: 46 | prop1: 47 | type: string 48 | prop2: 49 | type: string 50 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-complex-data-types/outputs.yaml: -------------------------------------------------------------------------------- 1 | prefix: generated 2 | items: 3 | - outputs/api/Api.java 4 | - outputs/model//Props.java 5 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-complex-data-types/outputs/api/Api.java: -------------------------------------------------------------------------------- 1 | package generated.api; 2 | 3 | import generated.model.Props; 4 | import generated.support.Generated; 5 | import java.util.Map; 6 | import org.springframework.http.HttpStatus; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | import org.springframework.web.bind.annotation.RequestParam; 9 | import org.springframework.web.bind.annotation.ResponseStatus; 10 | 11 | @Generated(value = "openapi-processor-spring", version = "test") 12 | public interface Api { 13 | 14 | @ResponseStatus(HttpStatus.NO_CONTENT) 15 | @GetMapping(path = "/endpoint-object") 16 | void getEndpointObject(Props props); 17 | 18 | @ResponseStatus(HttpStatus.NO_CONTENT) 19 | @GetMapping(path = "/endpoint-map") 20 | void getEndpointMap(@RequestParam Map props); 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-complex-data-types/outputs/model/_default_/Props.java: -------------------------------------------------------------------------------- 1 | package generated.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import generated.support.Generated; 5 | 6 | @Generated(value = "openapi-processor-spring", version = "test") 7 | public class Props { 8 | 9 | @JsonProperty("prop1") 10 | private String prop1; 11 | 12 | @JsonProperty("prop2") 13 | private String prop2; 14 | 15 | public String getProp1() { 16 | return prop1; 17 | } 18 | 19 | public void setProp1(String prop1) { 20 | this.prop1 = prop1; 21 | } 22 | 23 | public String getProp2() { 24 | return prop2; 25 | } 26 | 27 | public void setProp2(String prop2) { 28 | this.prop2 = prop2; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-complex-data-types/outputs/model/_record_/Props.java: -------------------------------------------------------------------------------- 1 | package generated.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import generated.support.Generated; 5 | 6 | @Generated(value = "openapi-processor-spring", version = "test") 7 | public record Props( 8 | @JsonProperty("prop1") 9 | String prop1, 10 | 11 | @JsonProperty("prop2") 12 | String prop2 13 | ) {} 14 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-enum/inputs.yaml: -------------------------------------------------------------------------------- 1 | items: 2 | - inputs/openapi30.yaml 3 | - inputs/openapi31.yaml 4 | - inputs/mapping.yaml 5 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-enum/inputs/mapping.yaml: -------------------------------------------------------------------------------- 1 | openapi-processor-mapping: v5 2 | 3 | options: 4 | package-name: generated 5 | format-code: false 6 | enum-type: framework 7 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-enum/inputs/openapi30.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.3 2 | info: 3 | title: test enum parameters 4 | version: 1.0.0 5 | 6 | paths: 7 | 8 | /endpoint: 9 | get: 10 | tags: 11 | - enum 12 | parameters: 13 | - name: foo 14 | description: enum parameter 15 | in: query 16 | required: true 17 | schema: 18 | $ref: '#/components/schemas/Foo' 19 | responses: 20 | '204': 21 | description: empty 22 | 23 | components: 24 | schemas: 25 | 26 | Foo: 27 | type: string 28 | enum: 29 | - foo 30 | - foo-2 31 | - foo-foo 32 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-enum/inputs/openapi31.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.1.0 2 | info: 3 | title: test enum parameters 4 | version: 1.0.0 5 | 6 | paths: 7 | 8 | /endpoint: 9 | get: 10 | tags: 11 | - enum 12 | parameters: 13 | - name: foo 14 | description: enum parameter 15 | in: query 16 | required: true 17 | schema: 18 | $ref: '#/components/schemas/Foo' 19 | responses: 20 | '204': 21 | description: empty 22 | 23 | components: 24 | schemas: 25 | 26 | Foo: 27 | type: string 28 | enum: 29 | - foo 30 | - foo-2 31 | - foo-foo 32 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-enum/outputs.yaml: -------------------------------------------------------------------------------- 1 | prefix: generated 2 | items: 3 | - outputs/api/EnumApi.java 4 | - outputs/model//Foo.java 5 | - outputs/spring/package-info.java 6 | - outputs/spring/StringToEnumConverterFactory.java 7 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-enum/outputs/api/EnumApi.java: -------------------------------------------------------------------------------- 1 | package generated.api; 2 | 3 | import generated.model.Foo; 4 | import generated.support.Generated; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.RequestParam; 8 | import org.springframework.web.bind.annotation.ResponseStatus; 9 | 10 | @Generated(value = "openapi-processor-spring", version = "test") 11 | public interface EnumApi { 12 | 13 | @ResponseStatus(HttpStatus.NO_CONTENT) 14 | @GetMapping(path = "/endpoint") 15 | void getEndpoint(@RequestParam(name = "foo") Foo foo); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-enum/outputs/model/_default_/Foo.java: -------------------------------------------------------------------------------- 1 | package generated.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonCreator; 4 | import com.fasterxml.jackson.annotation.JsonValue; 5 | import generated.support.Generated; 6 | import java.util.function.Supplier; 7 | 8 | @Generated(value = "openapi-processor-spring", version = "test") 9 | public enum Foo implements Supplier { 10 | FOO("foo"), 11 | FOO_2("foo-2"), 12 | FOO_FOO("foo-foo"); 13 | 14 | private final String value; 15 | 16 | Foo(String value) { 17 | this.value = value; 18 | } 19 | 20 | @JsonValue 21 | public String get() { 22 | return this.value; 23 | } 24 | 25 | @JsonCreator 26 | public static Foo fromValue(String value) { 27 | for (Foo val : Foo.values()) { 28 | if (val.value.equals(value)) { 29 | return val; 30 | } 31 | } 32 | throw new IllegalArgumentException(value); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-enum/outputs/spring/StringToEnumConverterFactory.java: -------------------------------------------------------------------------------- 1 | package generated.spring; 2 | 3 | import org.springframework.core.convert.converter.Converter; 4 | import org.springframework.core.convert.converter.ConverterFactory; 5 | 6 | import java.util.EnumSet; 7 | import java.util.function.Supplier; 8 | 9 | public class StringToEnumConverterFactory & Supplier> 10 | implements ConverterFactory { 11 | 12 | @Override 13 | @SuppressWarnings({"unchecked", "rawtypes"}) 14 | public Converter getConverter(Class targetType) { 15 | return new StringToEnumConverter(targetType); 16 | } 17 | 18 | static class StringToEnumConverter & Supplier> implements Converter { 19 | 20 | private final Class enumType; 21 | 22 | public StringToEnumConverter(Class enumType) { 23 | this.enumType = enumType; 24 | } 25 | 26 | public T convert(String source) { 27 | String sourceValue = source.trim(); 28 | 29 | for (T e : EnumSet.allOf(enumType)) { 30 | if (e.get().equals(sourceValue)) { 31 | return e; 32 | } 33 | } 34 | 35 | throw new IllegalArgumentException(String.format("No enum constant of %s has the value %s", 36 | enumType.getCanonicalName(), 37 | sourceValue)); 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-enum/outputs/spring/package-info.java: -------------------------------------------------------------------------------- 1 | @org.springframework.lang.NonNullApi 2 | @org.springframework.lang.NonNullFields 3 | package generated.spring; 4 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-pageable-mapping/inputs.yaml: -------------------------------------------------------------------------------- 1 | items: 2 | - inputs/openapi30.yaml 3 | - inputs/openapi31.yaml 4 | - inputs/mapping.yaml 5 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-pageable-mapping/inputs/mapping.yaml: -------------------------------------------------------------------------------- 1 | openapi-processor-mapping: v4 2 | 3 | options: 4 | package-name: generated 5 | format-code: false 6 | 7 | map: 8 | result: org.springframework.http.ResponseEntity 9 | 10 | types: 11 | - type: Pageable => org.springframework.data.domain.Pageable 12 | - type: StringPage => org.springframework.data.domain.Page 13 | 14 | paths: 15 | 16 | /page-inline: 17 | 18 | parameters: 19 | - name: pageable => org.springframework.data.domain.Pageable 20 | 21 | responses: 22 | - content: application/json => org.springframework.data.domain.Page 23 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-pageable-mapping/inputs/openapi30.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: Spring Page/Pageable API 4 | version: 1.0.0 5 | 6 | paths: 7 | /page: 8 | get: 9 | parameters: 10 | - in: query 11 | name: pageable 12 | required: false 13 | schema: 14 | $ref: '#/components/schemas/Pageable' 15 | responses: 16 | '200': 17 | description: none 18 | content: 19 | application/json: 20 | schema: 21 | $ref: '#/components/schemas/StringPage' 22 | 23 | /page-inline: 24 | get: 25 | parameters: 26 | - in: query 27 | name: pageable 28 | required: false 29 | schema: 30 | type: object 31 | properties: 32 | page: 33 | type: integer 34 | size: 35 | type: integer 36 | responses: 37 | '200': 38 | description: none 39 | content: 40 | application/json: 41 | schema: 42 | type: object 43 | allOf: 44 | - $ref: '#/components/schemas/Page' 45 | - $ref: '#/components/schemas/StringContent' 46 | 47 | components: 48 | schemas: 49 | 50 | Pageable: 51 | description: minimal Pageable query parameters 52 | type: object 53 | properties: 54 | page: 55 | type: integer 56 | size: 57 | type: integer 58 | 59 | Page: 60 | description: minimal Page response without content property 61 | type: object 62 | properties: 63 | number: 64 | type: integer 65 | size: 66 | type: integer 67 | 68 | StringContent: 69 | description: specific content List of the Page response 70 | type: object 71 | properties: 72 | content: 73 | type: array 74 | items: 75 | type: string 76 | 77 | StringPage: 78 | description: typed Page 79 | type: object 80 | allOf: 81 | - $ref: '#/components/schemas/Page' 82 | - $ref: '#/components/schemas/StringContent' 83 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-pageable-mapping/inputs/openapi31.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.1.0 2 | info: 3 | title: Spring Page/Pageable API 4 | version: 1.0.0 5 | 6 | paths: 7 | /page: 8 | get: 9 | parameters: 10 | - in: query 11 | name: pageable 12 | required: false 13 | schema: 14 | $ref: '#/components/schemas/Pageable' 15 | responses: 16 | '200': 17 | description: none 18 | content: 19 | application/json: 20 | schema: 21 | $ref: '#/components/schemas/StringPage' 22 | 23 | /page-inline: 24 | get: 25 | parameters: 26 | - in: query 27 | name: pageable 28 | required: false 29 | schema: 30 | type: object 31 | properties: 32 | page: 33 | type: integer 34 | size: 35 | type: integer 36 | responses: 37 | '200': 38 | description: none 39 | content: 40 | application/json: 41 | schema: 42 | type: object 43 | allOf: 44 | - $ref: '#/components/schemas/Page' 45 | - $ref: '#/components/schemas/StringContent' 46 | 47 | components: 48 | schemas: 49 | 50 | Pageable: 51 | description: minimal Pageable query parameters 52 | type: object 53 | properties: 54 | page: 55 | type: integer 56 | size: 57 | type: integer 58 | 59 | Page: 60 | description: minimal Page response without content property 61 | type: object 62 | properties: 63 | number: 64 | type: integer 65 | size: 66 | type: integer 67 | 68 | StringContent: 69 | description: specific content List of the Page response 70 | type: object 71 | properties: 72 | content: 73 | type: array 74 | items: 75 | type: string 76 | 77 | StringPage: 78 | description: typed Page 79 | type: object 80 | allOf: 81 | - $ref: '#/components/schemas/Page' 82 | - $ref: '#/components/schemas/StringContent' 83 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-pageable-mapping/outputs.yaml: -------------------------------------------------------------------------------- 1 | prefix: generated 2 | items: 3 | - outputs/api/Api.java 4 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-pageable-mapping/outputs/api/Api.java: -------------------------------------------------------------------------------- 1 | package generated.api; 2 | 3 | import generated.support.Generated; 4 | import org.springframework.data.domain.Page; 5 | import org.springframework.data.domain.Pageable; 6 | import org.springframework.http.ResponseEntity; 7 | import org.springframework.web.bind.annotation.GetMapping; 8 | 9 | @Generated(value = "openapi-processor-spring", version = "test") 10 | public interface Api { 11 | 12 | @GetMapping(path = "/page", produces = {"application/json"}) 13 | ResponseEntity> getPage(Pageable pageable); 14 | 15 | @GetMapping(path = "/page-inline", produces = {"application/json"}) 16 | ResponseEntity> getPageInline(Pageable pageable); 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-path-simple-data-types/inputs.yaml: -------------------------------------------------------------------------------- 1 | items: 2 | - inputs/mapping.yaml 3 | - inputs/openapi30.yaml 4 | - inputs/openapi31.yaml 5 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-path-simple-data-types/inputs/mapping.yaml: -------------------------------------------------------------------------------- 1 | openapi-processor-mapping: v4 2 | 3 | options: 4 | package-name: generated 5 | format-code: false 6 | 7 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-path-simple-data-types/inputs/openapi30.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: test simple path parameters 4 | version: 1.0.0 5 | 6 | paths: 7 | /endpoint/{foo}: 8 | get: 9 | tags: 10 | - endpoint 11 | parameters: 12 | - name: foo 13 | description: path, required, string 14 | in: path 15 | required: true 16 | schema: 17 | type: string 18 | responses: 19 | '204': 20 | description: empty 21 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-path-simple-data-types/inputs/openapi31.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.1.0 2 | info: 3 | title: test simple path parameters 4 | version: 1.0.0 5 | 6 | paths: 7 | /endpoint/{foo}: 8 | get: 9 | tags: 10 | - endpoint 11 | parameters: 12 | - name: foo 13 | description: path, required, string 14 | in: path 15 | required: true 16 | schema: 17 | type: string 18 | responses: 19 | '204': 20 | description: empty 21 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-path-simple-data-types/outputs.yaml: -------------------------------------------------------------------------------- 1 | prefix: generated 2 | items: 3 | - outputs/api/EndpointApi.java 4 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-path-simple-data-types/outputs/api/EndpointApi.java: -------------------------------------------------------------------------------- 1 | package generated.api; 2 | 3 | import generated.support.Generated; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.PathVariable; 7 | import org.springframework.web.bind.annotation.ResponseStatus; 8 | 9 | @Generated(value = "openapi-processor-spring", version = "test") 10 | public interface EndpointApi { 11 | 12 | @ResponseStatus(HttpStatus.NO_CONTENT) 13 | @GetMapping(path = "/endpoint/{foo}") 14 | void getEndpointFoo(@PathVariable(name = "foo") String foo); 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-query-annotate-simple-mapping/inputs.yaml: -------------------------------------------------------------------------------- 1 | items: 2 | - inputs/openapi30.yaml 3 | - inputs/openapi31.yaml 4 | - inputs/mapping.yaml 5 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-query-annotate-simple-mapping/inputs/mapping.yaml: -------------------------------------------------------------------------------- 1 | openapi-processor-mapping: v2 2 | 3 | options: 4 | package-name: generated 5 | format-code: false 6 | 7 | map: 8 | types: 9 | - type: string:uuid => java.util.UUID 10 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-query-annotate-simple-mapping/inputs/openapi30.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: test simple data type mapping 4 | version: 1.0.0 5 | 6 | paths: 7 | 8 | /foo: 9 | get: 10 | parameters: 11 | - name: id 12 | description: mapped to java UUID 13 | in: query 14 | required: true 15 | schema: 16 | type: string 17 | format: uuid 18 | responses: 19 | '204': 20 | description: empty 21 | 22 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-query-annotate-simple-mapping/inputs/openapi31.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.1.0 2 | info: 3 | title: test simple data type mapping 4 | version: 1.0.0 5 | 6 | paths: 7 | 8 | /foo: 9 | get: 10 | parameters: 11 | - name: id 12 | description: mapped to java UUID 13 | in: query 14 | required: true 15 | schema: 16 | type: string 17 | format: uuid 18 | responses: 19 | '204': 20 | description: empty 21 | 22 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-query-annotate-simple-mapping/outputs.yaml: -------------------------------------------------------------------------------- 1 | prefix: generated 2 | items: 3 | - outputs/api/Api.java 4 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-query-annotate-simple-mapping/outputs/api/Api.java: -------------------------------------------------------------------------------- 1 | package generated.api; 2 | 3 | import generated.support.Generated; 4 | import java.util.UUID; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.web.bind.annotation.GetMapping; 7 | import org.springframework.web.bind.annotation.RequestParam; 8 | import org.springframework.web.bind.annotation.ResponseStatus; 9 | 10 | @Generated(value = "openapi-processor-spring", version = "test") 11 | public interface Api { 12 | 13 | @ResponseStatus(HttpStatus.NO_CONTENT) 14 | @GetMapping(path = "/foo") 15 | void getFoo(@RequestParam(name = "id") UUID id); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-request-body-multipart-mapping/inputs.yaml: -------------------------------------------------------------------------------- 1 | items: 2 | - inputs/openapi30.yaml 3 | - inputs/openapi31.yaml 4 | - inputs/mapping.yaml 5 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-request-body-multipart-mapping/inputs/mapping.yaml: -------------------------------------------------------------------------------- 1 | openapi-processor-mapping: v2 2 | 3 | options: 4 | package-name: generated 5 | format-code: false 6 | 7 | map: 8 | result: org.springframework.http.ResponseEntity 9 | 10 | types: 11 | - type: string:binary => org.springframework.web.multipart.MultipartFile 12 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-request-body-multipart-mapping/inputs/openapi30.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: params-request-body-multipart-form-data 4 | version: 1.0.0 5 | 6 | paths: 7 | /multipart/single-file: 8 | post: 9 | requestBody: 10 | required: true 11 | content: 12 | multipart/form-data: 13 | schema: 14 | type: object 15 | properties: 16 | file: 17 | type: string 18 | format: binary 19 | other: 20 | type: string 21 | responses: 22 | '204': 23 | description: empty 24 | 25 | /multipart/multiple-files: 26 | post: 27 | requestBody: 28 | required: true 29 | content: 30 | multipart/form-data: 31 | schema: 32 | type: object 33 | properties: 34 | files: 35 | type: array 36 | items: 37 | type: string 38 | format: binary 39 | other: 40 | type: string 41 | responses: 42 | '204': 43 | description: empty 44 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-request-body-multipart-mapping/inputs/openapi31.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.1.0 2 | info: 3 | title: params-request-body-multipart-form-data 4 | version: 1.0.0 5 | 6 | paths: 7 | /multipart/single-file: 8 | post: 9 | requestBody: 10 | required: true 11 | content: 12 | multipart/form-data: 13 | schema: 14 | type: object 15 | properties: 16 | file: 17 | type: string 18 | format: binary 19 | other: 20 | type: string 21 | responses: 22 | '204': 23 | description: empty 24 | 25 | /multipart/multiple-files: 26 | post: 27 | requestBody: 28 | required: true 29 | content: 30 | multipart/form-data: 31 | schema: 32 | type: object 33 | properties: 34 | files: 35 | type: array 36 | items: 37 | type: string 38 | format: binary 39 | other: 40 | type: string 41 | responses: 42 | '204': 43 | description: empty 44 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-request-body-multipart-mapping/outputs.yaml: -------------------------------------------------------------------------------- 1 | prefix: generated 2 | items: 3 | - outputs/api/Api.java 4 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-request-body-multipart-mapping/outputs/api/Api.java: -------------------------------------------------------------------------------- 1 | package generated.api; 2 | 3 | import generated.support.Generated; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.http.ResponseEntity; 6 | import org.springframework.web.bind.annotation.PostMapping; 7 | import org.springframework.web.bind.annotation.RequestParam; 8 | import org.springframework.web.bind.annotation.ResponseStatus; 9 | import org.springframework.web.multipart.MultipartFile; 10 | 11 | @Generated(value = "openapi-processor-spring", version = "test") 12 | public interface Api { 13 | 14 | @ResponseStatus(HttpStatus.NO_CONTENT) 15 | @PostMapping(path = "/multipart/single-file", consumes = {"multipart/form-data"}) 16 | ResponseEntity postMultipartSingleFile( 17 | @RequestParam(name = "file") MultipartFile file, 18 | @RequestParam(name = "other") String other); 19 | 20 | @ResponseStatus(HttpStatus.NO_CONTENT) 21 | @PostMapping(path = "/multipart/multiple-files", consumes = {"multipart/form-data"}) 22 | ResponseEntity postMultipartMultipleFiles( 23 | @RequestParam(name = "files") MultipartFile[] files, 24 | @RequestParam(name = "other") String other); 25 | 26 | } 27 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-request-body/inputs.yaml: -------------------------------------------------------------------------------- 1 | items: 2 | - inputs/mapping.yaml 3 | - inputs/openapi30.yaml 4 | - inputs/openapi31.yaml 5 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-request-body/inputs/mapping.yaml: -------------------------------------------------------------------------------- 1 | openapi-processor-mapping: v4 2 | 3 | options: 4 | package-name: generated 5 | format-code: false 6 | 7 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-request-body/inputs/openapi30.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: test request body parameters 4 | version: 1.0.0 5 | 6 | paths: 7 | /book: 8 | post: 9 | requestBody: 10 | content: 11 | application/json: 12 | schema: 13 | $ref: '#/components/schemas/Book' 14 | required: true 15 | responses: 16 | '201': 17 | description: created book 18 | content: 19 | application/json: 20 | schema: 21 | $ref: '#/components/schemas/Book' 22 | 23 | components: 24 | schemas: 25 | Book: 26 | type: object 27 | properties: 28 | isbn: 29 | type: string 30 | title: 31 | type: string 32 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-request-body/inputs/openapi31.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.1.0 2 | info: 3 | title: test request body parameters 4 | version: 1.0.0 5 | 6 | paths: 7 | /book: 8 | post: 9 | requestBody: 10 | content: 11 | application/json: 12 | schema: 13 | $ref: '#/components/schemas/Book' 14 | required: true 15 | responses: 16 | '201': 17 | description: created book 18 | content: 19 | application/json: 20 | schema: 21 | $ref: '#/components/schemas/Book' 22 | 23 | components: 24 | schemas: 25 | Book: 26 | type: object 27 | properties: 28 | isbn: 29 | type: string 30 | title: 31 | type: string 32 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-request-body/outputs.yaml: -------------------------------------------------------------------------------- 1 | prefix: generated 2 | items: 3 | - outputs/api/Api.java 4 | - outputs/model//Book.java 5 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-request-body/outputs/api/Api.java: -------------------------------------------------------------------------------- 1 | package generated.api; 2 | 3 | import generated.model.Book; 4 | import generated.support.Generated; 5 | import org.springframework.http.HttpStatus; 6 | import org.springframework.web.bind.annotation.PostMapping; 7 | import org.springframework.web.bind.annotation.RequestBody; 8 | import org.springframework.web.bind.annotation.ResponseStatus; 9 | 10 | @Generated(value = "openapi-processor-spring", version = "test") 11 | public interface Api { 12 | 13 | @ResponseStatus(HttpStatus.CREATED) 14 | @PostMapping(path = "/book", consumes = {"application/json"}, produces = {"application/json"}) 15 | Book postBook(@RequestBody Book body); 16 | 17 | } 18 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-request-body/outputs/model/_default_/Book.java: -------------------------------------------------------------------------------- 1 | package generated.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import generated.support.Generated; 5 | 6 | @Generated(value = "openapi-processor-spring", version = "test") 7 | public class Book { 8 | 9 | @JsonProperty("isbn") 10 | private String isbn; 11 | 12 | @JsonProperty("title") 13 | private String title; 14 | 15 | public String getIsbn() { 16 | return isbn; 17 | } 18 | 19 | public void setIsbn(String isbn) { 20 | this.isbn = isbn; 21 | } 22 | 23 | public String getTitle() { 24 | return title; 25 | } 26 | 27 | public void setTitle(String title) { 28 | this.title = title; 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-request-body/outputs/model/_record_/Book.java: -------------------------------------------------------------------------------- 1 | package generated.model; 2 | 3 | import com.fasterxml.jackson.annotation.JsonProperty; 4 | import generated.support.Generated; 5 | 6 | @Generated(value = "openapi-processor-spring", version = "test") 7 | public record Book( 8 | @JsonProperty("isbn") 9 | String isbn, 10 | 11 | @JsonProperty("title") 12 | String title 13 | ) {} 14 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-simple-data-types/inputs.yaml: -------------------------------------------------------------------------------- 1 | items: 2 | - inputs/mapping.yaml 3 | - inputs/openapi30.yaml 4 | - inputs/openapi31.yaml 5 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-simple-data-types/inputs/mapping.yaml: -------------------------------------------------------------------------------- 1 | openapi-processor-mapping: v4 2 | 3 | options: 4 | package-name: generated 5 | format-code: false 6 | 7 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-simple-data-types/inputs/openapi30.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: test simple parameters 4 | version: 1.0.0 5 | 6 | paths: 7 | /endpoint: 8 | get: 9 | tags: 10 | - endpoint 11 | parameters: 12 | - name: foo 13 | description: query, required, string 14 | in: query 15 | required: true 16 | schema: 17 | type: string 18 | responses: 19 | '204': 20 | description: empty 21 | 22 | /endpoint-optional: 23 | get: 24 | tags: 25 | - endpoint 26 | parameters: 27 | - name: foo 28 | description: query, not required, string 29 | in: query 30 | required: false 31 | schema: 32 | type: string 33 | default: bar 34 | responses: 35 | '204': 36 | description: empty 37 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-simple-data-types/inputs/openapi31.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.1.0 2 | info: 3 | title: test simple parameters 4 | version: 1.0.0 5 | 6 | paths: 7 | /endpoint: 8 | get: 9 | tags: 10 | - endpoint 11 | parameters: 12 | - name: foo 13 | description: query, required, string 14 | in: query 15 | required: true 16 | schema: 17 | type: string 18 | responses: 19 | '204': 20 | description: empty 21 | 22 | /endpoint-optional: 23 | get: 24 | tags: 25 | - endpoint 26 | parameters: 27 | - name: foo 28 | description: query, not required, string 29 | in: query 30 | required: false 31 | schema: 32 | type: string 33 | default: bar 34 | responses: 35 | '204': 36 | description: empty 37 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-simple-data-types/outputs.yaml: -------------------------------------------------------------------------------- 1 | prefix: generated 2 | items: 3 | - outputs/api/EndpointApi.java 4 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/params-simple-data-types/outputs/api/EndpointApi.java: -------------------------------------------------------------------------------- 1 | package generated.api; 2 | 3 | import generated.support.Generated; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.RequestParam; 7 | import org.springframework.web.bind.annotation.ResponseStatus; 8 | 9 | @Generated(value = "openapi-processor-spring", version = "test") 10 | public interface EndpointApi { 11 | 12 | @ResponseStatus(HttpStatus.NO_CONTENT) 13 | @GetMapping(path = "/endpoint") 14 | void getEndpoint(@RequestParam(name = "foo") String foo); 15 | 16 | @ResponseStatus(HttpStatus.NO_CONTENT) 17 | @GetMapping(path = "/endpoint-optional") 18 | void getEndpointOptional(@RequestParam(name = "foo", required = false, defaultValue = "bar") String foo); 19 | 20 | } 21 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/response-status/inputs.yaml: -------------------------------------------------------------------------------- 1 | prefix: api 2 | items: 3 | - inputs/openapi30.yaml 4 | - inputs/openapi31.yaml 5 | - inputs/mapping.yaml 6 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/response-status/inputs/mapping.yaml: -------------------------------------------------------------------------------- 1 | openapi-processor-mapping: v13 2 | 3 | options: 4 | package-name: generated 5 | format-code: false 6 | 7 | map: 8 | result-style: success 9 | result-status: true 10 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/response-status/inputs/openapi30.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.0.2 2 | info: 3 | title: response status 4 | version: 1.0.0 5 | 6 | paths: 7 | 8 | /foo: 9 | get: 10 | responses: 11 | '204': 12 | description: empty 13 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/response-status/inputs/openapi31.yaml: -------------------------------------------------------------------------------- 1 | openapi: 3.1.0 2 | info: 3 | title: response status 4 | version: 1.0.0 5 | 6 | paths: 7 | 8 | /foo: 9 | get: 10 | responses: 11 | '204': 12 | description: empty 13 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/response-status/outputs.yaml: -------------------------------------------------------------------------------- 1 | prefix: generated 2 | items: 3 | - outputs/api/Api.java 4 | -------------------------------------------------------------------------------- /src/testInt/resources/tests/response-status/outputs/api/Api.java: -------------------------------------------------------------------------------- 1 | package generated.api; 2 | 3 | import generated.support.Generated; 4 | import org.springframework.http.HttpStatus; 5 | import org.springframework.web.bind.annotation.GetMapping; 6 | import org.springframework.web.bind.annotation.ResponseStatus; 7 | 8 | @Generated(value = "openapi-processor-spring", version = "test") 9 | public interface Api { 10 | 11 | @ResponseStatus(HttpStatus.NO_CONTENT) 12 | @GetMapping(path = "/foo") 13 | void getFoo(); 14 | 15 | } 16 | --------------------------------------------------------------------------------