diff --git a/docs/integration.md b/docs/integration.md index b0f4ed335ed..99c76b25f92 100644 --- a/docs/integration.md +++ b/docs/integration.md @@ -1,8 +1,19 @@ ## Workflow Integration (Maven, Github, CI/CD) +### Gradle Integration + +See the [openapi-generator-gradle-plugin README](../modules/openapi-generator-gradle-plugin/README.adoc) for details related to configuring and using the Gradle Plugin. + +Supported tasks include: + +* Listing generators +* Validation of Open API 2.0 and 3.0 Specs +* Generating "Meta" generators +* Generating all generators supported by OpenAPI Generator + ### Maven Integration -You can use the [openapi-generator-maven-plugin](../modules/openapi-generator-maven-plugin/README.md) for integrating with your workflow, and generating any codegen target. +See the [openapi-generator-maven-plugin README](../modules/openapi-generator-maven-plugin/README.md) for details related to configuring and using the Maven Plugin. ### GitHub Integration diff --git a/modules/openapi-generator-gradle-plugin/.gitignore b/modules/openapi-generator-gradle-plugin/.gitignore new file mode 100644 index 00000000000..67bffab9eec --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/.gitignore @@ -0,0 +1,152 @@ + +# Created by https://www.gitignore.io/api/gradle,kotlin,intellij,linux,osx + +### Intellij ### +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and Webstorm +# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839 + +# User-specific stuff: +.idea/**/workspace.xml +.idea/**/tasks.xml +.idea/dictionaries + +# Sensitive or high-churn files: +.idea/**/dataSources/ +.idea/**/dataSources.ids +.idea/**/dataSources.xml +.idea/**/dataSources.local.xml +.idea/**/sqlDataSources.xml +.idea/**/dynamic.xml +.idea/**/uiDesigner.xml + +# Gradle: +.idea/**/gradle.xml +.idea/**/libraries + +# CMake +cmake-build-debug/ + +# Mongo Explorer plugin: +.idea/**/mongoSettings.xml + +## File-based project format: +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Cursive Clojure plugin +.idea/replstate.xml + +# Ruby plugin and RubyMine +/.rakeTasks + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +fabric.properties + +### Intellij Patch ### +# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721 + +# *.iml +# modules.xml +# .idea/misc.xml +# *.ipr + +# Sonarlint plugin +.idea/sonarlint + +### Kotlin ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### OSX ### +*.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### Gradle ### +.gradle +**/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Cache of project +.gradletasknamecache + +# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 +# gradle/wrapper/gradle-wrapper.properties + + +# End of https://www.gitignore.io/api/gradle,kotlin,intellij,linux,osx diff --git a/modules/openapi-generator-gradle-plugin/README.adoc b/modules/openapi-generator-gradle-plugin/README.adoc new file mode 100644 index 00000000000..93c46aa2ce6 --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/README.adoc @@ -0,0 +1,452 @@ += OpenAPI Generator Gradle Plugin + +This document describes the gradle plugin for OpenAPI Generator. + +== Tasks + +Tasks are listed under the "OpenAPI Tools" tasks heading. + + +.OpenAPI Tools Tasks +|=== +|task name |description + +|*openApiGenerate* +|Generate code via Open API Tools Generator for Open API 2.0 or 3.x specification documents. + +|*openApiGenerators* +|Lists generators available via Open API Generators. + +|*openApiMeta* +|Generates a new generator to be consumed via Open API Generator. + +|*openApiValidate* +|Validates an Open API 2.0 or 3.x specification document. +|=== + +== Plugin Setup + +[source,groovy] +---- +buildscript { + repositories { + mavenLocal() + mavenCentral() + } + dependencies { + classpath "org.openapitools:openapi-generator-gradle-plugin:3.0.0-SNAPSHOT" + } +} + +apply plugin: 'org.openapi.generator' +---- + +[NOTE] +==== +The gradle plugin is not currently published to https://plugins.gradle.org/m2/. +==== + +== Configuration + +=== openApiGenerate + +.Options +|=== +|Key |Data Type |Default |Description + +|verbose +|Boolean +|false +|The verbosity of generation + +|generatorName +|String +|None +|The name of the generator which will handle codegen. + +|outputDir +|String +|None +|The output target directory into which code will be generated. + +|inputSpec +|String +|None +|The Open API 2.0/3.x specification location. + +|templateDir +|String +|None +|The template directory holding a custom template. + +|auth +|String +|None +|Adds authorization headers when fetching the OpenAPI definitions remotely. Pass in a URL-encoded string of name:header with a comma separating multiple values. + +|systemProperties +|Map(String,String) +|None +|Sets specified system properties. + +|configFile +|String +|None +|Path to json configuration file. See OpenAPI Generator readme for structure details. + +|skipOverwrite +|Boolean +|false +|Specifies if the existing files should be overwritten during the generation. + +|apiPackage +|String +|(generator specific) +|Package for generated api classes. + +|modelPackage +|String +|(generator specific) +|Package for generated model classes. + +|modelNamePrefix +|String +|None +|Prefix that will be prepended to all model names. + +|modelNameSuffix +|String +|None +|Suffix that will be appended to all model names. + +|instantiationTypes +|Map(String,String) +|None +|Sets instantiation type mappings. + +|typeMappings +|Map(String,String) +|None +|Sets mappings between OpenAPI spec types and generated code types. + +|additionalProperties +|Map(String,String) +|None +|Sets additional properties that can be referenced by the mustache templates. + +|languageSpecificPrimitives +|List(String) +|None +|Specifies additional language specific primitive types in the format of type1,type2,type3,type3. For example: String,boolean,Boolean,Double. + +|importMappings +|Map(String,String) +|None +|Specifies mappings between a given class and the import that should be used for that class. + +|invokerPackage +|String +|None +|Root package for generated code. + +|groupId +|String +|None +|GroupId in generated pom.xml/build.gradle or other build script. Language-specific conversions occur in non-jvm generators. + +|id +|String +|None +|ArtifactId in generated pom.xml/build.gradle or other build script. Language-specific conversions occur in non-jvm generators. + +|version +|String +|None +|Artifact version in generated pom.xml/build.gradle or other build script. Language-specific conversions occur in non-jvm generators. + +|library +|String +|None +|Reference the library template (sub-template) of a generator. + +|gitUserId +|String +|None +|Git user ID, e.g. openapitools. + +|gitRepoId +|String +|None +|Git repo ID, e.g. openapi-generator. + +|releaseNote +|String +|'Minor update' +|Release note. + +|httpUserAgent +|String +|None +|HTTP user agent, e.g. codegen_csharp_api_client. Generator default is 'OpenAPI-Generator/{packageVersion}}/{language}', but may be generator-specific. + +|reservedWordsMappings +|Map(String,String) +|None +|Specifies how a reserved name should be escaped to. Otherwise, the default _ is used. + +|ignoreFileOverride +|String +|None +|Specifies an override location for the .openapi-generator-ignore file. Most useful on initial generation. + +|removeOperationIdPrefix +|Boolean +|false +|Remove prefix of operationId, e.g. config_getId => getId. + +|apiFilesConstrainedTo +|List(String) +|None +|Defines which API-related files should be generated. This allows you to create a subset of generated files (or none at all). See Note Below. + +|modelFilesConstrainedTo +|List(String) +|None +|Defines which model-related files should be generated. This allows you to create a subset of generated files (or none at all). See Note Below. + +|supportingFilesConstrainedTo +|List(String) +|None +|Defines which supporting files should be generated. This allows you to create a subset of generated files (or none at all). See Note Below. + +|generateModelTests +|Boolean +|true +|Defines whether or not model-related _test_ files should be generated. + +|generateModelDocumentation +|Boolean +|true +|Defines whether or not model-related _documentation_ files should be generated. + +|generateApiTests +|Boolean +|true +|Defines whether or not api-related _test_ files should be generated. + +|generateApiDocumentation +|Boolean +|true +|Defines whether or not api-related _documentation_ files should be generated. + +|withXml +|Boolean +|false +|A special-case setting which configures some generators with XML support. In some cases, this forces json OR xml, so the default here is false. + +|configOptions +|Map(String,String) +|None +|A map of options specific to a generator. + +|=== + +[NOTE] +==== +Configuring any one of `apiFilesConstrainedTo`, `modelFilesConstrainedTo`, or `supportingFilesConstrainedTo` results +in others being disabled. That is, OpenAPI Generator considers any one of these to define a subset of generation. + +For more control over generation of individual files, configure an ignore file and refer to it via `ignoreFileOverride`. +==== + +=== openApiValidate + +.Options +|=== +|Key |Data Type |Default |Description + +|inputSpec +|String +|None +|The input specification to validate. Supports all formats supported by the Parser. + +|=== + +=== openApiMeta + +.Options +|=== +|Key |Data Type |Default |Description + +|generatorName +|String +|None +|The human-readable generator name of the newly created template generator. + +|packageName +|String +|org.openapitools.codegen +|The packageName generatorName to put the main class into. + +|outputFolder +|String +|Current Directory +|Where to write the generated files + +|=== + + +== Examples + +=== openApiGenerate + +This task exposes all options available via OpenAPI Generator CLI and the OpenAPI Generator Maven Plugin. + +.in build.gradle +[source,groovy] +---- +openApiGenerate { + generatorName = "kotlin" + inputSpec = "$rootDir/specs/petstore-v3.0.yaml".toString() + outputDir = "$buildDir/generated".toString() + apiPackage = "org.openapi.example.api" + invokerPackage = "org.openapi.example.invoker" + modelPackage = "org.openapi.example.model" + modelFilesConstrainedTo = [ + "Error" + ] + configOptions = [ + dateLibrary: "java8" + ] +} +---- + +The above code demonstrates configuration of global options as well as generator-specific config options. + +=== openApiGenerators + +This is an output-only listing task. There's no need to add configuration to build.gradle. + +.Example output of openApiGenerators task +[source,terminal] +---- +$ ./gradlew openApiGenerators + +> Task :openApiGenerators +The following generators are available: + +CLIENT generators: + - ada +… + +SERVER generators: + - ada-server +… + +DOCUMENTATION generators: + - cwiki +… + +CONFIG generators: + - apache2 + +OTHER generators: +… + +BUILD SUCCESSFUL in 0s +1 actionable task: 1 executed +---- + +[NOTE] +==== +Generator type listings in the above example have been truncated to avoid potential confusion with changing generator support. + +Please run the above task to list all available generators. +==== + +=== openApiMeta + +.in build.gradle +[source,groovy] +---- +openApiMeta { + generatorName = "Jim" + packageName = "us.jimschubert.example" +} +---- + +.Example output of openApiMeta task +[source,terminal] +---- +$ ./gradlew openApiMeta + +> Task :openApiMeta +Wrote file to /Users/jim/my_project/pom.xml +Wrote file to /Users/jim/my_project/src/main/java/us/jimschubert/example/JimGenerator.java +Wrote file to /Users/jim/my_project/README.md +Wrote file to /Users/jim/my_project/src/main/resources/jim/api.mustache +Wrote file to /Users/jim/my_project/src/main/resources/jim/model.mustache +Wrote file to /Users/jim/my_project/src/main/resources/jim/myFile.mustache +Wrote file to /Users/jim/my_project/src/main/resources/META-INF/services/org.openapitools.codegen.CodegenConfig +Created generator JimGenerator + +BUILD SUCCESSFUL in 0s +1 actionable task: 1 executed +---- + + +=== openApiValidate + +.in buid.gradle +[source,groovy] +---- +openApiValidate { + inputSpec = "/src/openapi-generator/modules/openapi-generator/src/test/resources/3_0/petstore.yaml" +} +---- + +.Example output of openApiValidate task (success) +[source,terminal] +---- +$ ./gradlew openApiValidate --input=/Users/jim/projects/openapi-generator/modules/openapi-generator/src/test/resources/3_0/ping.yaml + +> Task :openApiValidate +Validating spec /Users/jim/projects/openapi-generator/modules/openapi-generator/src/test/resources/3_0/ping.yaml +Spec is valid. + +BUILD SUCCESSFUL in 0s +1 actionable task: 1 executed +---- + +.Example output of openApiValidate task (failure) +[source,terminal] +---- +$ ./gradlew openApiValidate + +> Task :openApiValidate FAILED +Validating spec /Users/jim/projects/openapi-generator/modules/openapi-generator/src/test/resources/3_0/petstore.yaml + +Spec is invalid. +Issues: + + attribute info is missing + + +FAILURE: Build failed with an exception. + +* What went wrong: +Execution failed for task ':openApiValidate'. +> Validation failed. + +* Try: +Run with --stacktrace option to get the stack trace. Run with --info or --debug option to get more log output. Run with --scan to get full insights. + +* Get more help at https://help.gradle.org + +---- + +.in terminal (alternate) +[source,terminal] +---- +$ ./gradlew openApiValidate --input=/Users/jim/projects/openapi-generator/modules/openapi-generator/src/test/resources/3_0/petstore.yaml +---- \ No newline at end of file diff --git a/modules/openapi-generator-gradle-plugin/build.gradle b/modules/openapi-generator-gradle-plugin/build.gradle new file mode 100644 index 00000000000..9392744804b --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/build.gradle @@ -0,0 +1,89 @@ +buildscript { + ext.kotlin_version = '1.2.41' + repositories { + mavenCentral() + maven { + url "https://plugins.gradle.org/m2/" + } + } + dependencies { + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + classpath "gradle.plugin.org.gradle.kotlin:gradle-kotlin-dsl-plugins:0.17.5" + } +} + +group 'org.openapitools' +// Shared OpenAPI Generator version be passed via command line arg as -PopenApiGeneratorVersion=VERSION +version "$openApiGeneratorVersion" +description = """ +This plugin supports common functionality found in Open API Generator CLI as a gradle plugin. + +This gives you the ability to generate client SDKs, documentation, new generators, and to validate Open API 2.0 and 3.x +specifications as part of your build. Other tasks are available as command line tasks. +""" + +apply plugin: 'java-gradle-plugin' +apply plugin: 'maven-publish' +apply plugin: 'kotlin' +apply plugin: "org.gradle.kotlin.kotlin-dsl" + +sourceCompatibility = 1.8 +targetCompatibility = 1.8 + +repositories { + mavenCentral() + mavenLocal() +} + +dependencies { + compile gradleApi() + // Shared OpenAPI Generator version be passed via command line arg as -PopenApiGeneratorVersion=VERSION + compile "org.openapitools:openapi-generator:$openApiGeneratorVersion" + compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version" + + testCompile 'org.testng:testng:6.9.6', + "org.jetbrains.kotlin:kotlin-test:$kotlin_version" + + testCompile "org.jetbrains.kotlin:kotlin-compiler-embeddable:$kotlin_version" +} + +test { + useTestNG() + testClassesDirs = files(project.tasks.compileTestKotlin.destinationDir) + testLogging.showStandardStreams = false + + beforeTest { descriptor -> + logger.lifecycle("Running test: " + descriptor) + } + + failFast = true + + onOutput { descriptor, event -> + // SLF4J may complain about multiple bindings dependign on how this is run. + // This is just a warning, but can make test output less readable. So we ignore it specifically. + if (!event.message.contains("SLF4J:")) { + logger.lifecycle("Test: " + descriptor + " produced standard out/err: " + event.message) + } + } +} + + +gradlePlugin { + plugins { + openApiGenerator { + id = 'org.openapi.generator' + implementationClass = 'org.openapitools.generator.gradle.plugin.OpenApiGeneratorPlugin' + } + } +} + +compileKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} +compileTestKotlin { + kotlinOptions { + jvmTarget = "1.8" + } +} \ No newline at end of file diff --git a/modules/openapi-generator-gradle-plugin/gradle.properties b/modules/openapi-generator-gradle-plugin/gradle.properties new file mode 100644 index 00000000000..8d733fd5b05 --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/gradle.properties @@ -0,0 +1 @@ +openApiGeneratorVersion=3.0.0-SNAPSHOT diff --git a/modules/openapi-generator-gradle-plugin/gradle/wrapper/gradle-wrapper.jar b/modules/openapi-generator-gradle-plugin/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000000..91ca28c8b80 Binary files /dev/null and b/modules/openapi-generator-gradle-plugin/gradle/wrapper/gradle-wrapper.jar differ diff --git a/modules/openapi-generator-gradle-plugin/gradle/wrapper/gradle-wrapper.properties b/modules/openapi-generator-gradle-plugin/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000000..16d28051c9c --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-4.7-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/modules/openapi-generator-gradle-plugin/gradlew b/modules/openapi-generator-gradle-plugin/gradlew new file mode 100755 index 00000000000..cccdd3d517f --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/gradlew @@ -0,0 +1,172 @@ +#!/usr/bin/env sh + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=$(save "$@") + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong +if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then + cd "$(dirname "$0")" +fi + +exec "$JAVACMD" "$@" diff --git a/modules/openapi-generator-gradle-plugin/gradlew.bat b/modules/openapi-generator-gradle-plugin/gradlew.bat new file mode 100644 index 00000000000..e95643d6a2c --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/gradlew.bat @@ -0,0 +1,84 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/modules/openapi-generator-gradle-plugin/pom.xml b/modules/openapi-generator-gradle-plugin/pom.xml new file mode 100644 index 00000000000..28643d2a4a8 --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/pom.xml @@ -0,0 +1,59 @@ + + + org.openapitools + openapi-generator-project + 3.0.0-SNAPSHOT + ../.. + + 4.0.0 + + openapi-generator-gradle-plugin + + openapi-generator-gradle-plugin (gradle-plugin) + + + + org.openapitools + openapi-generator + ${project.version} + + + + + + + + + org.fortasoft + gradle-maven-plugin + 1.0.8 + + 4.7 + + -P openApiGeneratorVersion=${project.version} + + + + + install + + + invoke + + + + + clean + build + publishToMavenLocal + + + + + + + + + diff --git a/modules/openapi-generator-gradle-plugin/samples/local-spec/.gitignore b/modules/openapi-generator-gradle-plugin/samples/local-spec/.gitignore new file mode 100644 index 00000000000..fa897594662 --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/samples/local-spec/.gitignore @@ -0,0 +1,3 @@ +.gradle/ +src/ +build/ diff --git a/modules/openapi-generator-gradle-plugin/samples/local-spec/README.md b/modules/openapi-generator-gradle-plugin/samples/local-spec/README.md new file mode 100644 index 00000000000..278bddb12b5 --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/samples/local-spec/README.md @@ -0,0 +1,14 @@ +# Local Spec Sample + +This example assumes you have Gradle 4.7+ installed. No gradle wrapper is provided in samples. + +First, publish the openapi-generator-gradle-plugin locally via `sh gradlew build publishToMavenLocal` in the module directory. + +Then, run the following tasks in this example directory. + +```bash +gradle openApiGenerate +gradle openApiMeta +gradle openApiValidate +gradle buildGoSdk +``` diff --git a/modules/openapi-generator-gradle-plugin/samples/local-spec/build.gradle b/modules/openapi-generator-gradle-plugin/samples/local-spec/build.gradle new file mode 100644 index 00000000000..52f80315398 --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/samples/local-spec/build.gradle @@ -0,0 +1,49 @@ +buildscript { + repositories { + mavenLocal() + mavenCentral() + maven { + url "https://plugins.gradle.org/m2/" + } + } + dependencies { + classpath "org.openapitools:openapi-generator-gradle-plugin:3.0.0-SNAPSHOT" + } +} + +apply plugin: 'org.openapi.generator' + +openApiMeta { + generatorName = "Sample" + packageName = "org.openapitools.example" + outputFolder = "$buildDir/meta".toString() +} + +openApiValidate { + inputSpec = "$rootDir/petstore-v3.0-invalid.yaml".toString() +} + +// Builds a Kotlin client by default. +openApiGenerate { + generatorName = "kotlin" + inputSpec = "$rootDir/petstore-v3.0.yaml".toString() + outputDir = "$buildDir/kotlin".toString() + apiPackage = "org.openapitools.example.api" + invokerPackage = "org.openapitools.example.invoker" + modelPackage = "org.openapitools.example.model" + configOptions = [ + dateLibrary: "java8" + ] +} + +task buildGoSdk(type: org.openapitools.generator.gradle.plugin.tasks.GenerateTask){ + generatorName = "go" + inputSpec = "$rootDir/petstore-v3.0.yaml".toString() + additionalProperties = [ + packageName: "petstore" + ] + outputDir = "$buildDir/go".toString() + configOptions = [ + dateLibrary: "threetenp" + ] +} diff --git a/modules/openapi-generator-gradle-plugin/samples/local-spec/petstore-v3.0-invalid.yaml b/modules/openapi-generator-gradle-plugin/samples/local-spec/petstore-v3.0-invalid.yaml new file mode 100644 index 00000000000..0f5c6fc2982 --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/samples/local-spec/petstore-v3.0-invalid.yaml @@ -0,0 +1,103 @@ +openapi: "3.0.0" +servers: + - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a pet + tags: + - pets + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /pets/{petId}: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + Pet: + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: "#/components/schemas/Pet" + Error: + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string diff --git a/modules/openapi-generator-gradle-plugin/samples/local-spec/petstore-v3.0.yaml b/modules/openapi-generator-gradle-plugin/samples/local-spec/petstore-v3.0.yaml new file mode 100644 index 00000000000..264dbeabff1 --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/samples/local-spec/petstore-v3.0.yaml @@ -0,0 +1,109 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /pets/{petId}: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + Pet: + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: "#/components/schemas/Pet" + Error: + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string diff --git a/modules/openapi-generator-gradle-plugin/samples/local-spec/settings.gradle b/modules/openapi-generator-gradle-plugin/samples/local-spec/settings.gradle new file mode 100644 index 00000000000..e69de29bb2d diff --git a/modules/openapi-generator-gradle-plugin/settings.gradle b/modules/openapi-generator-gradle-plugin/settings.gradle new file mode 100644 index 00000000000..563879e5d5d --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'openapi-generator-gradle-plugin' + diff --git a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/OpenApiGeneratorPlugin.kt b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/OpenApiGeneratorPlugin.kt new file mode 100644 index 00000000000..dda1bfc2310 --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/OpenApiGeneratorPlugin.kt @@ -0,0 +1,130 @@ +/* + * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openapitools.generator.gradle.plugin + +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.kotlin.dsl.invoke +import org.openapitools.generator.gradle.plugin.extensions.OpenApiGeneratorGenerateExtension +import org.openapitools.generator.gradle.plugin.extensions.OpenApiGeneratorMetaExtension +import org.openapitools.generator.gradle.plugin.extensions.OpenApiGeneratorValidateExtension +import org.openapitools.generator.gradle.plugin.tasks.GenerateTask +import org.openapitools.generator.gradle.plugin.tasks.GeneratorsTask +import org.openapitools.generator.gradle.plugin.tasks.MetaTask +import org.openapitools.generator.gradle.plugin.tasks.ValidateTask + +/** + * A plugin providing common Open API Generator use cases. + * + * @author Jim Schubert + */ +@Suppress("unused") +class OpenApiGeneratorPlugin : Plugin { + override fun apply(project: Project) { + project.run { + val meta = extensions.create( + "openApiMeta", + OpenApiGeneratorMetaExtension::class.java, + project + ) + + val validate = extensions.create( + "openApiValidate", + OpenApiGeneratorValidateExtension::class.java, + project + ) + + val generate = extensions.create( + "openApiGenerate", + OpenApiGeneratorGenerateExtension::class.java, + project + ) + + generate.outputDir.set("$buildDir/generate-resources/main") + + tasks { + "openApiGenerators"(GeneratorsTask::class) { + group = pluginGroup + description = "Lists generators available via Open API Generators." + } + "openApiMeta"(MetaTask::class) { + group = pluginGroup + description = "Generates a new generator to be consumed via Open API Generator." + + generatorName.set(meta.generatorName) + packageName.set(meta.packageName) + outputFolder.set(meta.outputFolder) + } + "openApiValidate"(ValidateTask::class) { + group = pluginGroup + description = "Validates an Open API 2.0 or 3.x specification document." + + inputSpec.set(validate.inputSpec) + } + "openApiGenerate"(GenerateTask::class) { + group = pluginGroup + description = "Generate code via Open API Tools Generator for Open API 2.0 or 3.x specification documents." + + verbose.set(generate.verbose) + generatorName.set(generate.generatorName) + outputDir.set(generate.outputDir) + inputSpec.set(generate.inputSpec) + templateDir.set(generate.templateDir) + auth.set(generate.auth) + systemProperties.set(generate.systemProperties) + configFile.set(generate.configFile) + skipOverwrite.set(generate.skipOverwrite) + apiPackage.set(generate.apiPackage) + modelPackage.set(generate.modelPackage) + modelNamePrefix.set(generate.modelNamePrefix) + modelNameSuffix.set(generate.modelNameSuffix) + instantiationTypes.set(generate.instantiationTypes) + typeMappings.set(generate.typeMappings) + additionalProperties.set(generate.additionalProperties) + languageSpecificPrimitives.set(generate.languageSpecificPrimitives) + importMappings.set(generate.importMappings) + invokerPackage.set(generate.invokerPackage) + groupId.set(generate.groupId) + id.set(generate.id) + version.set(generate.version) + library.set(generate.library) + gitUserId.set(generate.gitUserId) + gitRepoId.set(generate.gitRepoId) + releaseNote.set(generate.releaseNote) + httpUserAgent.set(generate.httpUserAgent) + reservedWordsMappings.set(generate.reservedWordsMappings) + ignoreFileOverride.set(generate.ignoreFileOverride) + removeOperationIdPrefix.set(generate.removeOperationIdPrefix) + apiFilesConstrainedTo.set(generate.apiFilesConstrainedTo) + modelFilesConstrainedTo.set(generate.modelFilesConstrainedTo) + supportingFilesConstrainedTo.set(generate.supportingFilesConstrainedTo) + generateModelTests.set(generate.generateModelTests) + generateModelDocumentation.set(generate.generateModelDocumentation) + generateApiTests.set(generate.generateApiTests) + generateApiDocumentation.set(generate.generateApiDocumentation) + withXml.set(generate.withXml) + configOptions.set(generate.configOptions) + } + } + } + } + + companion object { + const val pluginGroup = "OpenAPI Tools" + } +} + diff --git a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorGenerateExtension.kt b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorGenerateExtension.kt new file mode 100644 index 00000000000..a62efd0bc55 --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorGenerateExtension.kt @@ -0,0 +1,275 @@ +/* + * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openapitools.generator.gradle.plugin.extensions + +import org.gradle.api.Project +import org.gradle.kotlin.dsl.listProperty +import org.gradle.kotlin.dsl.property + +/** + * Gradle project level extension object definition for the generate task + * + * @author Jim Schubert + */ +open class OpenApiGeneratorGenerateExtension(project: Project) { + + /** + * The verbosity of generation + */ + val verbose = project.objects.property() + + /** + * The name of the generator which will handle codegen. (see "openApiGenerators" task) + */ + val generatorName = project.objects.property() + + /** + * The output target directory into which code will be generated. + */ + val outputDir = project.objects.property() + + /** + * The Open API 2.0/3.x specification location. + */ + val inputSpec = project.objects.property() + + /** + * The template directory holding a custom template. + */ + val templateDir = project.objects.property() + + /** + * Adds authorization headers when fetching the OpenAPI definitions remotely. + * Pass in a URL-encoded string of name:header with a comma separating multiple values + */ + val auth = project.objects.property() + + /** + * Sets specified system properties. + */ + val systemProperties = project.objects.property>() + + /** + * Path to json configuration file. + * File content should be in a json format { "optionKey":"optionValue", "optionKey1":"optionValue1"...} + * Supported options can be different for each language. Run config-help -g {generator name} command for language specific config options. + */ + val configFile = project.objects.property() + + /** + * Specifies if the existing files should be overwritten during the generation. + */ + val skipOverwrite = project.objects.property() + + /** + * Package for generated api classes + */ + val apiPackage = project.objects.property() + + /** + * Package for generated models + */ + val modelPackage = project.objects.property() + + /** + * Prefix that will be prepended to all model names. Default is the empty string. + */ + val modelNamePrefix = project.objects.property() + + /** + * Suffix that will be appended to all model names. Default is the empty string. + */ + val modelNameSuffix = project.objects.property() + + /** + * Sets instantiation type mappings. + */ + val instantiationTypes = project.objects.property>() + + /** + * Sets mappings between OpenAPI spec types and generated code types. + */ + val typeMappings = project.objects.property>() + + /** + * Sets additional properties that can be referenced by the mustache templates. + */ + val additionalProperties = project.objects.property>() + + /** + * Specifies additional language specific primitive types in the format of type1,type2,type3,type3. For example: String,boolean,Boolean,Double. + */ + val languageSpecificPrimitives = project.objects.listProperty() + + /** + * Specifies mappings between a given class and the import that should be used for that class. + */ + val importMappings = project.objects.property>() + + /** + * Root package for generated code. + */ + val invokerPackage = project.objects.property() + + /** + * GroupId in generated pom.xml/build.gradle or other build script. Language-specific conversions occur in non-jvm generators. + */ + val groupId = project.objects.property() + + /** + * ArtifactId in generated pom.xml/build.gradle or other build script. Language-specific conversions occur in non-jvm generators. + */ + val id = project.objects.property() + + /** + * Artifact version in generated pom.xml/build.gradle or other build script. Language-specific conversions occur in non-jvm generators. + */ + val version = project.objects.property() + + /** + * Reference the library template (sub-template) of a generator. + */ + val library = project.objects.property() + + /** + * Git user ID, e.g. openapitools. + */ + val gitUserId = project.objects.property() + + /** + * Git repo ID, e.g. openapi-generator. + */ + val gitRepoId = project.objects.property() + + /** + * Release note, default to 'Minor update'. + */ + val releaseNote = project.objects.property() + + /** + * HTTP user agent, e.g. codegen_csharp_api_client, default to 'OpenAPI-Generator/{packageVersion}}/{language}' + */ + val httpUserAgent = project.objects.property() + + /** + * Specifies how a reserved name should be escaped to. Otherwise, the default _ is used. + */ + val reservedWordsMappings = project.objects.property>() + + /** + * Specifies an override location for the .openapi-generator-ignore file. Most useful on initial generation. + */ + val ignoreFileOverride = project.objects.property() + + /** + * Remove prefix of operationId, e.g. config_getId => getId + */ + val removeOperationIdPrefix = project.objects.property() + + /** + * Defines which API-related files should be generated. This allows you to create a subset of generated files (or none at all). + * + * NOTE: Configuring any one of [apiFilesConstrainedTo], [modelFilesConstrainedTo], or [supportingFilesConstrainedTo] results + * in others being disabled. That is, OpenAPI Generator considers any one of these to define a subset of generation. + * For more control over generation of individual files, configure an ignore file and refer to it via [ignoreFileOverride]. + */ + val apiFilesConstrainedTo = project.objects.listProperty() + + /** + * Defines which model-related files should be generated. This allows you to create a subset of generated files (or none at all). + * + * NOTE: Configuring any one of [apiFilesConstrainedTo], [modelFilesConstrainedTo], or [supportingFilesConstrainedTo] results + * in others being disabled. That is, OpenAPI Generator considers any one of these to define a subset of generation. + * For more control over generation of individual files, configure an ignore file and refer to it via [ignoreFileOverride]. + */ + val modelFilesConstrainedTo = project.objects.listProperty() + + /** + * Defines which supporting files should be generated. This allows you to create a subset of generated files (or none at all). + * + * Supporting files are those related to projects/frameworks which may be modified + * by consumers. + * + * NOTE: Configuring any one of [apiFilesConstrainedTo], [modelFilesConstrainedTo], or [supportingFilesConstrainedTo] results + * in others being disabled. That is, OpenAPI Generator considers any one of these to define a subset of generation. + * For more control over generation of individual files, configure an ignore file and refer to it via [ignoreFileOverride]. + */ + val supportingFilesConstrainedTo = project.objects.listProperty() + + /** + * Defines whether or not model-related _test_ files should be generated. + * + * This option enables/disables generation of ALL model-related _test_ files. + * + * For more control over generation of individual files, configure an ignore file and + * refer to it via [ignoreFileOverride]. + */ + val generateModelTests = project.objects.property() + + /** + * Defines whether or not model-related _documentation_ files should be generated. + * + * This option enables/disables generation of ALL model-related _documentation_ files. + * + * For more control over generation of individual files, configure an ignore file and + * refer to it via [ignoreFileOverride]. + */ + val generateModelDocumentation = project.objects.property() + + /** + * Defines whether or not api-related _test_ files should be generated. + * + * This option enables/disables generation of ALL api-related _test_ files. + * + * For more control over generation of individual files, configure an ignore file and + * refer to it via [ignoreFileOverride]. + */ + val generateApiTests = project.objects.property() + + /** + * Defines whether or not api-related _documentation_ files should be generated. + * + * This option enables/disables generation of ALL api-related _documentation_ files. + * + * For more control over generation of individual files, configure an ignore file and + * refer to it via [ignoreFileOverride]. + */ + val generateApiDocumentation = project.objects.property() + + /** + * A special-case setting which configures some generators with XML support. In some cases, + * this forces json OR xml, so the default here is false. + */ + val withXml = project.objects.property() + + /** + * A map of options specific to a generator. + */ + val configOptions = project.objects.property>() + + init { + releaseNote.set("Minor update") + modelNamePrefix.set("") + modelNameSuffix.set("") + generateModelTests.set(true) + generateModelDocumentation.set(true) + generateApiTests.set(true) + generateApiDocumentation.set(true) + withXml.set(false) + configOptions.set(mapOf()) + } +} \ No newline at end of file diff --git a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorMetaExtension.kt b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorMetaExtension.kt new file mode 100644 index 00000000000..94d298a31b8 --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorMetaExtension.kt @@ -0,0 +1,48 @@ +/* + * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openapitools.generator.gradle.plugin.extensions + +import org.gradle.api.Project +import org.gradle.kotlin.dsl.property + +/** + * Gradle project level extension object definition for the meta-generator task + * + * @author Jim Schubert + */ +open class OpenApiGeneratorMetaExtension(project: Project) { + /** + * The human-readable generator name of the newly created template generator. + */ + val generatorName = project.objects.property() + + /** + * The packageName generatorName to put the main class into (defaults to org.openapitools.codegen) + */ + val packageName = project.objects.property() + + /** + * Where to write the generated files (current dir by default). + */ + val outputFolder = project.objects.property() + + init { + generatorName.set("default") + packageName.set("org.openapitools.codegen") + outputFolder.set("") + } +} \ No newline at end of file diff --git a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorValidateExtension.kt b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorValidateExtension.kt new file mode 100644 index 00000000000..3b4a1853b94 --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/extensions/OpenApiGeneratorValidateExtension.kt @@ -0,0 +1,32 @@ +/* + * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openapitools.generator.gradle.plugin.extensions + +import org.gradle.api.Project +import org.gradle.kotlin.dsl.property + +/** + * Gradle project level extension object definition for the generators task + * + * @author Jim Schubert + */ +open class OpenApiGeneratorValidateExtension(project: Project) { + /** + * The input specification to validate. Supports all formats supported by the Parser. + */ + val inputSpec = project.objects.property() +} \ No newline at end of file diff --git a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/GenerateTask.kt b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/GenerateTask.kt new file mode 100644 index 00000000000..dbe0424dbca --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/GenerateTask.kt @@ -0,0 +1,541 @@ +/* + * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openapitools.generator.gradle.plugin.tasks + +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.TaskAction +import org.gradle.internal.logging.text.StyledTextOutput +import org.gradle.internal.logging.text.StyledTextOutputFactory +import org.gradle.kotlin.dsl.listProperty +import org.gradle.kotlin.dsl.property +import org.openapitools.codegen.CodegenConstants +import org.openapitools.codegen.DefaultGenerator +import org.openapitools.codegen.config.CodegenConfigurator +import org.openapitools.codegen.config.CodegenConfiguratorUtils.* + + +/** + * A task which generates the desired code. + * + * Example (CLI): + * + * ./gradlew -q openApiGenerate + * + * @author Jim Schubert + */ +open class GenerateTask : DefaultTask() { + + /** + * The verbosity of generation + */ + @get:Internal + val verbose = project.objects.property() + + /** + * The name of the generator which will handle codegen. (see "openApiGenerators" task) + */ + @get:Internal + val generatorName = project.objects.property() + + /** + * The output target directory into which code will be generated. + */ + @get:Internal + val outputDir = project.objects.property() + + /** + * The Open API 2.0/3.x specification location. + */ + @get:Internal + val inputSpec = project.objects.property() + + /** + * The template directory holding a custom template. + */ + @get:Internal + val templateDir = project.objects.property() + + /** + * Adds authorization headers when fetching the OpenAPI definitions remotely. + * Pass in a URL-encoded string of name:header with a comma separating multiple values + */ + @get:Internal + val auth = project.objects.property() + + /** + * Sets specified system properties. + */ + @get:Internal + val systemProperties = project.objects.property>() + + /** + * Path to json configuration file. + * File content should be in a json format { "optionKey":"optionValue", "optionKey1":"optionValue1"...} + * Supported options can be different for each language. Run config-help -g {generator name} command for language specific config options. + */ + @get:Internal + val configFile = project.objects.property() + + /** + * Specifies if the existing files should be overwritten during the generation. + */ + @get:Internal + val skipOverwrite = project.objects.property() + + /** + * Package for generated api classes + */ + @get:Internal + val apiPackage = project.objects.property() + + /** + * Package for generated models + */ + @get:Internal + val modelPackage = project.objects.property() + + /** + * Prefix that will be prepended to all model names. Default is the empty string. + */ + @get:Internal + val modelNamePrefix = project.objects.property() + + /** + * Suffix that will be appended to all model names. Default is the empty string. + */ + @get:Internal + val modelNameSuffix = project.objects.property() + + /** + * Sets instantiation type mappings. + */ + @get:Internal + val instantiationTypes = project.objects.property>() + + /** + * Sets mappings between OpenAPI spec types and generated code types. + */ + @get:Internal + val typeMappings = project.objects.property>() + + /** + * Sets additional properties that can be referenced by the mustache templates in the format of name=value,name=value. + * You can also have multiple occurrences of this option. + */ + @get:Internal + val additionalProperties = project.objects.property>() + + /** + * Specifies additional language specific primitive types in the format of type1,type2,type3,type3. For example: String,boolean,Boolean,Double. + */ + @get:Internal + val languageSpecificPrimitives = project.objects.listProperty() + + /** + * Specifies mappings between a given class and the import that should be used for that class. + */ + @get:Internal + val importMappings = project.objects.property>() + + /** + * Root package for generated code. + */ + @get:Internal + val invokerPackage = project.objects.property() + + /** + * GroupId in generated pom.xml/build.gradle or other build script. Language-specific conversions occur in non-jvm generators. + */ + @get:Internal + val groupId = project.objects.property() + + /** + * ArtifactId in generated pom.xml/build.gradle or other build script. Language-specific conversions occur in non-jvm generators. + */ + @get:Internal + val id = project.objects.property() + + /** + * Artifact version in generated pom.xml/build.gradle or other build script. Language-specific conversions occur in non-jvm generators. + */ + @get:Internal + val version = project.objects.property() + + /** + * Reference the library template (sub-template) of a generator. + */ + @get:Internal + val library = project.objects.property() + + /** + * Git user ID, e.g. openapitools. + */ + @get:Internal + val gitUserId = project.objects.property() + + /** + * Git repo ID, e.g. openapi-generator. + */ + @get:Internal + val gitRepoId = project.objects.property() + + /** + * Release note, default to 'Minor update'. + */ + @get:Internal + val releaseNote = project.objects.property() + + /** + * HTTP user agent, e.g. codegen_csharp_api_client, default to 'OpenAPI-Generator/{packageVersion}}/{language}' + */ + @get:Internal + val httpUserAgent = project.objects.property() + + /** + * Specifies how a reserved name should be escaped to. + */ + @get:Internal + val reservedWordsMappings = project.objects.property>() + + /** + * Specifies an override location for the .openapi-generator-ignore file. Most useful on initial generation. + */ + @get:Internal + val ignoreFileOverride = project.objects.property() + + /** + * Remove prefix of operationId, e.g. config_getId => getId + */ + @get:Internal + val removeOperationIdPrefix = project.objects.property() + + /** + * Defines which API-related files should be generated. This allows you to create a subset of generated files (or none at all). + * + * This option enables/disables generation of ALL api-related files. + * + * NOTE: Configuring any one of [apiFilesConstrainedTo], [modelFilesConstrainedTo], or [supportingFilesConstrainedTo] results + * in others being disabled. That is, OpenAPI Generator considers any one of these to define a subset of generation. + * For more control over generation of individual files, configure an ignore file and refer to it via [ignoreFileOverride]. + */ + @get:Internal + val apiFilesConstrainedTo = project.objects.listProperty() + + /** + * Defines which model-related files should be generated. This allows you to create a subset of generated files (or none at all). + * + * NOTE: Configuring any one of [apiFilesConstrainedTo], [modelFilesConstrainedTo], or [supportingFilesConstrainedTo] results + * in others being disabled. That is, OpenAPI Generator considers any one of these to define a subset of generation. + * For more control over generation of individual files, configure an ignore file and refer to it via [ignoreFileOverride]. + */ + @get:Internal + val modelFilesConstrainedTo = project.objects.listProperty() + + /** + * Defines which supporting files should be generated. This allows you to create a subset of generated files (or none at all). + * + * Supporting files are those related to projects/frameworks which may be modified + * by consumers. + * + * NOTE: Configuring any one of [apiFilesConstrainedTo], [modelFilesConstrainedTo], or [supportingFilesConstrainedTo] results + * in others being disabled. That is, OpenAPI Generator considers any one of these to define a subset of generation. + * For more control over generation of individual files, configure an ignore file and refer to it via [ignoreFileOverride]. + */ + @get:Internal + val supportingFilesConstrainedTo = project.objects.listProperty() + + /** + * Defines whether or not model-related _test_ files should be generated. + * + * This option enables/disables generation of ALL model-related _test_ files. + * + * For more control over generation of individual files, configure an ignore file and + * refer to it via [ignoreFileOverride]. + */ + @get:Internal + val generateModelTests = project.objects.property() + + /** + * Defines whether or not model-related _documentation_ files should be generated. + * + * This option enables/disables generation of ALL model-related _documentation_ files. + * + * For more control over generation of individual files, configure an ignore file and + * refer to it via [ignoreFileOverride]. + */ + @get:Internal + val generateModelDocumentation = project.objects.property() + + /** + * Defines whether or not api-related _test_ files should be generated. + * + * This option enables/disables generation of ALL api-related _test_ files. + * + * For more control over generation of individual files, configure an ignore file and + * refer to it via [ignoreFileOverride]. + */ + @get:Internal + val generateApiTests = project.objects.property() + + /** + * Defines whether or not api-related _documentation_ files should be generated. + * + * This option enables/disables generation of ALL api-related _documentation_ files. + * + * For more control over generation of individual files, configure an ignore file and + * refer to it via [ignoreFileOverride]. + */ + @get:Internal + val generateApiDocumentation = project.objects.property() + + /** + * A special-case setting which configures some generators with XML support. In some cases, + * this forces json OR xml, so the default here is false. + */ + @get:Internal + val withXml = project.objects.property() + + /** + * A dynamic map of options specific to a generator. + */ + @get:Internal + val configOptions = project.objects.property>() + + private val originalEnvironmentVariables = mutableMapOf() + + private fun Property.ifNotEmpty(block: Property.(T) -> Unit) { + if (isPresent) { + val item: T? = get() + if (item != null) { + when (get()) { + is String -> if ((get() as String).isNotEmpty()) { + block(get()) + } + is String? -> if (true == (get() as String?)?.isNotEmpty()) { + block(get()) + } + else -> block(get()) + } + } + } + } + + @Suppress("unused") + @TaskAction + fun doWork() { + val configurator: CodegenConfigurator = if (configFile.isPresent) { + CodegenConfigurator.fromFile(configFile.get()) + } else CodegenConfigurator() + + try { + if (systemProperties.isPresent) { + systemProperties.get().forEach { (key, value) -> + originalEnvironmentVariables[key] = System.getProperty(key) + System.setProperty(key, value) + configurator.addSystemProperty(key, value) + } + } + + if (supportingFilesConstrainedTo.isPresent && supportingFilesConstrainedTo.get().isNotEmpty()) { + System.setProperty(CodegenConstants.SUPPORTING_FILES, supportingFilesConstrainedTo.get().joinToString(",")) + } else { + System.clearProperty(CodegenConstants.SUPPORTING_FILES) + } + + if (modelFilesConstrainedTo.isPresent && modelFilesConstrainedTo.get().isNotEmpty()) { + System.setProperty(CodegenConstants.MODELS, modelFilesConstrainedTo.get().joinToString(",")) + } else { + System.clearProperty(CodegenConstants.MODELS) + } + + if (apiFilesConstrainedTo.isPresent && apiFilesConstrainedTo.get().isNotEmpty()) { + System.setProperty(CodegenConstants.APIS, apiFilesConstrainedTo.get().joinToString(",")) + } else { + System.clearProperty(CodegenConstants.APIS) + } + + System.setProperty(CodegenConstants.API_DOCS, generateApiDocumentation.get().toString()) + System.setProperty(CodegenConstants.MODEL_DOCS, generateModelDocumentation.get().toString()) + System.setProperty(CodegenConstants.MODEL_TESTS, generateModelTests.get().toString()) + System.setProperty(CodegenConstants.API_TESTS, generateApiTests.get().toString()) + System.setProperty(CodegenConstants.WITH_XML, withXml.get().toString()) + + // now override with any specified parameters + verbose.ifNotEmpty { value -> + configurator.isVerbose = value + } + + skipOverwrite.ifNotEmpty { value -> + configurator.isSkipOverwrite = value ?: false + } + + inputSpec.ifNotEmpty { value -> + configurator.inputSpec = value + } + + generatorName.ifNotEmpty { value -> + configurator.generatorName = value + } + + outputDir.ifNotEmpty { value -> + configurator.outputDir = value + } + + auth.ifNotEmpty { value -> + configurator.auth = value + } + + templateDir.ifNotEmpty { value -> + configurator.templateDir = value + } + + apiPackage.ifNotEmpty { value -> + configurator.apiPackage = value + } + + modelPackage.ifNotEmpty { value -> + configurator.modelPackage = value + } + + modelNamePrefix.ifNotEmpty { value -> + configurator.modelNamePrefix = value + } + + modelNameSuffix.ifNotEmpty { value -> + configurator.modelNameSuffix = value + } + + invokerPackage.ifNotEmpty { value -> + configurator.invokerPackage = value + } + + groupId.ifNotEmpty { value -> + configurator.groupId = value + } + + id.ifNotEmpty { value -> + configurator.artifactId = value + } + + version.ifNotEmpty { value -> + configurator.artifactVersion = value + } + + library.ifNotEmpty { value -> + configurator.library = value + } + + gitUserId.ifNotEmpty { value -> + configurator.gitUserId = value + } + + gitRepoId.ifNotEmpty { value -> + configurator.gitRepoId = value + } + + releaseNote.ifNotEmpty { value -> + configurator.releaseNote = value + } + + httpUserAgent.ifNotEmpty { value -> + configurator.httpUserAgent = value + } + + ignoreFileOverride.ifNotEmpty { value -> + configurator.ignoreFileOverride = value + } + + removeOperationIdPrefix.ifNotEmpty { value -> + configurator.removeOperationIdPrefix = value!! + } + + if (systemProperties.isPresent) { + systemProperties.get().forEach { entry -> + configurator.addSystemProperty(entry.key, entry.value) + } + } + + if (instantiationTypes.isPresent) { + instantiationTypes.get().forEach { entry -> + configurator.addInstantiationType(entry.key, entry.value) + } + } + + if (importMappings.isPresent) { + importMappings.get().forEach { entry -> + configurator.addImportMapping(entry.key, entry.value) + } + } + + if (typeMappings.isPresent) { + typeMappings.get().forEach { entry -> + configurator.addTypeMapping(entry.key, entry.value) + } + } + + if (additionalProperties.isPresent) { + additionalProperties.get().forEach { entry -> + configurator.addAdditionalProperty(entry.key, entry.value) + } + } + + if (languageSpecificPrimitives.isPresent) { + languageSpecificPrimitives.get().forEach { + configurator.addLanguageSpecificPrimitive(it) + } + } + + if (reservedWordsMappings.isPresent) { + reservedWordsMappings.get().forEach { entry -> + configurator.addAdditionalReservedWordMapping(entry.key, entry.value) + } + } + + val clientOptInput = configurator.toClientOptInput() + val codgenConfig = clientOptInput.config + + if (configOptions.isPresent) { + val userSpecifiedConfigOptions = configOptions.get() + codgenConfig.cliOptions().forEach { + if (userSpecifiedConfigOptions.containsKey(it.opt)) { + clientOptInput.config.additionalProperties()[it.opt] = userSpecifiedConfigOptions[it.opt] + } + } + } + + try { + val out = services.get(StyledTextOutputFactory::class.java).create("openapi") + out.withStyle(StyledTextOutput.Style.Success) + + DefaultGenerator().opts(clientOptInput).generate() + + out.println("Successfully generated code to ${configurator.outputDir}") + } catch (e: RuntimeException) { + logger.error(e.message) + throw GradleException("Code generation failed.") + } + } finally { + originalEnvironmentVariables.forEach { entry -> + System.setProperty(entry.key, entry.value) + } + originalEnvironmentVariables.clear() + } + } +} diff --git a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/GeneratorsTask.kt b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/GeneratorsTask.kt new file mode 100644 index 00000000000..08e821b83a5 --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/GeneratorsTask.kt @@ -0,0 +1,71 @@ +/* + * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openapitools.generator.gradle.plugin.tasks + +import org.gradle.api.DefaultTask +import org.gradle.api.tasks.TaskAction +import org.gradle.internal.logging.text.StyledTextOutput +import org.gradle.internal.logging.text.StyledTextOutputFactory +import org.openapitools.codegen.CodegenConfigLoader +import org.openapitools.codegen.CodegenType + +/** + * A task which lists out the generators available in OpenAPI Generator + * + * Example (CLI): + * + * ./gradlew -q openApiGenerators + * + * @author Jim Schubert + */ +open class GeneratorsTask : DefaultTask() { + @Suppress("unused") + @TaskAction + fun doWork() { + val generators = CodegenConfigLoader.getAll() + + val out = services.get(StyledTextOutputFactory::class.java).create("openapi") + + StringBuilder().apply { + val types = CodegenType.values() + + append("The following generators are available:") + + append(System.lineSeparator()) + append(System.lineSeparator()) + + for (type in types) { + append(type.name).append(" generators:") + append(System.lineSeparator()) + + generators.filter { it.tag == type } + .sortedBy { it.name } + .forEach({ generator -> + append(" - ") + append(generator.name) + append(System.lineSeparator()) + }) + + append(System.lineSeparator()) + append(System.lineSeparator()) + } + + out.withStyle(StyledTextOutput.Style.Success) + out.formatln("%s%n", toString()) + } + } +} \ No newline at end of file diff --git a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/MetaTask.kt b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/MetaTask.kt new file mode 100644 index 00000000000..7d11f4d8b6c --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/MetaTask.kt @@ -0,0 +1,132 @@ +/* + * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openapitools.generator.gradle.plugin.tasks + +import com.samskivert.mustache.Mustache +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.TaskAction +import org.gradle.internal.logging.text.StyledTextOutput +import org.gradle.internal.logging.text.StyledTextOutputFactory +import org.gradle.kotlin.dsl.property +import org.openapitools.codegen.CodegenConfig +import org.openapitools.codegen.CodegenConstants +import org.openapitools.codegen.DefaultGenerator +import org.openapitools.codegen.SupportingFile +import java.io.File +import java.io.IOException +import java.nio.charset.Charset + +/** + * A task which generates a new generator (meta). Useful for redistributable generator packages. + * + * @author Jim Schubert + */ +open class MetaTask : DefaultTask() { + + @get:Internal + val generatorName = project.objects.property() + + @get:Internal + val packageName = project.objects.property() + + @get:Internal + val outputFolder = project.objects.property() + + @Suppress("unused") + @TaskAction + fun doWork() { + + val packageToPath = packageName.get().replace(".", File.separator) + val dir = File(outputFolder.get()) + val klass = "${generatorName.get().titleCasedTextOnly()}Generator" + + val templateResourceDir = generatorName.get().hyphenatedTextOnly() + + val out = services.get(StyledTextOutputFactory::class.java).create("openapi") + + out.withStyle(StyledTextOutput.Style.Info) + + logger.debug("package: {}", packageName.get()) + logger.debug("dir: {}", dir.absolutePath) + logger.debug("generator class: {}", klass) + + val supportingFiles = listOf( + SupportingFile("pom.mustache", "", "pom.xml"), + SupportingFile("generatorClass.mustache", dir("src", "main", "java", packageToPath), "$klass.java"), + SupportingFile("README.mustache", "", "README.md"), + SupportingFile("api.template", dir("src", "main", "resources", templateResourceDir), "api.mustache"), + SupportingFile("model.template", dir("src", "main", "resources", templateResourceDir), "model.mustache"), + SupportingFile("myFile.template", dir("src", "main", "resources", templateResourceDir), "myFile.mustache"), + SupportingFile("services.mustache", dir("src", "main", "resources", "META-INF", "services"), CodegenConfig::class.java.canonicalName)) + + val currentVersion = CodegenConstants::class.java.`package`.implementationVersion + + val data = mapOf("generatorPackage" to packageToPath, + "generatorClass" to klass, + "name" to templateResourceDir, + "fullyQualifiedGeneratorClass" to "${packageName.get()}.$klass", + "openapiGeneratorVersion" to currentVersion) + + val generator = DefaultGenerator() + supportingFiles.map { + try { + val destinationFolder = File(File(dir.absolutePath), it.folder) + destinationFolder.mkdirs() + val outputFile = File(destinationFolder, it.destinationFilename) + + val template = generator.readTemplate(File("codegen", it.templateFile).path) + var formatted = template + + if (it.templateFile.endsWith(".mustache")) { + formatted = Mustache.compiler() + .withLoader(loader(generator)) + .defaultValue("") + .compile(template).execute(data) + } + + outputFile.writeText(formatted, Charset.forName("UTF8")) + + out.formatln("Wrote file to %s", outputFile.absolutePath) + + // TODO: register outputs + // return outputFile + } catch (e: IOException) { + logger.error(e.message) + throw GradleException("Can't generate project", e) + } + } + out.withStyle(StyledTextOutput.Style.Success) + out.formatln("Created generator %s", klass) + } + + private fun loader(generator: DefaultGenerator): Mustache.TemplateLoader { + return Mustache.TemplateLoader { name -> + generator.getTemplateReader("codegen${File.separator}$name.mustache") + } + } + + private fun String.titleCasedTextOnly(): String = + this.split(Regex("[^a-zA-Z0-9]")).joinToString(separator = "", transform = String::capitalize) + + private fun String.hyphenatedTextOnly(): String = + this.split(Regex("[^a-zA-Z0-9]")).joinToString(separator = "-", transform = String::toLowerCase) + + private fun dir(vararg parts: String): String = + parts.joinToString(separator = File.separator) +} diff --git a/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/ValidateTask.kt b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/ValidateTask.kt new file mode 100644 index 00000000000..0956ba22c26 --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/src/main/kotlin/org/openapitools/generator/gradle/plugin/tasks/ValidateTask.kt @@ -0,0 +1,82 @@ +/* + * Copyright 2018 OpenAPI-Generator Contributors (https://openapi-generator.tech) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.openapitools.generator.gradle.plugin.tasks + +import io.swagger.parser.OpenAPIParser +import org.gradle.api.DefaultTask +import org.gradle.api.GradleException +import org.gradle.api.tasks.Internal +import org.gradle.api.tasks.TaskAction +import org.gradle.api.tasks.options.Option +import org.gradle.internal.logging.text.StyledTextOutput +import org.gradle.internal.logging.text.StyledTextOutputFactory +import org.gradle.kotlin.dsl.property + +/** + * A generator which validates an Open API spec. This task outputs a list of validation issues and errors. + * + * Example: + * cli: + * + * ./gradlew openApiValidate --input=/path/to/file + * + * build.gradle: + * + * openApiMeta { + * inputSpec = "path/to/spec.yaml" + * } + * + * @author Jim Schubert + */ +open class ValidateTask : DefaultTask() { + @get:Internal + var inputSpec = project.objects.property() + + @Suppress("unused") + @get:Internal + @set:Option(option = "input", description = "The input specification.") + var input: String? = null + set(value) { + inputSpec.set(value) + } + + @Suppress("unused") + @TaskAction + fun doWork() { + val spec = inputSpec.get() + logger.quiet("Validating spec $spec") + val result = OpenAPIParser().readLocation(spec, null, null) + val messages = result.messages.toSet() + val out = services.get(StyledTextOutputFactory::class.java).create("openapi") + + if (messages.isNotEmpty()) { + + out.withStyle(StyledTextOutput.Style.Error) + out.println("\nSpec is invalid.\nIssues:\n") + + messages.forEach { + out.withStyle(StyledTextOutput.Style.Error) + out.println("\t$it\n") + } + + throw GradleException("Validation failed.") + } else { + out.withStyle(StyledTextOutput.Style.Success) + out.println("Spec is valid.") + } + } +} \ No newline at end of file diff --git a/modules/openapi-generator-gradle-plugin/src/test/kotlin/GenerateTaskDslTest.kt b/modules/openapi-generator-gradle-plugin/src/test/kotlin/GenerateTaskDslTest.kt new file mode 100644 index 00000000000..fe84a903bd7 --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/src/test/kotlin/GenerateTaskDslTest.kt @@ -0,0 +1,71 @@ +package org.openapitools.generator.gradle.plugin + +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome +import org.testng.annotations.Test +import java.io.File +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class GenerateTaskDslTest : TestBase() { + override var temp: File = createTempDir(javaClass.simpleName) + + private val defaultBuildGradle = """ + plugins { + id 'org.openapi.generator' + } + openApiGenerate { + generatorName = "kotlin" + inputSpec = file("spec.yaml").absolutePath + outputDir = file("build/kotlin").absolutePath + apiPackage = "org.openapitools.example.api" + invokerPackage = "org.openapitools.example.invoker" + modelPackage = "org.openapitools.example.model" + configOptions = [ + dateLibrary: "java8" + ] + } + """.trimIndent() + + @Test + fun `openApiGenerate should create an expected file structure from DSL config`() { + // Arrange + val projectFiles = mapOf( + "spec.yaml" to javaClass.classLoader.getResourceAsStream("specs/petstore-v3.0-invalid.yaml") + ) + withProject(defaultBuildGradle, projectFiles) + + // Act + val result = GradleRunner.create() + .withProjectDir(temp) + .withArguments("openApiGenerate") + .withPluginClasspath() + .build() + + // Assert + assertTrue(result.output.contains("Successfully generated code to"), "User friendly generate notice is missing.") + + listOf( + "build/kotlin/.openapi-generator-ignore", + "build/kotlin/docs/PetsApi.md", + "build/kotlin/docs/Pets.md", + "build/kotlin/docs/Error.md", + "build/kotlin/docs/Pet.md", + "build/kotlin/README.md", + "build/kotlin/build.gradle", + "build/kotlin/.openapi-generator/VERSION", + "build/kotlin/settings.gradle", + "build/kotlin/src/main/kotlin/org/openapitools/example/model/Pets.kt", + "build/kotlin/src/main/kotlin/org/openapitools/example/model/Pet.kt", + "build/kotlin/src/main/kotlin/org/openapitools/example/model/Error.kt", + "build/kotlin/src/main/kotlin/org/openapitools/example/api/PetsApi.kt", + "build/kotlin/src/main/kotlin/org/openapitools/client/infrastructure/ApiClient.kt" + ).map { + val f = File(temp, it) + assertTrue(f.exists() && f.isFile, "An expected file was not generated when invoking the generation.") + } + + assertEquals(TaskOutcome.SUCCESS, result.task(":openApiGenerate")?.outcome, + "Expected a successful run, but found ${result.task(":openApiGenerate")?.outcome}") + } +} \ No newline at end of file diff --git a/modules/openapi-generator-gradle-plugin/src/test/kotlin/GeneratorsTaskDslTest.kt b/modules/openapi-generator-gradle-plugin/src/test/kotlin/GeneratorsTaskDslTest.kt new file mode 100644 index 00000000000..f3373fd657e --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/src/test/kotlin/GeneratorsTaskDslTest.kt @@ -0,0 +1,38 @@ +package org.openapitools.generator.gradle.plugin + +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome +import org.testng.annotations.Test +import java.io.File +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class GeneratorsTaskDslTest : TestBase() { + override var temp: File = createTempDir(javaClass.simpleName) + + @Test + fun `openApiGenerators should list generators available to the user`() { + // Arrange + withProject(""" + | plugins { + | id 'org.openapi.generator' + | } + """.trimMargin()) + + // Act + val result = GradleRunner.create() + .withProjectDir(temp) + .withArguments("openApiGenerators") + .withPluginClasspath() + .build() + + // Assert + assertTrue(result.output.contains("The following generators are available:"), "User friendly generator notice is missing.") + assertTrue(result.output.contains("CLIENT generators:"), "Expected client generator header is missing.") + assertTrue(result.output.contains("android"), "Spot-checking listed client generators is missing a client generator.") + assertTrue(result.output.contains("SERVER generators:"), "Expected server generator header is missing.") + assertTrue(result.output.contains("kotlin-server"), "Spot-checking listed server generators is missing a server generator.") + assertEquals(TaskOutcome.SUCCESS, result.task(":openApiGenerators")?.outcome, + "Expected a successful run, but found ${result.task(":openApiGenerators")?.outcome}") + } +} \ No newline at end of file diff --git a/modules/openapi-generator-gradle-plugin/src/test/kotlin/MetaTaskDslTest.kt b/modules/openapi-generator-gradle-plugin/src/test/kotlin/MetaTaskDslTest.kt new file mode 100644 index 00000000000..fe857d1b94a --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/src/test/kotlin/MetaTaskDslTest.kt @@ -0,0 +1,58 @@ +package org.openapitools.generator.gradle.plugin + +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome +import org.testng.annotations.Test +import java.io.File +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class MetaTaskDslTest : TestBase() { + override var temp: File = createTempDir(javaClass.simpleName) + + @Test + fun `openApiMeta should generate desired project contents`() { + // Arrange + val buildDirReplacement = "\$buildDir/meta" + withProject(""" + | plugins { + | id 'org.openapi.generator' + | } + | + | openApiMeta { + | generatorName = "Sample" + | packageName = "org.openapitools.example" + | outputFolder = "$buildDirReplacement".toString() + | } + """.trimMargin()) + + // Act + val result = GradleRunner.create() + .withProjectDir(temp) + .withArguments("openApiMeta") + .withPluginClasspath() + .build() + + // Assert + assertTrue(result.output.contains("Wrote file to"), "User friendly write notice is missing.") + + // To avoid any OS-specific output causing issues with our stdout comparisons, only compare on expected filenames. + listOf( + "SampleGenerator.java", + "README.md", + "api.mustache", + "model.mustache", + "myFile.mustache", + "org.openapitools.codegen.CodegenConfig", + "pom.xml" + ).map { + assertTrue(result.output.contains(it), "Expected $it to be listed in gradle stdout.") + } + + assertEquals( + TaskOutcome.SUCCESS, + result.task(":openApiMeta")?.outcome, + "Expected a successful run, but found ${result.task(":openApiMeta")?.outcome}" + ) + } +} \ No newline at end of file diff --git a/modules/openapi-generator-gradle-plugin/src/test/kotlin/TestBase.kt b/modules/openapi-generator-gradle-plugin/src/test/kotlin/TestBase.kt new file mode 100644 index 00000000000..47a1bfba9ec --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/src/test/kotlin/TestBase.kt @@ -0,0 +1,34 @@ +package org.openapitools.generator.gradle.plugin + +import org.testng.annotations.AfterMethod +import org.testng.annotations.BeforeMethod +import java.io.File +import java.io.InputStream + +abstract class TestBase { + protected open lateinit var temp: File + + @BeforeMethod + protected fun before() { + temp = createTempDir(javaClass.simpleName) + temp.deleteOnExit() + } + + @AfterMethod + protected fun after(){ + temp.deleteRecursively() + } + + protected fun withProject( + buildContents: String, + projectFiles: Map = mapOf() + ) { + val buildFile = File(temp,"build.gradle") + buildFile.writeText(buildContents) + + projectFiles.forEach { entry -> + val target = File(temp, entry.key) + entry.value.copyTo(target.outputStream()) + } + } +} \ No newline at end of file diff --git a/modules/openapi-generator-gradle-plugin/src/test/kotlin/ValidateTaskDslTest.kt b/modules/openapi-generator-gradle-plugin/src/test/kotlin/ValidateTaskDslTest.kt new file mode 100644 index 00000000000..acc066c98da --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/src/test/kotlin/ValidateTaskDslTest.kt @@ -0,0 +1,99 @@ +package org.openapitools.generator.gradle.plugin + +import org.gradle.testkit.runner.GradleRunner +import org.gradle.testkit.runner.TaskOutcome.FAILED +import org.gradle.testkit.runner.TaskOutcome.SUCCESS +import org.testng.annotations.Test +import java.io.File +import kotlin.test.assertEquals +import kotlin.test.assertTrue + +class ValidateTaskDslTest : TestBase() { + override var temp: File = createTempDir(javaClass.simpleName) + + @Test + fun `openApiValidate should fail on non-file spec`() { + // Arrange + withProject(""" + | plugins { + | id 'org.openapi.generator' + | } + | + | openApiValidate { + | inputSpec = "some_location" + | } + """.trimMargin()) + + // Act + val result = GradleRunner.create() + .withProjectDir(temp) + .withArguments("openApiValidate") + .withPluginClasspath() + .buildAndFail() + + // Assert + assertTrue(result.output.contains("unable to read location `some_location`"), "Unexpected/no message presented to the user for a spec pointing to an invalid URI.") + assertEquals(FAILED, result.task(":openApiValidate")?.outcome, + "Expected a failed run, but found ${result.task(":openApiValidate")?.outcome}") + } + + @Test + fun `openApiValidate should succeed on valid spec`() { + // Arrange + val projectFiles = mapOf( + "spec.yaml" to javaClass.classLoader.getResourceAsStream("specs/petstore-v3.0.yaml") + ) + + withProject(""" + | plugins { + | id 'org.openapi.generator' + | } + | + | openApiValidate { + | inputSpec = file("spec.yaml").absolutePath + | } + """.trimMargin(), projectFiles) + + // Act + val result = GradleRunner.create() + .withProjectDir(temp) + .withArguments("openApiValidate") + .withPluginClasspath() + .build() + + // Assert + assertTrue(result.output.contains("Spec is valid."), "Unexpected/no message presented to the user for a valid spec.") + assertEquals(SUCCESS, result.task(":openApiValidate")?.outcome, + "Expected a successful run, but found ${result.task(":openApiValidate")?.outcome}") + } + + @Test + fun `openApiValidate should fail on invalid spec`() { + // Arrange + val projectFiles = mapOf( + "spec.yaml" to javaClass.classLoader.getResourceAsStream("specs/petstore-v3.0-invalid.yaml") + ) + withProject(""" + | plugins { + | id 'org.openapi.generator' + | } + | + | openApiValidate { + | inputSpec = file('spec.yaml').absolutePath + | } + """.trimMargin(), projectFiles) + + // Act + val result = GradleRunner.create() + .withProjectDir(temp) + .withArguments("openApiValidate") + .withPluginClasspath() + .buildAndFail() + + // Assert + assertTrue(result.output.contains("Spec is invalid."), "Unexpected/no message presented to the user for an invalid spec.") + assertEquals(FAILED, result.task(":openApiValidate")?.outcome, + "Expected a failed run, but found ${result.task(":openApiValidate")?.outcome}") + } + +} \ No newline at end of file diff --git a/modules/openapi-generator-gradle-plugin/src/test/resources/specs/petstore-v3.0-invalid.yaml b/modules/openapi-generator-gradle-plugin/src/test/resources/specs/petstore-v3.0-invalid.yaml new file mode 100644 index 00000000000..0f5c6fc2982 --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/src/test/resources/specs/petstore-v3.0-invalid.yaml @@ -0,0 +1,103 @@ +openapi: "3.0.0" +servers: + - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a pet + tags: + - pets + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /pets/{petId}: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + Pet: + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: "#/components/schemas/Pet" + Error: + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string diff --git a/modules/openapi-generator-gradle-plugin/src/test/resources/specs/petstore-v3.0.yaml b/modules/openapi-generator-gradle-plugin/src/test/resources/specs/petstore-v3.0.yaml new file mode 100644 index 00000000000..264dbeabff1 --- /dev/null +++ b/modules/openapi-generator-gradle-plugin/src/test/resources/specs/petstore-v3.0.yaml @@ -0,0 +1,109 @@ +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + responses: + '200': + description: A paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + responses: + '201': + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /pets/{petId}: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + '200': + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + Pet: + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: "#/components/schemas/Pet" + Error: + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string diff --git a/pom.xml b/pom.xml index 69818d7bba6..8f6a390c4ca 100644 --- a/pom.xml +++ b/pom.xml @@ -883,6 +883,7 @@ modules/openapi-generator modules/openapi-generator-cli modules/openapi-generator-maven-plugin + modules/openapi-generator-gradle-plugin modules/openapi-generator-online