From 6360a0359a0344a39cabca152cfda823f2c837da Mon Sep 17 00:00:00 2001 From: Muli Yulzary Date: Fri, 15 Jan 2021 07:59:25 +0200 Subject: [PATCH 01/18] initial functionality and basic setup --- .github/dependabot.yml | 14 ++ .github/workflows/ci.yml | 33 ++++ build.gradle | 64 ++++++ gradle.properties | 1 + gradle/wrapper/gradle-wrapper.properties | 5 + gradlew | 185 ++++++++++++++++++ gradlew.bat | 89 +++++++++ package.json | 3 + settings.gradle | 2 + .../dropwizard/swagger/AutoAuthReader.kt | 47 +++++ .../dropwizard/swagger/SwaggerBundle.kt | 47 +++++ .../swagger/SwaggerConfiguration.kt | 11 ++ .../swagger/ui/SwaggerUiConfiguration.kt | 90 +++++++++ .../swagger/ui/SwaggerUiResource.kt | 32 +++ .../dropwizard/swagger/ui/SyntaxHighlight.kt | 6 + .../muliyul/dropwizard/swagger/ui/Theme.kt | 12 ++ .../dropwizard/swagger/TestConfiguration.kt | 12 ++ .../dropwizard/swagger/TestResource.kt | 24 +++ .../swagger/TestSwaggerApplication.kt | 22 +++ .../com/muliyul/dropwizard/swagger/User.kt | 7 + .../dropwizard/swagger/ext/Selenium.kt | 17 ++ .../integration/SwaggerIntegrationTest.kt | 44 +++++ .../swagger/integration/SwaggerUiPage.kt | 34 ++++ src/test/resources/config-test.yaml | 39 ++++ src/test/resources/junit-platform.properties | 2 + src/test/resources/openapi.yaml | 30 +++ .../resources/selenium-jupiter.properties | 98 ++++++++++ update-swagger-ui.sh | 15 ++ 28 files changed, 985 insertions(+) create mode 100644 .github/dependabot.yml create mode 100644 .github/workflows/ci.yml create mode 100644 build.gradle create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100644 gradlew create mode 100644 gradlew.bat create mode 100644 package.json create mode 100644 settings.gradle create mode 100644 src/main/kotlin/com/muliyul/dropwizard/swagger/AutoAuthReader.kt create mode 100644 src/main/kotlin/com/muliyul/dropwizard/swagger/SwaggerBundle.kt create mode 100644 src/main/kotlin/com/muliyul/dropwizard/swagger/SwaggerConfiguration.kt create mode 100644 src/main/kotlin/com/muliyul/dropwizard/swagger/ui/SwaggerUiConfiguration.kt create mode 100644 src/main/kotlin/com/muliyul/dropwizard/swagger/ui/SwaggerUiResource.kt create mode 100644 src/main/kotlin/com/muliyul/dropwizard/swagger/ui/SyntaxHighlight.kt create mode 100644 src/main/kotlin/com/muliyul/dropwizard/swagger/ui/Theme.kt create mode 100644 src/test/kotlin/com/muliyul/dropwizard/swagger/TestConfiguration.kt create mode 100644 src/test/kotlin/com/muliyul/dropwizard/swagger/TestResource.kt create mode 100644 src/test/kotlin/com/muliyul/dropwizard/swagger/TestSwaggerApplication.kt create mode 100644 src/test/kotlin/com/muliyul/dropwizard/swagger/User.kt create mode 100644 src/test/kotlin/com/muliyul/dropwizard/swagger/ext/Selenium.kt create mode 100644 src/test/kotlin/com/muliyul/dropwizard/swagger/integration/SwaggerIntegrationTest.kt create mode 100644 src/test/kotlin/com/muliyul/dropwizard/swagger/integration/SwaggerUiPage.kt create mode 100644 src/test/resources/config-test.yaml create mode 100644 src/test/resources/junit-platform.properties create mode 100644 src/test/resources/openapi.yaml create mode 100644 src/test/resources/selenium-jupiter.properties create mode 100644 update-swagger-ui.sh diff --git a/.github/dependabot.yml b/.github/dependabot.yml new file mode 100644 index 0000000..2ebee9a --- /dev/null +++ b/.github/dependabot.yml @@ -0,0 +1,14 @@ +version: 2 +updates: + - package-ecosystem: "github-actions" + directory: "/" + schedule: + interval: "weekly" + timezone: "Asia/Jerusalem" + - package-ecosystem: "gradle" + directory: "/" + schedule: + interval: "daily" + timezone: "Asia/Jerusalem" + reviewers: + - "muliyul" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..755e9af --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,33 @@ +name: Dropwizard Swagger CI + +on: + push: + - master + + pull_request: + - master + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + + - name: Set up JDK 1.8 + uses: actions/setup-java@v1 + with: + java-version: 1.8 + + - name: Cache Gradle packages + uses: actions/cache@v2 + with: + path: ~/.gradle/caches + key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}-uidist-{{ hashFiles('src/main/resources/static/swagger-ui/*.*') }} + restore-keys: ${{ runner.os }}-gradle + + - name: Clone Swagger UI distro + run: ${GITHUB_WORKSPACE}/update-swagger-ui.sh + + - name: Build + run: ./gradlew build diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000..6f52e1a --- /dev/null +++ b/build.gradle @@ -0,0 +1,64 @@ +plugins { + id 'org.jetbrains.kotlin.jvm' version '1.4.21' +} + +group 'com.muliyul' +version '0.0.1' + +repositories { + mavenCentral() +} + + +java { + registerFeature('auth') { + usingSourceSet(sourceSets.main) + } +} + +dependencies { + implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") + testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") + + testImplementation platform("org.glassfish.jersey:jersey-bom:2.0+") + testImplementation("org.glassfish.jersey.ext:jersey-proxy-client") + + implementation platform("io.dropwizard:dropwizard-bom:2.0.17") + implementation("io.dropwizard:dropwizard-core") +// implementation("io.dropwizard:dropwizard-client") + implementation("io.dropwizard:dropwizard-assets") + authImplementation("io.dropwizard:dropwizard-auth") + testImplementation("io.dropwizard:dropwizard-testing") + +// implementation platform("com.fasterxml.jackson:jackson-bom:2.10+") +// implementation("com.fasterxml.jackson.module:jackson-module-kotlin") + + implementation("io.swagger.core.v3:swagger-jaxrs2:2.1.2") + + // Junit + testImplementation platform("org.junit:junit-bom:5.7.0") + testImplementation("org.junit.jupiter:junit-jupiter-api") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") + + testImplementation('org.seleniumhq.selenium:selenium-java:3.141.59') +// testImplementation('org.seleniumhq.selenium:selenium-chrome-driver:3.141.59') + // TODO: introduced in 4.0.0 + //testImplementation('org.seleniumhq.selenium:selenium-chromium-driver:4.0.0') +// testImplementation('org.seleniumhq.selenium:selenium-firefox-driver:3.141.59') +// testImplementation('org.seleniumhq.selenium:selenium-ie-driver:3.141.59') +// testImplementation('org.seleniumhq.selenium:selenium-edge-driver:3.141.59') +// testImplementation('org.seleniumhq.selenium:selenium-opera-driver:3.141.59') + testImplementation('io.github.bonigarcia:selenium-jupiter:3.3.5') +} + +compileKotlin { + kotlinOptions.jvmTarget = "1.8" +} +compileTestKotlin { + kotlinOptions.jvmTarget = "1.8" +} + + +test { + useJUnitPlatform() +} diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000..29e08e8 --- /dev/null +++ b/gradle.properties @@ -0,0 +1 @@ +kotlin.code.style=official \ No newline at end of file diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..be52383 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.7-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100644 index 0000000..4f906e0 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## 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='"-Xmx64m" "-Xms64m"' + +# 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 or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; 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=`expr $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" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@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 Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@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="-Xmx64m" "-Xms64m" + +@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 execute + +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 execute + +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 + +: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 %* + +: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/package.json b/package.json new file mode 100644 index 0000000..0db3279 --- /dev/null +++ b/package.json @@ -0,0 +1,3 @@ +{ + +} diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000..0a1de7b --- /dev/null +++ b/settings.gradle @@ -0,0 +1,2 @@ +rootProject.name = 'dropwizard-swagger' + diff --git a/src/main/kotlin/com/muliyul/dropwizard/swagger/AutoAuthReader.kt b/src/main/kotlin/com/muliyul/dropwizard/swagger/AutoAuthReader.kt new file mode 100644 index 0000000..c0ccd70 --- /dev/null +++ b/src/main/kotlin/com/muliyul/dropwizard/swagger/AutoAuthReader.kt @@ -0,0 +1,47 @@ +package com.muliyul.dropwizard.swagger + +import com.fasterxml.jackson.annotation.* +import io.dropwizard.auth.* +import io.swagger.v3.jaxrs2.* +import io.swagger.v3.oas.annotations.* +import io.swagger.v3.oas.models.Operation +import io.swagger.v3.oas.models.security.* +import java.lang.reflect.* +import javax.ws.rs.* + +open class AutoAuthReader : Reader() { + @Suppress("unused") + @field:Hidden + private val annHolder: String? = null + + override fun getParameters( + type: Type?, + annotations: MutableList?, + operation: Operation, + classConsumes: Consumes?, + methodConsumes: Consumes?, + jsonViewAnnotation: JsonView? + ): ResolvedParameter { + val isAuthParam = annotations?.any { it.annotationClass == Auth::class } == true + + return if (isAuthParam) super.getParameters( + type, + annotations, + operation, + classConsumes, + methodConsumes, + jsonViewAnnotation + ).apply { + // Instructs swagger-core to ignore this param as a body param + requestBody = null + } + else super.getParameters( + type, + annotations, + operation, + classConsumes, + methodConsumes, + jsonViewAnnotation + ) + } +} diff --git a/src/main/kotlin/com/muliyul/dropwizard/swagger/SwaggerBundle.kt b/src/main/kotlin/com/muliyul/dropwizard/swagger/SwaggerBundle.kt new file mode 100644 index 0000000..4987898 --- /dev/null +++ b/src/main/kotlin/com/muliyul/dropwizard/swagger/SwaggerBundle.kt @@ -0,0 +1,47 @@ +package com.muliyul.dropwizard.swagger + +import com.muliyul.dropwizard.swagger.ui.* +import io.dropwizard.* +import io.dropwizard.Configuration +import io.dropwizard.assets.* +import io.dropwizard.setup.* +import io.swagger.v3.jaxrs2.integration.* +import io.swagger.v3.jaxrs2.integration.resources.* + +class SwaggerBundle( + private val resourcesPackageNames: Set, + private val uiConfiguration: SwaggerUiConfiguration = SwaggerUiConfiguration() +) : ConfiguredBundle where C : Configuration, C : SwaggerConfiguration { + + constructor( + vararg resourcesPackageNames: String, + uiConfiguration: SwaggerUiConfiguration = SwaggerUiConfiguration() + ) : this(resourcesPackageNames.toSet(), uiConfiguration) + + override fun initialize(bootstrap: Bootstrap<*>) { + bootstrap.addBundle(AssetsBundle("/static/swagger-ui/", "/swagger-ui/", null, "swagger")) + } + + override fun run(configuration: C, environment: Environment) { + + val servletConfig = environment.jerseyServletContainer!!.servletConfig + val swaggerConfiguration = configuration.swaggerConfiguration?.apply { + // TODO: check if dropwizard-auth is on the classpath + readerClass = AutoAuthReader::class.qualifiedName + } + + environment.jersey().run { + JaxrsOpenApiContextBuilder>() + .servletConfig(servletConfig) + .application(resourceConfig) + .resourcePackages(resourcesPackageNames) + .openApiConfiguration(swaggerConfiguration) + .ctxId(ServletConfigContextUtils.getContextIdFromServletConfig(servletConfig)) + .buildContext(true) + + register(AcceptHeaderOpenApiResource().resourcePackages(resourcesPackageNames)) + register(OpenApiResource().resourcePackages(resourcesPackageNames)) + register(SwaggerResource(environment.objectMapper, uiConfiguration)) + } + } +} diff --git a/src/main/kotlin/com/muliyul/dropwizard/swagger/SwaggerConfiguration.kt b/src/main/kotlin/com/muliyul/dropwizard/swagger/SwaggerConfiguration.kt new file mode 100644 index 0000000..6372e7c --- /dev/null +++ b/src/main/kotlin/com/muliyul/dropwizard/swagger/SwaggerConfiguration.kt @@ -0,0 +1,11 @@ +package com.muliyul.dropwizard.swagger + +import com.muliyul.dropwizard.swagger.ui.* +import io.swagger.v3.oas.integration.SwaggerConfiguration + +interface SwaggerConfiguration { + val swaggerConfiguration: SwaggerConfiguration? +// fun getSwaggerConfiguration(): SwaggerConfiguration? = null +// fun getSwaggerUiConfiguration(): SwaggerUiConfiguration? = null + val swaggerUiConfiguration: SwaggerUiConfiguration? +} diff --git a/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/SwaggerUiConfiguration.kt b/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/SwaggerUiConfiguration.kt new file mode 100644 index 0000000..2119d9b --- /dev/null +++ b/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/SwaggerUiConfiguration.kt @@ -0,0 +1,90 @@ +package com.muliyul.dropwizard.swagger.ui + +import com.fasterxml.jackson.annotation.* +import javax.ws.rs.* + +@JsonInclude(value = JsonInclude.Include.NON_NULL) +class SwaggerUiConfiguration( + // Core + val configUrl: String? = null, + val spec: Map? = null, + url: JsOrString = """window.location.origin + '/openapi'""", + val urls: List? = null, + + // Display + val deepLinking: Boolean? = null, + val displayOperationId: Boolean? = null, + val defaultModelsExpandDepth: Int? = null, + val defaultModelExpandDepth: Int? = null, + val defaultModelRendering: String? = null, + val displayRequestDuration: Boolean? = null, + val docExpansion: String? = null, + val filter: Boolean? = null, + val maxDisplayedTags: Int? = null, + @field:JsonRawValue + val operationsSorter: JsFunction? = null, + @field:JsonRawValue + val tagsSorter: JsFunction? = null, + val showExtensions: Boolean? = null, + val showCommonExtensions: Boolean? = null, + val useUnsafeMarkdown: Boolean? = null, + val syntaxHighlight: SyntaxHighlight? = null, + + // Network + val oauth2RedirectUrl: String? = null, + @field:JsonRawValue + val requestInterceptor: JsFunction? = null, +// @field:JsonProperty("request.curlOptions") +// @field:JsonRawValue +// val curlOptions: JsOrString = JS_UNDEFINED, + @field:JsonRawValue + val responseInterceptor: JsFunction? = null, + val showMutatedRequest: Boolean? = null, + val supportedSubmitMethods: List? = null, + val validatorUrl: String? = null, + val withCredentials: Boolean? = null, + + // Macros + @field:JsonRawValue + val modelPropertyMacro: JsFunction? = null, + @field:JsonRawValue + val parameterMacro: JsFunction? = null, + + // Authorization + val persistAuthorization: Boolean? = null, + + //Plugin system + val layout: String = "StandaloneLayout", + @field:JsonRawValue + val presets: JsArrayOfObjects = """[ + SwaggerUIBundle.presets.apis, + SwaggerUIStandalonePreset + ]""", + @field:JsonRawValue + val plugins: JsArrayOfObjects? = null +// = """[ +// SwaggerUIBundle.plugins.DownloadUrl +// ]""" +) { + @field:JsonProperty("dom_id") + private val domId: String = "#swagger-ui" + + @field:JsonRawValue + val url = if (url.contains("""["']""".toRegex())) { + //probably raw js. use as is. + url + } + // otherwise probably a path/url. wrap in quotes. + else "'$url'" +} + +private const val JS_UNDEFINED = "undefined" + +// JavaScript concatenated string (window.location.origin + '/something') or plain string +typealias JsOrString = String +// function() {} or () => {} +typealias JsFunction = String +// [{}, GlobalObject...] +typealias JsArrayOfObjects = String +// any dom element to attach the ui to +typealias JsElementRef = String diff --git a/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/SwaggerUiResource.kt b/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/SwaggerUiResource.kt new file mode 100644 index 0000000..ec9447e --- /dev/null +++ b/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/SwaggerUiResource.kt @@ -0,0 +1,32 @@ +package com.muliyul.dropwizard.swagger.ui + +import com.fasterxml.jackson.databind.* +import io.swagger.v3.oas.annotations.* +import javax.ws.rs.* +import javax.ws.rs.core.* + +@Path("/swagger") +class SwaggerResource( + private val mapper: ObjectMapper, + private val uiConfiguration: SwaggerUiConfiguration +) { + @get:GET + @get:Consumes(MediaType.WILDCARD) + @get:Produces(MediaType.TEXT_HTML) + @get:Operation(hidden = true) + val index by lazy { + val originalIndexInputStream = ClassLoader.getSystemClassLoader() + .getResourceAsStream("static/swagger-ui/index.html") ?: error("Could not find index.html in the classpath.") + + val originalIndex = originalIndexInputStream + .reader() + .readText() + .replace("./", "./swagger-ui/") + + val swaggerUiConfigurationRegex = """.*\((?\{(.*\s*)*})\).*""".toRegex() + val (before, after) = swaggerUiConfigurationRegex.split(originalIndex, 2) + + val prettyPrinter = mapper.writer().withDefaultPrettyPrinter() + "${before};var ui = SwaggerUIBundle(${prettyPrinter.writeValueAsString(uiConfiguration)});$after" + } +} diff --git a/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/SyntaxHighlight.kt b/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/SyntaxHighlight.kt new file mode 100644 index 0000000..66ca7ea --- /dev/null +++ b/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/SyntaxHighlight.kt @@ -0,0 +1,6 @@ +package com.muliyul.dropwizard.swagger.ui + +data class SyntaxHighlight( + val activate: Boolean = true, + val theme: Theme = Theme.Agate +) diff --git a/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/Theme.kt b/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/Theme.kt new file mode 100644 index 0000000..00e448b --- /dev/null +++ b/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/Theme.kt @@ -0,0 +1,12 @@ +package com.muliyul.dropwizard.swagger.ui + +enum class Theme { + Agate, + Arta, + Monokai, + Nord, + Obsidian, + Tomorrow_Night; + + private fun toJson() = this.name.toLowerCase().replace('_', '-') +} diff --git a/src/test/kotlin/com/muliyul/dropwizard/swagger/TestConfiguration.kt b/src/test/kotlin/com/muliyul/dropwizard/swagger/TestConfiguration.kt new file mode 100644 index 0000000..0983dea --- /dev/null +++ b/src/test/kotlin/com/muliyul/dropwizard/swagger/TestConfiguration.kt @@ -0,0 +1,12 @@ +package com.muliyul.dropwizard.swagger + +import com.fasterxml.jackson.annotation.* +import com.muliyul.dropwizard.swagger.ui.* +import io.dropwizard.* + +class TestConfiguration( + @field:JsonProperty("swagger") + override val swaggerConfiguration: io.swagger.v3.oas.integration.SwaggerConfiguration? = null, + @field:JsonProperty("swagger-ui") + override val swaggerUiConfiguration: SwaggerUiConfiguration? = null +) : Configuration(), SwaggerConfiguration diff --git a/src/test/kotlin/com/muliyul/dropwizard/swagger/TestResource.kt b/src/test/kotlin/com/muliyul/dropwizard/swagger/TestResource.kt new file mode 100644 index 0000000..a6d47bd --- /dev/null +++ b/src/test/kotlin/com/muliyul/dropwizard/swagger/TestResource.kt @@ -0,0 +1,24 @@ +package com.muliyul.dropwizard.swagger + +import io.dropwizard.auth.* +import io.swagger.v3.oas.annotations.* +import io.swagger.v3.oas.annotations.security.* +import javax.ws.rs.* +import javax.ws.rs.core.* + +@Path("/") +class TestResource { + @GET + @Path("/sanity") + @Consumes(MediaType.WILDCARD) + @Produces(MediaType.TEXT_HTML) + fun sanity() = "Sanity" + + @GET + @Path("/ignoring_auth") + @Consumes(MediaType.WILDCARD) + @Produces(MediaType.TEXT_HTML) + @Operation(security = [SecurityRequirement(name = "petstore_auth")]) + fun ignoring_auth(@Auth user: User) = "Sanity" +} + diff --git a/src/test/kotlin/com/muliyul/dropwizard/swagger/TestSwaggerApplication.kt b/src/test/kotlin/com/muliyul/dropwizard/swagger/TestSwaggerApplication.kt new file mode 100644 index 0000000..8d2a45d --- /dev/null +++ b/src/test/kotlin/com/muliyul/dropwizard/swagger/TestSwaggerApplication.kt @@ -0,0 +1,22 @@ +package com.muliyul.dropwizard.swagger + +import io.dropwizard.* +import io.dropwizard.configuration.* +import io.dropwizard.setup.* + +class TestSwaggerApplication: Application() { + override fun initialize(bootstrap: Bootstrap) { + bootstrap.configurationSourceProvider = ResourceConfigurationSourceProvider() + bootstrap.addBundle(SwaggerBundle("com.muliyul.dropwizard.swagger")) + } + + override fun run(configuration: TestConfiguration, environment: Environment) { + environment.jersey().register(TestResource()) + } + + companion object { + @JvmStatic + fun main(args: Array) = TestSwaggerApplication().run(*args) + } +} + diff --git a/src/test/kotlin/com/muliyul/dropwizard/swagger/User.kt b/src/test/kotlin/com/muliyul/dropwizard/swagger/User.kt new file mode 100644 index 0000000..d410234 --- /dev/null +++ b/src/test/kotlin/com/muliyul/dropwizard/swagger/User.kt @@ -0,0 +1,7 @@ +package com.muliyul.dropwizard.swagger + +import java.security.* + +class User: Principal { + override fun getName(): String = toString() +} diff --git a/src/test/kotlin/com/muliyul/dropwizard/swagger/ext/Selenium.kt b/src/test/kotlin/com/muliyul/dropwizard/swagger/ext/Selenium.kt new file mode 100644 index 0000000..190779f --- /dev/null +++ b/src/test/kotlin/com/muliyul/dropwizard/swagger/ext/Selenium.kt @@ -0,0 +1,17 @@ +package com.muliyul.dropwizard.swagger.ext + +import org.openqa.selenium.* +import org.openqa.selenium.support.ui.* + +fun WebDriver.wait(timeoutInSeconds: Long) = WebDriverWait(this, timeoutInSeconds) + +fun WebDriverWait.untilError(block: (WebDriver) -> T) = try { + until { block(it) } +} catch (e: Throwable) { + throw Error(e) +} + +val WebDriver.origin + get() = currentUrl.substring(0, currentUrl.indexOf("/", "https://".length)) + +fun WebDriver.relativeGet(path: String) = get(currentUrl + path.removePrefix("/")) diff --git a/src/test/kotlin/com/muliyul/dropwizard/swagger/integration/SwaggerIntegrationTest.kt b/src/test/kotlin/com/muliyul/dropwizard/swagger/integration/SwaggerIntegrationTest.kt new file mode 100644 index 0000000..9878464 --- /dev/null +++ b/src/test/kotlin/com/muliyul/dropwizard/swagger/integration/SwaggerIntegrationTest.kt @@ -0,0 +1,44 @@ +package com.muliyul.dropwizard.swagger.integration + +import com.muliyul.dropwizard.swagger.* +import io.dropwizard.testing.* +import io.dropwizard.testing.junit5.* +import io.github.bonigarcia.seljup.* +import org.glassfish.jersey.client.proxy.* +import org.junit.jupiter.api.* +import org.junit.jupiter.api.extension.* +import org.junit.jupiter.api.extension.Extensions +import org.openqa.selenium.chrome.* +import kotlin.test.* +import kotlin.test.Test + +@Extensions( + ExtendWith(DropwizardExtensionsSupport::class), + ExtendWith(SeleniumJupiter::class) +) +@SingleSession +class SwaggerIntegrationTest( + @DockerBrowser(type = BrowserType.CHROME) private val webDriver: ChromeDriver +) { + companion object { + @JvmStatic + private val ext = DropwizardAppExtension( + TestSwaggerApplication::class.java, + "config-test.yaml", + ConfigOverride.randomPorts() + ) + } + + @BeforeEach + fun beforeEach() { + webDriver.get("http://localhost:${ext.localPort}") + } + + @Test + fun `should expose default endpoint`() { + SwaggerUiPage(webDriver) + .expandSanity() + + println() + } +} diff --git a/src/test/kotlin/com/muliyul/dropwizard/swagger/integration/SwaggerUiPage.kt b/src/test/kotlin/com/muliyul/dropwizard/swagger/integration/SwaggerUiPage.kt new file mode 100644 index 0000000..58839f5 --- /dev/null +++ b/src/test/kotlin/com/muliyul/dropwizard/swagger/integration/SwaggerUiPage.kt @@ -0,0 +1,34 @@ +package com.muliyul.dropwizard.swagger.integration + +import com.muliyul.dropwizard.swagger.ext.* +import org.openqa.selenium.remote.* +import org.openqa.selenium.support.* +import org.openqa.selenium.support.ui.* + +import org.openqa.selenium.support.FindBy +import org.openqa.selenium.WebElement + + +class SwaggerUiPage( + private val webDriver: RemoteWebDriver +) : LoadableComponent() { + @FindBy(id = "operations-default-sanity") + private lateinit var sanityResource: WebElement + + init { + PageFactory.initElements(webDriver, this) + get() + } + + override fun load() { + webDriver.relativeGet("/swagger") + } + + override fun isLoaded() { + webDriver.wait(5).untilError { sanityResource.isDisplayed } + } + + fun expandSanity() { + sanityResource.click() + } +} diff --git a/src/test/resources/config-test.yaml b/src/test/resources/config-test.yaml new file mode 100644 index 0000000..4cd975b --- /dev/null +++ b/src/test/resources/config-test.yaml @@ -0,0 +1,39 @@ +server: + applicationConnectors: + - type: http + port: 8080 + adminConnectors: + - type: http + port: 8081 + +#swagger: +# prettyPrint: true +# cacheTTL: 0 +# openAPI: +# info: +# version: '1.0' +# title: Swagger Pet Sample App Config File +# description: 'This is a sample server Petstore server. You can find out more +# about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, +# #swagger](http://swagger.io/irc/). For this sample, you can use the api key +# `special-key` to test the authorization filters.' +# termsOfService: http://swagger.io/terms/ +# contact: +# email: apiteam@swagger.io +# license: +# name: Apache 2.0 +# url: http://www.apache.org/licenses/LICENSE-2.0.html +# components: +# securitySchemes: +# petstore_auth: +# type: oauth2 +# flows: +# implicit: +# authorizationUrl: http://petstore.swagger.io/oauth/dialog +# scopes: +# write:pets: modify pets in your account +# read:pets: read your pets +# api_key: +# type: apiKey +# name: api_key +# in: header diff --git a/src/test/resources/junit-platform.properties b/src/test/resources/junit-platform.properties new file mode 100644 index 0000000..dd62250 --- /dev/null +++ b/src/test/resources/junit-platform.properties @@ -0,0 +1,2 @@ +junit.jupiter.execution.parallel.enabled=true +junit.jupiter.execution.parallel.mode.default=concurrent diff --git a/src/test/resources/openapi.yaml b/src/test/resources/openapi.yaml new file mode 100644 index 0000000..3f0fd30 --- /dev/null +++ b/src/test/resources/openapi.yaml @@ -0,0 +1,30 @@ +prettyPrint: true +cacheTTL: 0 +openAPI: + info: + version: '1.0' + title: Swagger Pet Sample App Config File + description: 'This is a sample server Petstore server. You can find out more + about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, + #swagger](http://swagger.io/irc/). For this sample, you can use the api key + `special-key` to test the authorization filters.' + termsOfService: http://swagger.io/terms/ + contact: + email: apiteam@swagger.io + license: + name: Apache 2.0 + url: http://www.apache.org/licenses/LICENSE-2.0.html + components: + securitySchemes: + petstore_auth: + type: oauth2 + flows: + implicit: + authorizationUrl: http://petstore.swagger.io/oauth/dialog + scopes: + write:pets: modify pets in your account + read:pets: read your pets + api_key: + type: apiKey + name: api_key + in: header diff --git a/src/test/resources/selenium-jupiter.properties b/src/test/resources/selenium-jupiter.properties new file mode 100644 index 0000000..cd157b0 --- /dev/null +++ b/src/test/resources/selenium-jupiter.properties @@ -0,0 +1,98 @@ +sel.jup.vnc=false +sel.jup.vnc.screen.resolution=1920x1080x24 +sel.jup.vnc.create.redirect.html.page=false +sel.jup.vnc.export=vnc.session.url +sel.jup.recording=false +sel.jup.recording.when.failure=false +sel.jup.recording.video.screen.size=1024x768 +sel.jup.recording.video.frame.rate=12 +sel.jup.recording.image=selenoid/video-recorder:latest-release +sel.jup.output.folder=. +sel.jup.screenshot.at.the.end.of.tests=false +sel.jup.screenshot.format=base64 +sel.jup.exception.when.no.driver=true +sel.jup.browser.template.json.file=classpath:browsers.json +sel.jup.default.browser=chrome-in-docker +sel.jup.default.version=latest +sel.jup.default.browser.fallback=chrome,firefox,safari,edge,phantomjs +sel.jup.default.browser.fallback.version=latest,latest,latest,latest,latest +sel.jup.remote.webdriver.wait.timeout.sec=20 +sel.jup.remote.webdriver.poll.time.sec=2 +sel.jup.ttl.sec=86400 +sel.jup.wdm.use.preferences=true + +sel.jup.browser.list.from.docker.hub=true +sel.jup.browser.session.timeout.duration=1m0s +sel.jup.browser.list.in.parallel=true +sel.jup.selenoid.image=aerokube/selenoid:1.10.0 +sel.jup.selenoid.port=4444 +sel.jup.selenoid.vnc.password=selenoid +sel.jup.selenoid.tmpfs.size=128m +sel.jup.novnc.image=psharkey/novnc:3.3-t6 +sel.jup.novnc.port=8080 +sel.jup.chrome.image.format=selenoid/vnc:chrome_%s +sel.jup.chrome.first.version=48.0 +sel.jup.chrome.latest.version=79.0 +sel.jup.chrome.path=/ +sel.jup.chrome.beta.image=elastestbrowsers/chrome:beta +sel.jup.chrome.beta.path=/wd/hub +sel.jup.chrome.unstable.image=elastestbrowsers/chrome:unstable +sel.jup.chrome.unstable.path=/wd/hub +sel.jup.firefox.image.format=selenoid/vnc:firefox_%s +sel.jup.firefox.first.version=3.6 +sel.jup.firefox.latest.version=71.0 +sel.jup.firefox.path=/wd/hub +sel.jup.firefox.beta.image=elastestbrowsers/firefox:beta +sel.jup.firefox.beta.path=/wd/hub +sel.jup.firefox.unstable.image=elastestbrowsers/firefox:nightly +sel.jup.firefox.unstable.path=/wd/hub +sel.jup.opera.image.format=selenoid/vnc:opera_%s +sel.jup.opera.first.version=33.0 +sel.jup.opera.latest.version=65.0 +sel.jup.opera.path=/ +sel.jup.opera.binary.path.linux=/usr/bin/opera +sel.jup.opera.binary.path.win=C:\\Program Files\\Opera\\launcher.exe +sel.jup.opera.binary.path.mac=/Applications/Opera.app/Contents/MacOS/Opera +sel.jup.edge.image=windows/edge:%s +sel.jup.edge.path=/ +sel.jup.edge.latest.version=18 +sel.jup.iexplorer.image=windows/ie:%s +sel.jup.iexplorer.path=/ +sel.jup.iexplorer.latest.version=11 + +sel.jup.android.default.version=9.0 +sel.jup.android.image.5.0.1=butomo1989/docker-android-x86-5.0.1:1.5-p6 +sel.jup.android.image.5.1.1=butomo1989/docker-android-x86-5.1.1:1.5-p6 +sel.jup.android.image.6.0=butomo1989/docker-android-x86-6.0:1.5-p6 +sel.jup.android.image.7.0.1=butomo1989/docker-android-x86-7.0:1.5-p6 +sel.jup.android.image.7.1.1=butomo1989/docker-android-x86-7.1.1:1.5-p6 +sel.jup.android.image.8.0=butomo1989/docker-android-x86-8.0:1.5-p6 +sel.jup.android.image.8.1=butomo1989/docker-android-x86-8.1:1.5-p6 +sel.jup.android.image.9.0=butomo1989/docker-android-x86-9.0:1.5-p6 +sel.jup.android.image.genymotion=budtmo/docker-android-genymotion:1.7-p0 +sel.jup.android.novnc.port=6080 +sel.jup.android.appium.port=4723 +sel.jup.android.device.name=Samsung Galaxy S6 +sel.jup.android.device.timeout.sec=200 +sel.jup.android.device.startup.timeout.sec=75 +sel.jup.android.appium.ping.period.sec=10 +sel.jup.android.logging=false +sel.jup.android.logs.folder=androidLogs +sel.jup.android.screen.width=1900 +sel.jup.android.screen.height=900 +sel.jup.android.screen.depth=24+32 + +sel.jup.docker.wait.timeout.sec=20 +sel.jup.docker.poll.time.ms=200 +sel.jup.docker.default.socket=/var/run/docker.sock +sel.jup.docker.hub.url=https://hub.docker.com/ +sel.jup.docker.stop.timeout.sec=5 +sel.jup.docker.api.version=1.35 +sel.jup.docker.network=bridge +sel.jup.docker.timezone=Europe/Madrid +sel.jup.docker.lang=en +sel.jup.docker.startup.timeout.duration=3m + +sel.jup.server.port=4042 +sel.jup.server.path=/wd/hub +sel.jup.server.timeout.sec=180 diff --git a/update-swagger-ui.sh b/update-swagger-ui.sh new file mode 100644 index 0000000..306d86f --- /dev/null +++ b/update-swagger-ui.sh @@ -0,0 +1,15 @@ +git clone git@github.com:swagger-api/swagger-ui.git --no-checkout tmp --depth=1 +git config core.sparseCheckout true +git sparse-checkout init --cone # to fetch only root files +git sparse-checkout set dist # etc, to list sub-folders to checkout +git read-tree -mu HEAD +mkdir -p src/main/resources/static/swagger-ui +cd tmp || exit 1 +mv tmp/dist/favicon-16x16.png src/main/resources/static/swagger-ui +mv tmp/dist/favicon-32x32.png src/main/resources/static/swagger-ui +mv tmp/dist/swagger-ui.css src/main/resources/static/swagger-ui +mv tmp/dist/index.html src/main/resources/static/swagger-ui +mv tmp/dist/oauth2-redirect.html src/main/resources/static/swagger-ui +mv tmp/dist/swager-ui-bundle.js src/main/resources/static/swagger-ui +mv tmp/dist/swager-ui-standalone-preset.js src/main/resources/static/swagger-ui +rm -rf tmp From d9df0c66b016d1dd65d425a5fca4d74e4237e5e9 Mon Sep 17 00:00:00 2001 From: Muli Yulzary Date: Fri, 15 Jan 2021 08:09:20 +0200 Subject: [PATCH 02/18] add missing branch keys in ci.yml --- .github/workflows/ci.yml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 755e9af..c1829ae 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,10 +2,12 @@ name: Dropwizard Swagger CI on: push: - - master + branches: + - master pull_request: - - master + branches: + - master jobs: build: From f6b69cf2378d3cf0f42cd2186c0a4e225a081f5c Mon Sep 17 00:00:00 2001 From: Muli Yulzary Date: Fri, 15 Jan 2021 08:17:04 +0200 Subject: [PATCH 03/18] use main not master --- .github/workflows/ci.yml | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c1829ae..1d6286c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -3,11 +3,10 @@ name: Dropwizard Swagger CI on: push: branches: - - master - + - main pull_request: branches: - - master + - main jobs: build: From fdab91a0c25a22a5752d6aab2d92a8d8b58a5e83 Mon Sep 17 00:00:00 2001 From: Muli Yulzary Date: Fri, 15 Jan 2021 08:19:12 +0200 Subject: [PATCH 04/18] allow script execution --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1d6286c..e3a4f49 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,9 @@ jobs: restore-keys: ${{ runner.os }}-gradle - name: Clone Swagger UI distro - run: ${GITHUB_WORKSPACE}/update-swagger-ui.sh + run: | + chmod +x ./${GITHUB_WORKSPACE}/update-swagger-ui.sh + ./${GITHUB_WORKSPACE}/update-swagger-ui.sh - name: Build run: ./gradlew build From 0de67b8757831a6b381959f049656a7a768cabb0 Mon Sep 17 00:00:00 2001 From: Muli Yulzary Date: Fri, 15 Jan 2021 08:21:15 +0200 Subject: [PATCH 05/18] fix syntax error --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e3a4f49..c60362a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -30,7 +30,7 @@ jobs: - name: Clone Swagger UI distro run: | chmod +x ./${GITHUB_WORKSPACE}/update-swagger-ui.sh - ./${GITHUB_WORKSPACE}/update-swagger-ui.sh + "${GITHUB_WORKSPACE}/update-swagger-ui.sh" - name: Build run: ./gradlew build From 1c70123bbc00ed382a91393addbc88d1c89ccf28 Mon Sep 17 00:00:00 2001 From: Muli Yulzary Date: Fri, 15 Jan 2021 08:22:36 +0200 Subject: [PATCH 06/18] fix syntax error --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index c60362a..9c54efe 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -29,7 +29,7 @@ jobs: - name: Clone Swagger UI distro run: | - chmod +x ./${GITHUB_WORKSPACE}/update-swagger-ui.sh + chmod +x "./${GITHUB_WORKSPACE}/update-swagger-ui.sh" "${GITHUB_WORKSPACE}/update-swagger-ui.sh" - name: Build From 02ccceecdf83c827b6556a541643c422469f2ecd Mon Sep 17 00:00:00 2001 From: Muli Yulzary Date: Fri, 15 Jan 2021 08:28:34 +0200 Subject: [PATCH 07/18] fix syntax error --- .github/workflows/ci.yml | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9c54efe..4cd3bd7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,9 +28,7 @@ jobs: restore-keys: ${{ runner.os }}-gradle - name: Clone Swagger UI distro - run: | - chmod +x "./${GITHUB_WORKSPACE}/update-swagger-ui.sh" - "${GITHUB_WORKSPACE}/update-swagger-ui.sh" + run: sh ./${GITHUB_WORKSPACE}/update-swagger-ui.sh - name: Build run: ./gradlew build From a3769ab2d66f3aa9f44d901e62e02151c2185e7e Mon Sep 17 00:00:00 2001 From: Muli Yulzary Date: Fri, 15 Jan 2021 08:29:45 +0200 Subject: [PATCH 08/18] fix path --- .github/workflows/ci.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 4cd3bd7..85d91c7 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -28,7 +28,7 @@ jobs: restore-keys: ${{ runner.os }}-gradle - name: Clone Swagger UI distro - run: sh ./${GITHUB_WORKSPACE}/update-swagger-ui.sh + run: sh ./update-swagger-ui.sh - name: Build run: ./gradlew build From 72c3be3d686dd46f18d96bb2198b12388fc1b304 Mon Sep 17 00:00:00 2001 From: Muli Yulzary Date: Fri, 15 Jan 2021 08:30:58 +0200 Subject: [PATCH 09/18] add mkdir tmp --- update-swagger-ui.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/update-swagger-ui.sh b/update-swagger-ui.sh index 306d86f..ba50b92 100644 --- a/update-swagger-ui.sh +++ b/update-swagger-ui.sh @@ -4,6 +4,7 @@ git sparse-checkout init --cone # to fetch only root files git sparse-checkout set dist # etc, to list sub-folders to checkout git read-tree -mu HEAD mkdir -p src/main/resources/static/swagger-ui +mkdir tmp cd tmp || exit 1 mv tmp/dist/favicon-16x16.png src/main/resources/static/swagger-ui mv tmp/dist/favicon-32x32.png src/main/resources/static/swagger-ui From e2952262e2b12d15036ef7b2323c856de6ade8f3 Mon Sep 17 00:00:00 2001 From: Muli Yulzary Date: Fri, 15 Jan 2021 08:37:16 +0200 Subject: [PATCH 10/18] use https to clone and add exit clauses to mvs --- update-swagger-ui.sh | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/update-swagger-ui.sh b/update-swagger-ui.sh index ba50b92..d138b84 100644 --- a/update-swagger-ui.sh +++ b/update-swagger-ui.sh @@ -1,4 +1,4 @@ -git clone git@github.com:swagger-api/swagger-ui.git --no-checkout tmp --depth=1 +git clone https://github.com/swagger-api/swagger-ui --no-checkout tmp --depth=1 git config core.sparseCheckout true git sparse-checkout init --cone # to fetch only root files git sparse-checkout set dist # etc, to list sub-folders to checkout @@ -6,11 +6,11 @@ git read-tree -mu HEAD mkdir -p src/main/resources/static/swagger-ui mkdir tmp cd tmp || exit 1 -mv tmp/dist/favicon-16x16.png src/main/resources/static/swagger-ui -mv tmp/dist/favicon-32x32.png src/main/resources/static/swagger-ui -mv tmp/dist/swagger-ui.css src/main/resources/static/swagger-ui -mv tmp/dist/index.html src/main/resources/static/swagger-ui -mv tmp/dist/oauth2-redirect.html src/main/resources/static/swagger-ui -mv tmp/dist/swager-ui-bundle.js src/main/resources/static/swagger-ui -mv tmp/dist/swager-ui-standalone-preset.js src/main/resources/static/swagger-ui +mv tmp/dist/favicon-16x16.png src/main/resources/static/swagger-ui || exit 1 +mv tmp/dist/favicon-32x32.png src/main/resources/static/swagger-ui || exit 1 +mv tmp/dist/swagger-ui.css src/main/resources/static/swagger-ui || exit 1 +mv tmp/dist/index.html src/main/resources/static/swagger-ui || exit 1 +mv tmp/dist/oauth2-redirect.html src/main/resources/static/swagger-ui || exit 1 +mv tmp/dist/swager-ui-bundle.js src/main/resources/static/swagger-ui || exit 1 +mv tmp/dist/swager-ui-standalone-preset.js src/main/resources/static/swagger-ui || exit 1 rm -rf tmp From aec0d8b3726f73b8b54f0230bc1b68d181e2c97e Mon Sep 17 00:00:00 2001 From: Muli Yulzary Date: Fri, 15 Jan 2021 08:38:31 +0200 Subject: [PATCH 11/18] mkdir if not exists --- update-swagger-ui.sh | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/update-swagger-ui.sh b/update-swagger-ui.sh index d138b84..861a6dd 100644 --- a/update-swagger-ui.sh +++ b/update-swagger-ui.sh @@ -4,7 +4,7 @@ git sparse-checkout init --cone # to fetch only root files git sparse-checkout set dist # etc, to list sub-folders to checkout git read-tree -mu HEAD mkdir -p src/main/resources/static/swagger-ui -mkdir tmp +mkdir -p tmp cd tmp || exit 1 mv tmp/dist/favicon-16x16.png src/main/resources/static/swagger-ui || exit 1 mv tmp/dist/favicon-32x32.png src/main/resources/static/swagger-ui || exit 1 From 24e4fb3fa720e8e3e394f4bfd31e821f629591ab Mon Sep 17 00:00:00 2001 From: Muli Yulzary Date: Fri, 15 Jan 2021 18:25:59 +0200 Subject: [PATCH 12/18] fix update-swagger-ui.sh --- update-swagger-ui.sh | 19 +++++++++---------- 1 file changed, 9 insertions(+), 10 deletions(-) diff --git a/update-swagger-ui.sh b/update-swagger-ui.sh index 861a6dd..66f6467 100644 --- a/update-swagger-ui.sh +++ b/update-swagger-ui.sh @@ -1,16 +1,15 @@ -git clone https://github.com/swagger-api/swagger-ui --no-checkout tmp --depth=1 +rm -rf tmp +git clone https://github.com/swagger-api/swagger-ui.git --no-checkout tmp --depth=1 +cd tmp || exit 1 git config core.sparseCheckout true git sparse-checkout init --cone # to fetch only root files git sparse-checkout set dist # etc, to list sub-folders to checkout git read-tree -mu HEAD +cd .. mkdir -p src/main/resources/static/swagger-ui -mkdir -p tmp -cd tmp || exit 1 -mv tmp/dist/favicon-16x16.png src/main/resources/static/swagger-ui || exit 1 -mv tmp/dist/favicon-32x32.png src/main/resources/static/swagger-ui || exit 1 -mv tmp/dist/swagger-ui.css src/main/resources/static/swagger-ui || exit 1 -mv tmp/dist/index.html src/main/resources/static/swagger-ui || exit 1 -mv tmp/dist/oauth2-redirect.html src/main/resources/static/swagger-ui || exit 1 -mv tmp/dist/swager-ui-bundle.js src/main/resources/static/swagger-ui || exit 1 -mv tmp/dist/swager-ui-standalone-preset.js src/main/resources/static/swagger-ui || exit 1 +mv tmp/dist/*.png src/main/resources/static/swagger-ui/ || exit 1 +mv tmp/dist/*.css src/main/resources/static/swagger-ui/ || exit 1 +mv tmp/dist/*.html src/main/resources/static/swagger-ui/ || exit 1 +mv tmp/dist/swagger-ui-bundle.* src/main/resources/static/swagger-ui/ || exit 1 +mv tmp/dist/swagger-ui-standalone-preset.* src/main/resources/static/swagger-ui/ || exit 1 rm -rf tmp From 923ee172681febec57ed0b33f603be8e20669a9f Mon Sep 17 00:00:00 2001 From: Muli Yulzary Date: Fri, 15 Jan 2021 18:32:13 +0200 Subject: [PATCH 13/18] add script hint, fix gradle run --- .github/workflows/ci.yml | 2 +- update-swagger-ui.sh | 2 ++ 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 85d91c7..b7c4b23 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,4 +31,4 @@ jobs: run: sh ./update-swagger-ui.sh - name: Build - run: ./gradlew build + run: sh ./gradlew build diff --git a/update-swagger-ui.sh b/update-swagger-ui.sh index 66f6467..d12d4a4 100644 --- a/update-swagger-ui.sh +++ b/update-swagger-ui.sh @@ -1,3 +1,5 @@ +#!/usr/bin/env sh + rm -rf tmp git clone https://github.com/swagger-api/swagger-ui.git --no-checkout tmp --depth=1 cd tmp || exit 1 From 07979b854436c0f728ed1e5181f8d90d29c4751c Mon Sep 17 00:00:00 2001 From: Muli Yulzary Date: Fri, 15 Jan 2021 18:36:32 +0200 Subject: [PATCH 14/18] use gradle action --- .github/workflows/ci.yml | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b7c4b23..a9b86c1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -31,4 +31,7 @@ jobs: run: sh ./update-swagger-ui.sh - name: Build - run: sh ./gradlew build + uses: eskatos/gradle-command-action@v1 + with: + gradle-version: 6.7 + arguments: build From e1cc6a8611f2f7d31f341066db1a21b105a74a5c Mon Sep 17 00:00:00 2001 From: Muli Yulzary Date: Fri, 15 Jan 2021 18:44:31 +0200 Subject: [PATCH 15/18] fix gradle build step --- .github/workflows/ci.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index a9b86c1..c04dca2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -32,6 +32,6 @@ jobs: - name: Build uses: eskatos/gradle-command-action@v1 - with: - gradle-version: 6.7 - arguments: build + with: + gradle-version: 6.7 + arguments: build From c9001b8d1bd02f0859ee6401334f7337a5315b64 Mon Sep 17 00:00:00 2001 From: Muli Yulzary Date: Tue, 16 Feb 2021 05:09:47 +0200 Subject: [PATCH 16/18] - add Java builder - add UI configuration - simplify DropwizardCompatibleReader - add DropwizardCompatibleScanner (filter irrelevant resources) - update openapi.yaml to v3 --- README.md | 81 ++ build.gradle | 32 +- ...eader.kt => DropwizardCompatibleReader.kt} | 27 +- .../swagger/DropwizardCompatibleScanner.kt | 15 + .../dropwizard/swagger/SwaggerBundle.kt | 44 +- .../swagger/SwaggerBundleConfiguration.kt | 7 + .../swagger/SwaggerConfiguration.kt | 11 - .../muliyul/dropwizard/swagger/ui/Aliases.kt | 8 + .../swagger/ui/SwaggerUiResource.kt | 5 +- .../muliyul/dropwizard/swagger/ui/Theme.kt | 4 + .../swagger/ui/configuration/Builder.kt | 174 ++++ .../SwaggerUiConfiguration.kt | 29 +- .../swagger/TestJavaApplication.java | 24 + .../swagger/TestJavaConfiguration.java | 17 + .../dropwizard/swagger/TestConfiguration.kt | 12 - .../swagger/TestKotlinApplication.kt | 24 + .../swagger/TestKotlinConfiguration.kt | 12 + .../dropwizard/swagger/TestResource.kt | 11 +- .../swagger/TestSwaggerApplication.kt | 22 - .../com/muliyul/dropwizard/swagger/User.kt | 6 +- .../swagger/bundles/auth/AuthBundle.kt | 28 + .../bundles/auth/BasicAuthenticator.kt | 13 + .../dropwizard/swagger/ext/Selenium.kt | 2 +- .../integration/SwaggerIntegrationTest.kt | 11 +- src/test/resources/openapi.yaml | 832 +++++++++++++++++- 25 files changed, 1282 insertions(+), 169 deletions(-) create mode 100644 README.md rename src/main/kotlin/com/muliyul/dropwizard/swagger/{AutoAuthReader.kt => DropwizardCompatibleReader.kt} (54%) create mode 100644 src/main/kotlin/com/muliyul/dropwizard/swagger/DropwizardCompatibleScanner.kt create mode 100644 src/main/kotlin/com/muliyul/dropwizard/swagger/SwaggerBundleConfiguration.kt delete mode 100644 src/main/kotlin/com/muliyul/dropwizard/swagger/SwaggerConfiguration.kt create mode 100644 src/main/kotlin/com/muliyul/dropwizard/swagger/ui/Aliases.kt create mode 100644 src/main/kotlin/com/muliyul/dropwizard/swagger/ui/configuration/Builder.kt rename src/main/kotlin/com/muliyul/dropwizard/swagger/ui/{ => configuration}/SwaggerUiConfiguration.kt (81%) create mode 100644 src/test/java/com/muliyul/dropwizard/swagger/TestJavaApplication.java create mode 100644 src/test/java/com/muliyul/dropwizard/swagger/TestJavaConfiguration.java delete mode 100644 src/test/kotlin/com/muliyul/dropwizard/swagger/TestConfiguration.kt create mode 100644 src/test/kotlin/com/muliyul/dropwizard/swagger/TestKotlinApplication.kt create mode 100644 src/test/kotlin/com/muliyul/dropwizard/swagger/TestKotlinConfiguration.kt delete mode 100644 src/test/kotlin/com/muliyul/dropwizard/swagger/TestSwaggerApplication.kt create mode 100644 src/test/kotlin/com/muliyul/dropwizard/swagger/bundles/auth/AuthBundle.kt create mode 100644 src/test/kotlin/com/muliyul/dropwizard/swagger/bundles/auth/BasicAuthenticator.kt diff --git a/README.md b/README.md new file mode 100644 index 0000000..211cef5 --- /dev/null +++ b/README.md @@ -0,0 +1,81 @@ +dropwizard-swagger +================== + +A Dropwizard bundle inspired by the original [dropwizard-swagger](https://github.com/federecio/dropwizard-swagger) and its popular [fork](https://github.com/smoketurner/dropwizard-swagger) +that serves [Swagger UI](https://github.com/swagger-api/swagger-ui) and loads [OpenApi](https://github.com/OAI/OpenAPI-Specification) 3.0 (or Swagger2) endpoints. + +#### Notable improvements: + +- Spec can be defined in standard locations (see [this](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#known-locations)). `config.yaml` takes precedence. +- UI is configurable (programmatically or via `config.yaml`). +- If `dropwizard-auth` is included, then by default your `@Auth` annotated parameters will be marked as `@Hidden` so they won't interfere with request body. + +### Installation + +#### Maven +```xml + +com.muliyul +dropwizard-swagger +0.0.1 + +``` + +#### Gradle +```groovy +implementation('com.muliyul:dropwizard-swagger:0.0.1') +``` + +### Usage + +See [example](src/test/java/com/muliyul/dropwizard/swagger/TestJavaConfiguration.java) +```java +class AppConfiguration extends Configuration implements com.muliyul.dropwizard.swagger.SwaggerConfiguration { + @JsonProperty("swagger") + private SwaggerConfiguration swaggerConfiguration = null; + @JsonProperty("swagger-ui") + private SwaggerUiConfiguration swaggerUiConfiguration = null; + + public SwaggerConfiguration getSwaggerConfiguration() { + return swaggerConfiguration; + } + + public SwaggerUiConfiguration getSwaggerUiConfiguration() { + return swaggerUiConfiguration; + } +} +``` + +#### In your Application class: +```java +@Override +public void initialize(Bootstrap bootstrap) { + bootstrap.addBundle(new SwaggerBundle<>()); + + // or pass the packages to scan + // bootstrap.addBundle(new SwaggerBundle("com.example.resources", "com.example.resources2")); + // or specify them in config.yaml + // or specify them in one of the known OpenApi spec locations + // the choice is yours! +} +``` + +That's it! + +The bundle will scan your classpath for any resources and expose them to swagger-ui via `/swagger`. + +You can access the complete definitions in `/openapi`, `/openapi.json`, `/openapi.yaml` [see this](https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#openapiresource). + +#### Configuring Swagger-UI + +See [SwaggerUiConfiguration](src/main/kotlin/com/muliyul/dropwizard/swagger/ui/configuration/SwaggerUiConfiguration.kt) + +--- +Check out OpenApi spec file/definitions [here](). + +Check out the available Swagger-UI options [here](). + +--- +## Development + +Clone the project and run `TestJavaApplication` or `TestKotlinApplication`. diff --git a/build.gradle b/build.gradle index 6f52e1a..9cf38ce 100644 --- a/build.gradle +++ b/build.gradle @@ -18,37 +18,31 @@ java { dependencies { implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") - testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") - - testImplementation platform("org.glassfish.jersey:jersey-bom:2.0+") - testImplementation("org.glassfish.jersey.ext:jersey-proxy-client") implementation platform("io.dropwizard:dropwizard-bom:2.0.17") implementation("io.dropwizard:dropwizard-core") -// implementation("io.dropwizard:dropwizard-client") implementation("io.dropwizard:dropwizard-assets") authImplementation("io.dropwizard:dropwizard-auth") +// implementation("io.dropwizard:dropwizard-client") + + implementation("io.swagger.core.v3:swagger-jaxrs2:2.1.2") + + // Test testImplementation("io.dropwizard:dropwizard-testing") -// implementation platform("com.fasterxml.jackson:jackson-bom:2.10+") -// implementation("com.fasterxml.jackson.module:jackson-module-kotlin") + testImplementation('org.seleniumhq.selenium:selenium-java:3.141.59') + testImplementation('io.github.bonigarcia:selenium-jupiter:3.3.5') + + testImplementation platform("org.glassfish.jersey:jersey-bom:2.33") + testImplementation("org.glassfish.jersey.ext:jersey-proxy-client") + testImplementation("io.dropwizard:dropwizard-auth") - implementation("io.swagger.core.v3:swagger-jaxrs2:2.1.2") - // Junit + // JUnit + testImplementation("org.jetbrains.kotlin:kotlin-test-junit5") testImplementation platform("org.junit:junit-bom:5.7.0") testImplementation("org.junit.jupiter:junit-jupiter-api") testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine") - - testImplementation('org.seleniumhq.selenium:selenium-java:3.141.59') -// testImplementation('org.seleniumhq.selenium:selenium-chrome-driver:3.141.59') - // TODO: introduced in 4.0.0 - //testImplementation('org.seleniumhq.selenium:selenium-chromium-driver:4.0.0') -// testImplementation('org.seleniumhq.selenium:selenium-firefox-driver:3.141.59') -// testImplementation('org.seleniumhq.selenium:selenium-ie-driver:3.141.59') -// testImplementation('org.seleniumhq.selenium:selenium-edge-driver:3.141.59') -// testImplementation('org.seleniumhq.selenium:selenium-opera-driver:3.141.59') - testImplementation('io.github.bonigarcia:selenium-jupiter:3.3.5') } compileKotlin { diff --git a/src/main/kotlin/com/muliyul/dropwizard/swagger/AutoAuthReader.kt b/src/main/kotlin/com/muliyul/dropwizard/swagger/DropwizardCompatibleReader.kt similarity index 54% rename from src/main/kotlin/com/muliyul/dropwizard/swagger/AutoAuthReader.kt rename to src/main/kotlin/com/muliyul/dropwizard/swagger/DropwizardCompatibleReader.kt index c0ccd70..e42cf01 100644 --- a/src/main/kotlin/com/muliyul/dropwizard/swagger/AutoAuthReader.kt +++ b/src/main/kotlin/com/muliyul/dropwizard/swagger/DropwizardCompatibleReader.kt @@ -3,17 +3,14 @@ package com.muliyul.dropwizard.swagger import com.fasterxml.jackson.annotation.* import io.dropwizard.auth.* import io.swagger.v3.jaxrs2.* -import io.swagger.v3.oas.annotations.* -import io.swagger.v3.oas.models.Operation -import io.swagger.v3.oas.models.security.* +import io.swagger.v3.oas.models.* import java.lang.reflect.* import javax.ws.rs.* -open class AutoAuthReader : Reader() { - @Suppress("unused") - @field:Hidden - private val annHolder: String? = null - +/** + * This class instructs Swagger to ignore [Auth] + */ +open class DropwizardCompatibleReader : Reader() { override fun getParameters( type: Type?, annotations: MutableList?, @@ -24,7 +21,7 @@ open class AutoAuthReader : Reader() { ): ResolvedParameter { val isAuthParam = annotations?.any { it.annotationClass == Auth::class } == true - return if (isAuthParam) super.getParameters( + return super.getParameters( type, annotations, operation, @@ -32,16 +29,8 @@ open class AutoAuthReader : Reader() { methodConsumes, jsonViewAnnotation ).apply { - // Instructs swagger-core to ignore this param as a body param - requestBody = null + // instructs Swagger to ignore Auth + if (isAuthParam) requestBody = null } - else super.getParameters( - type, - annotations, - operation, - classConsumes, - methodConsumes, - jsonViewAnnotation - ) } } diff --git a/src/main/kotlin/com/muliyul/dropwizard/swagger/DropwizardCompatibleScanner.kt b/src/main/kotlin/com/muliyul/dropwizard/swagger/DropwizardCompatibleScanner.kt new file mode 100644 index 0000000..aa5e2e2 --- /dev/null +++ b/src/main/kotlin/com/muliyul/dropwizard/swagger/DropwizardCompatibleScanner.kt @@ -0,0 +1,15 @@ +package com.muliyul.dropwizard.swagger + +import io.swagger.v3.jaxrs2.integration.* +import io.swagger.v3.oas.integration.api.* + +private val ignoredPackages = setOf("com.papertrail.profiler", "org.glassfish.jersey") + +private val delegate = JaxrsApplicationAndAnnotationScanner() + +class DropwizardCompatibleScanner : OpenApiScanner by delegate { + override fun classes(): MutableSet> = + delegate.classes() + .filter { c -> ignoredPackages.none { p -> c.name.startsWith(p, ignoreCase = true) } } + .toMutableSet() +} diff --git a/src/main/kotlin/com/muliyul/dropwizard/swagger/SwaggerBundle.kt b/src/main/kotlin/com/muliyul/dropwizard/swagger/SwaggerBundle.kt index 4987898..c9f95c4 100644 --- a/src/main/kotlin/com/muliyul/dropwizard/swagger/SwaggerBundle.kt +++ b/src/main/kotlin/com/muliyul/dropwizard/swagger/SwaggerBundle.kt @@ -1,47 +1,45 @@ package com.muliyul.dropwizard.swagger import com.muliyul.dropwizard.swagger.ui.* +import com.muliyul.dropwizard.swagger.ui.configuration.* import io.dropwizard.* -import io.dropwizard.Configuration import io.dropwizard.assets.* import io.dropwizard.setup.* import io.swagger.v3.jaxrs2.integration.* import io.swagger.v3.jaxrs2.integration.resources.* -class SwaggerBundle( - private val resourcesPackageNames: Set, - private val uiConfiguration: SwaggerUiConfiguration = SwaggerUiConfiguration() -) : ConfiguredBundle where C : Configuration, C : SwaggerConfiguration { - constructor( - vararg resourcesPackageNames: String, - uiConfiguration: SwaggerUiConfiguration = SwaggerUiConfiguration() - ) : this(resourcesPackageNames.toSet(), uiConfiguration) +class SwaggerBundle @JvmOverloads constructor( + private vararg val resourcesPackageNames: String, + private val swaggerUiConfiguration: SwaggerUiConfiguration = SwaggerUiConfiguration() +) : ConfiguredBundle where C : Configuration, C : SwaggerBundleConfiguration { override fun initialize(bootstrap: Bootstrap<*>) { bootstrap.addBundle(AssetsBundle("/static/swagger-ui/", "/swagger-ui/", null, "swagger")) } override fun run(configuration: C, environment: Environment) { + val swaggerUiConfiguration = configuration.swaggerUiConfiguration ?: swaggerUiConfiguration - val servletConfig = environment.jerseyServletContainer!!.servletConfig - val swaggerConfiguration = configuration.swaggerConfiguration?.apply { - // TODO: check if dropwizard-auth is on the classpath - readerClass = AutoAuthReader::class.qualifiedName - } + val resourcesPackageNames = resourcesPackageNames.toSet() - environment.jersey().run { - JaxrsOpenApiContextBuilder>() - .servletConfig(servletConfig) - .application(resourceConfig) - .resourcePackages(resourcesPackageNames) - .openApiConfiguration(swaggerConfiguration) - .ctxId(ServletConfigContextUtils.getContextIdFromServletConfig(servletConfig)) - .buildContext(true) + JaxrsOpenApiContextBuilder>() + .application(environment.jersey().resourceConfig) + .resourcePackages(resourcesPackageNames) + .buildContext(false) + .apply { + setOpenApiScanner(DropwizardCompatibleScanner()) + setOpenApiReader(DropwizardCompatibleReader()) + } + .init() + environment.jersey().apply { register(AcceptHeaderOpenApiResource().resourcePackages(resourcesPackageNames)) register(OpenApiResource().resourcePackages(resourcesPackageNames)) - register(SwaggerResource(environment.objectMapper, uiConfiguration)) + if (!swaggerUiConfiguration.disabled) { + register(SwaggerResource(environment.objectMapper, swaggerUiConfiguration)) + } } } + } diff --git a/src/main/kotlin/com/muliyul/dropwizard/swagger/SwaggerBundleConfiguration.kt b/src/main/kotlin/com/muliyul/dropwizard/swagger/SwaggerBundleConfiguration.kt new file mode 100644 index 0000000..5bf2f7c --- /dev/null +++ b/src/main/kotlin/com/muliyul/dropwizard/swagger/SwaggerBundleConfiguration.kt @@ -0,0 +1,7 @@ +package com.muliyul.dropwizard.swagger + +import com.muliyul.dropwizard.swagger.ui.configuration.* + +interface SwaggerBundleConfiguration { + val swaggerUiConfiguration: SwaggerUiConfiguration? +} diff --git a/src/main/kotlin/com/muliyul/dropwizard/swagger/SwaggerConfiguration.kt b/src/main/kotlin/com/muliyul/dropwizard/swagger/SwaggerConfiguration.kt deleted file mode 100644 index 6372e7c..0000000 --- a/src/main/kotlin/com/muliyul/dropwizard/swagger/SwaggerConfiguration.kt +++ /dev/null @@ -1,11 +0,0 @@ -package com.muliyul.dropwizard.swagger - -import com.muliyul.dropwizard.swagger.ui.* -import io.swagger.v3.oas.integration.SwaggerConfiguration - -interface SwaggerConfiguration { - val swaggerConfiguration: SwaggerConfiguration? -// fun getSwaggerConfiguration(): SwaggerConfiguration? = null -// fun getSwaggerUiConfiguration(): SwaggerUiConfiguration? = null - val swaggerUiConfiguration: SwaggerUiConfiguration? -} diff --git a/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/Aliases.kt b/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/Aliases.kt new file mode 100644 index 0000000..875d82d --- /dev/null +++ b/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/Aliases.kt @@ -0,0 +1,8 @@ +package com.muliyul.dropwizard.swagger.ui + +// JavaScript concatenated string (window.location.origin + '/something') or plain string +typealias JsStringOrString = String +// function() {} or () => {} +typealias JsFunction = String +// [{}, GlobalObject...] +typealias JsArrayOfObjects = String diff --git a/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/SwaggerUiResource.kt b/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/SwaggerUiResource.kt index ec9447e..6f0f7b1 100644 --- a/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/SwaggerUiResource.kt +++ b/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/SwaggerUiResource.kt @@ -1,6 +1,7 @@ package com.muliyul.dropwizard.swagger.ui import com.fasterxml.jackson.databind.* +import com.muliyul.dropwizard.swagger.ui.configuration.* import io.swagger.v3.oas.annotations.* import javax.ws.rs.* import javax.ws.rs.core.* @@ -8,7 +9,7 @@ import javax.ws.rs.core.* @Path("/swagger") class SwaggerResource( private val mapper: ObjectMapper, - private val uiConfiguration: SwaggerUiConfiguration + private val swaggerUiConfiguration: SwaggerUiConfiguration ) { @get:GET @get:Consumes(MediaType.WILDCARD) @@ -27,6 +28,6 @@ class SwaggerResource( val (before, after) = swaggerUiConfigurationRegex.split(originalIndex, 2) val prettyPrinter = mapper.writer().withDefaultPrettyPrinter() - "${before};var ui = SwaggerUIBundle(${prettyPrinter.writeValueAsString(uiConfiguration)});$after" + "${before}\r\nvar ui = SwaggerUIBundle(${prettyPrinter.writeValueAsString(swaggerUiConfiguration)});\r\n$after" } } diff --git a/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/Theme.kt b/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/Theme.kt index 00e448b..6bb9f89 100644 --- a/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/Theme.kt +++ b/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/Theme.kt @@ -1,5 +1,8 @@ package com.muliyul.dropwizard.swagger.ui +import com.fasterxml.jackson.annotation.* + +@Suppress("unused") enum class Theme { Agate, Arta, @@ -8,5 +11,6 @@ enum class Theme { Obsidian, Tomorrow_Night; + @JsonValue private fun toJson() = this.name.toLowerCase().replace('_', '-') } diff --git a/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/configuration/Builder.kt b/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/configuration/Builder.kt new file mode 100644 index 0000000..49ff6d1 --- /dev/null +++ b/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/configuration/Builder.kt @@ -0,0 +1,174 @@ +package com.muliyul.dropwizard.swagger.ui.configuration + +import com.muliyul.dropwizard.swagger.ui.* + +// Mainly for java users +class Builder { + private var disabled: Boolean = false + private var configUrl: String? = null + private var spec: Map? = null + private var url: JsStringOrString = """window.location.origin + '/openapi'""" + private var urls: List? = null + + // Display + private var deepLinking: Boolean? = null + private var displayOperationId: Boolean? = null + private var defaultModelsExpandDepth: Int? = null + private var defaultModelExpandDepth: Int? = null + private var defaultModelRendering: String? = null + private var displayRequestDuration: Boolean? = null + private var docExpansion: String? = null + private var filter: Boolean? = null + private var maxDisplayedTags: Int? = null + private var operationsSorter: JsFunction? = null + private var tagsSorter: JsFunction? = null + private var showExtensions: Boolean? = null + private var showCommonExtensions: Boolean? = null + private var useUnsafeMarkdown: Boolean? = null + private var syntaxHighlight: SyntaxHighlight? = null + + // Network + private var oauth2RedirectUrl: String? = null + private var requestInterceptor: JsFunction? = null + +// TODO: these break the UI for some reason. not supported in the meantime. +// @field:JsonProperty("request.curlOptions") +// @field:JsonRawValue +// var curlOptions: JsOrString = JS_UNDEFINED, + private var responseInterceptor: JsFunction? = null + private var showMutatedRequest: Boolean? = null + private var supportedSubmitMethods: List? = null + private var validatorUrl: String? = null + private var withCredentials: Boolean? = null + + // Macros + private var modelPropertyMacro: JsFunction? = null + private var parameterMacro: JsFunction? = null + + // Authorization + private var persistAuthorization: Boolean? = null + + //Plugin system + private var layout: String = "StandaloneLayout" + private var presets: JsArrayOfObjects = """[ + SwaggerUIBundle.presets.apis, + SwaggerUIStandalonePreset + ]""" + private var plugins: JsArrayOfObjects? = null + + fun disabled() = apply { + this.disabled = true + } + + fun configUrl(configUrl: String) = apply { + this.configUrl = configUrl + } + + fun spec(spec: Map) = apply { + this.spec = spec + } + + fun url(url: String) = apply { + this.url = url + } + + fun urls(vararg urls: String) = apply { + this.urls = urls.toList() + } + + fun deepLinking(deepLinking: Boolean) = apply { + this.deepLinking = deepLinking + } + + fun displayOperationId(displayOperationId: Boolean) = apply { + this.displayOperationId = displayOperationId + } + + fun defaultModelsExpandDepth(defaultModelsExpandDepth: Int) = apply { + this.defaultModelsExpandDepth = defaultModelsExpandDepth + } + + fun defaultModelExpandDepth(defaultModelExpandDepth: Int) = apply { + this.defaultModelExpandDepth = defaultModelExpandDepth + } + + fun defaultModelRendering(defaultModelRendering: String) = apply { + this.defaultModelRendering = defaultModelRendering + } + + fun displayRequestDuration(displayRequestDuration: Boolean) = apply { + this.displayRequestDuration = displayRequestDuration + } + + fun docExpansion(docExpansion: String) = apply { + this.docExpansion = docExpansion + } + + fun filter(filter: Boolean) = apply { + this.filter = filter + } + + fun maxDisplayedTags(maxDisplayedTags: Int) = apply { + this.maxDisplayedTags = maxDisplayedTags + } + + fun operationsSorter(operationsSorter: JsFunction) = apply { + this.operationsSorter = operationsSorter + } + + fun tagsSorter(tagsSorter: JsFunction) = apply { + this.tagsSorter = tagsSorter + } + + fun showExtensions(showExtensions: Boolean) = apply { + this.showExtensions = showExtensions + } + + fun showCommonExtensions(showCommonExtensions: Boolean) = apply { + this.showCommonExtensions = showCommonExtensions + } + + fun useUnsafeMarkdown(useUnsafeMarkdown: Boolean) = apply { + this.useUnsafeMarkdown = useUnsafeMarkdown + } + + fun syntaxHighlight(syntaxHighlight: SyntaxHighlight) = apply { + this.syntaxHighlight = syntaxHighlight + } + + fun build() = SwaggerUiConfiguration( + disabled = disabled, + configUrl = configUrl, + spec = spec, + url = url, + urls = urls, + deepLinking = deepLinking, + displayOperationId = displayOperationId, + defaultModelsExpandDepth = defaultModelsExpandDepth, + defaultModelExpandDepth = defaultModelExpandDepth, + defaultModelRendering = defaultModelRendering, + displayRequestDuration = displayRequestDuration, + docExpansion = docExpansion, + filter = filter, + maxDisplayedTags = maxDisplayedTags, + operationsSorter = operationsSorter, + tagsSorter = tagsSorter, + showExtensions = showExtensions, + showCommonExtensions = showCommonExtensions, + useUnsafeMarkdown = useUnsafeMarkdown, + syntaxHighlight = syntaxHighlight, + oauth2RedirectUrl = oauth2RedirectUrl, + requestInterceptor = requestInterceptor, + responseInterceptor = responseInterceptor, + showMutatedRequest = showMutatedRequest, + supportedSubmitMethods = supportedSubmitMethods, + validatorUrl = validatorUrl, + withCredentials = withCredentials, + modelPropertyMacro = modelPropertyMacro, + parameterMacro = parameterMacro, + persistAuthorization = persistAuthorization, + layout = layout, + presets = presets, + plugins = plugins + ) +} diff --git a/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/SwaggerUiConfiguration.kt b/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/configuration/SwaggerUiConfiguration.kt similarity index 81% rename from src/main/kotlin/com/muliyul/dropwizard/swagger/ui/SwaggerUiConfiguration.kt rename to src/main/kotlin/com/muliyul/dropwizard/swagger/ui/configuration/SwaggerUiConfiguration.kt index 2119d9b..682d633 100644 --- a/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/SwaggerUiConfiguration.kt +++ b/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/configuration/SwaggerUiConfiguration.kt @@ -1,14 +1,19 @@ -package com.muliyul.dropwizard.swagger.ui +package com.muliyul.dropwizard.swagger.ui.configuration import com.fasterxml.jackson.annotation.* -import javax.ws.rs.* +import com.muliyul.dropwizard.swagger.ui.* +/** + * @see Swagger-UI configuration + */ @JsonInclude(value = JsonInclude.Include.NON_NULL) class SwaggerUiConfiguration( + val disabled: Boolean = false, + // Core val configUrl: String? = null, val spec: Map? = null, - url: JsOrString = """window.location.origin + '/openapi'""", + url: JsStringOrString = """window.location.origin + '/openapi'""", val urls: List? = null, // Display @@ -34,6 +39,7 @@ class SwaggerUiConfiguration( val oauth2RedirectUrl: String? = null, @field:JsonRawValue val requestInterceptor: JsFunction? = null, +// TODO: these break the UI for some reason. not supported in the meantime. // @field:JsonProperty("request.curlOptions") // @field:JsonRawValue // val curlOptions: JsOrString = JS_UNDEFINED, @@ -66,6 +72,7 @@ class SwaggerUiConfiguration( // SwaggerUIBundle.plugins.DownloadUrl // ]""" ) { + @Suppress("unused") @field:JsonProperty("dom_id") private val domId: String = "#swagger-ui" @@ -76,15 +83,9 @@ class SwaggerUiConfiguration( } // otherwise probably a path/url. wrap in quotes. else "'$url'" -} - -private const val JS_UNDEFINED = "undefined" -// JavaScript concatenated string (window.location.origin + '/something') or plain string -typealias JsOrString = String -// function() {} or () => {} -typealias JsFunction = String -// [{}, GlobalObject...] -typealias JsArrayOfObjects = String -// any dom element to attach the ui to -typealias JsElementRef = String + companion object { + @JvmStatic + fun builder() = Builder() + } +} diff --git a/src/test/java/com/muliyul/dropwizard/swagger/TestJavaApplication.java b/src/test/java/com/muliyul/dropwizard/swagger/TestJavaApplication.java new file mode 100644 index 0000000..d699156 --- /dev/null +++ b/src/test/java/com/muliyul/dropwizard/swagger/TestJavaApplication.java @@ -0,0 +1,24 @@ +package com.muliyul.dropwizard.swagger; + +import com.muliyul.dropwizard.swagger.bundles.auth.AuthBundle; +import io.dropwizard.Application; +import io.dropwizard.configuration.ResourceConfigurationSourceProvider; +import io.dropwizard.setup.Bootstrap; +import io.dropwizard.setup.Environment; + +public class TestJavaApplication extends Application { + public static void main(String[] args) throws Exception { + new TestKotlinApplication().run(args); + } + + @Override + public void initialize(Bootstrap bootstrap) { + bootstrap.setConfigurationSourceProvider(new ResourceConfigurationSourceProvider()); + bootstrap.addBundle(new SwaggerBundle<>()); + bootstrap.addBundle(new AuthBundle<>()); + } + + @Override + public void run(TestJavaConfiguration configuration, Environment environment) { + } +} diff --git a/src/test/java/com/muliyul/dropwizard/swagger/TestJavaConfiguration.java b/src/test/java/com/muliyul/dropwizard/swagger/TestJavaConfiguration.java new file mode 100644 index 0000000..a031a97 --- /dev/null +++ b/src/test/java/com/muliyul/dropwizard/swagger/TestJavaConfiguration.java @@ -0,0 +1,17 @@ +package com.muliyul.dropwizard.swagger; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.muliyul.dropwizard.swagger.ui.configuration.SwaggerUiConfiguration; +import io.dropwizard.Configuration; +import org.jetbrains.annotations.Nullable; + +public class TestJavaConfiguration extends Configuration implements SwaggerBundleConfiguration { + @JsonProperty("swagger-ui") + private SwaggerUiConfiguration swaggerUiConfiguration = null; + + @Nullable + @Override + public SwaggerUiConfiguration getSwaggerUiConfiguration() { + return swaggerUiConfiguration; + } +} diff --git a/src/test/kotlin/com/muliyul/dropwizard/swagger/TestConfiguration.kt b/src/test/kotlin/com/muliyul/dropwizard/swagger/TestConfiguration.kt deleted file mode 100644 index 0983dea..0000000 --- a/src/test/kotlin/com/muliyul/dropwizard/swagger/TestConfiguration.kt +++ /dev/null @@ -1,12 +0,0 @@ -package com.muliyul.dropwizard.swagger - -import com.fasterxml.jackson.annotation.* -import com.muliyul.dropwizard.swagger.ui.* -import io.dropwizard.* - -class TestConfiguration( - @field:JsonProperty("swagger") - override val swaggerConfiguration: io.swagger.v3.oas.integration.SwaggerConfiguration? = null, - @field:JsonProperty("swagger-ui") - override val swaggerUiConfiguration: SwaggerUiConfiguration? = null -) : Configuration(), SwaggerConfiguration diff --git a/src/test/kotlin/com/muliyul/dropwizard/swagger/TestKotlinApplication.kt b/src/test/kotlin/com/muliyul/dropwizard/swagger/TestKotlinApplication.kt new file mode 100644 index 0000000..4103c3f --- /dev/null +++ b/src/test/kotlin/com/muliyul/dropwizard/swagger/TestKotlinApplication.kt @@ -0,0 +1,24 @@ +package com.muliyul.dropwizard.swagger + +import com.muliyul.dropwizard.swagger.bundles.auth.* +import io.dropwizard.* +import io.dropwizard.configuration.* +import io.dropwizard.setup.* + +class TestKotlinApplication: Application() { + override fun initialize(bootstrap: Bootstrap) { + bootstrap.configurationSourceProvider = ResourceConfigurationSourceProvider() + bootstrap.addBundle(SwaggerBundle()) + bootstrap.addBundle(AuthBundle()) + } + + override fun run(configuration: TestKotlinConfiguration, environment: Environment) { + environment.jersey().register(TestResource()) + } + + companion object { + @JvmStatic + fun main(args: Array) = TestKotlinApplication().run(*args) + } +} + diff --git a/src/test/kotlin/com/muliyul/dropwizard/swagger/TestKotlinConfiguration.kt b/src/test/kotlin/com/muliyul/dropwizard/swagger/TestKotlinConfiguration.kt new file mode 100644 index 0000000..082c666 --- /dev/null +++ b/src/test/kotlin/com/muliyul/dropwizard/swagger/TestKotlinConfiguration.kt @@ -0,0 +1,12 @@ +package com.muliyul.dropwizard.swagger + +import com.fasterxml.jackson.annotation.* +import com.muliyul.dropwizard.swagger.ui.configuration.* +import io.dropwizard.* + +class TestKotlinConfiguration( +// @field:JsonProperty("swagger") +// override val swaggerConfiguration: SwaggerConfiguration? = null, + @field:JsonProperty("swagger-ui") + override val swaggerUiConfiguration: SwaggerUiConfiguration? = null +) : Configuration(), SwaggerBundleConfiguration diff --git a/src/test/kotlin/com/muliyul/dropwizard/swagger/TestResource.kt b/src/test/kotlin/com/muliyul/dropwizard/swagger/TestResource.kt index a6d47bd..1ef50b4 100644 --- a/src/test/kotlin/com/muliyul/dropwizard/swagger/TestResource.kt +++ b/src/test/kotlin/com/muliyul/dropwizard/swagger/TestResource.kt @@ -1,8 +1,6 @@ package com.muliyul.dropwizard.swagger import io.dropwizard.auth.* -import io.swagger.v3.oas.annotations.* -import io.swagger.v3.oas.annotations.security.* import javax.ws.rs.* import javax.ws.rs.core.* @@ -11,14 +9,13 @@ class TestResource { @GET @Path("/sanity") @Consumes(MediaType.WILDCARD) - @Produces(MediaType.TEXT_HTML) - fun sanity() = "Sanity" + @Produces(MediaType.TEXT_PLAIN) + fun sanity() = "Sanity check!" @GET @Path("/ignoring_auth") @Consumes(MediaType.WILDCARD) - @Produces(MediaType.TEXT_HTML) - @Operation(security = [SecurityRequirement(name = "petstore_auth")]) - fun ignoring_auth(@Auth user: User) = "Sanity" + @Produces(MediaType.TEXT_PLAIN) + fun ignoringAuth(@Auth user: User) = "Hello ${user.name}!" } diff --git a/src/test/kotlin/com/muliyul/dropwizard/swagger/TestSwaggerApplication.kt b/src/test/kotlin/com/muliyul/dropwizard/swagger/TestSwaggerApplication.kt deleted file mode 100644 index 8d2a45d..0000000 --- a/src/test/kotlin/com/muliyul/dropwizard/swagger/TestSwaggerApplication.kt +++ /dev/null @@ -1,22 +0,0 @@ -package com.muliyul.dropwizard.swagger - -import io.dropwizard.* -import io.dropwizard.configuration.* -import io.dropwizard.setup.* - -class TestSwaggerApplication: Application() { - override fun initialize(bootstrap: Bootstrap) { - bootstrap.configurationSourceProvider = ResourceConfigurationSourceProvider() - bootstrap.addBundle(SwaggerBundle("com.muliyul.dropwizard.swagger")) - } - - override fun run(configuration: TestConfiguration, environment: Environment) { - environment.jersey().register(TestResource()) - } - - companion object { - @JvmStatic - fun main(args: Array) = TestSwaggerApplication().run(*args) - } -} - diff --git a/src/test/kotlin/com/muliyul/dropwizard/swagger/User.kt b/src/test/kotlin/com/muliyul/dropwizard/swagger/User.kt index d410234..783345b 100644 --- a/src/test/kotlin/com/muliyul/dropwizard/swagger/User.kt +++ b/src/test/kotlin/com/muliyul/dropwizard/swagger/User.kt @@ -2,6 +2,8 @@ package com.muliyul.dropwizard.swagger import java.security.* -class User: Principal { - override fun getName(): String = toString() +class User( + private val username: String +): Principal { + override fun getName(): String = username } diff --git a/src/test/kotlin/com/muliyul/dropwizard/swagger/bundles/auth/AuthBundle.kt b/src/test/kotlin/com/muliyul/dropwizard/swagger/bundles/auth/AuthBundle.kt new file mode 100644 index 0000000..26c1872 --- /dev/null +++ b/src/test/kotlin/com/muliyul/dropwizard/swagger/bundles/auth/AuthBundle.kt @@ -0,0 +1,28 @@ +package com.muliyul.dropwizard.swagger.bundles.auth + +import com.muliyul.dropwizard.swagger.* +import io.dropwizard.* +import io.dropwizard.auth.* +import io.dropwizard.auth.basic.* +import io.dropwizard.setup.* +import io.dropwizard.auth.AuthValueFactoryProvider + + + + +class AuthBundle : ConfiguredBundle { + + override fun run(configuration: C, environment: Environment) { + environment.jersey().run { + val basicAuthFilter = BasicCredentialAuthFilter.Builder() + .setAuthenticator(BasicAuthenticator) + .setPrefix("Basic") + .buildAuthFilter() + + register(AuthDynamicFeature(basicAuthFilter)) + register(AuthValueFactoryProvider.Binder(User::class.java)) + } + } + +} + diff --git a/src/test/kotlin/com/muliyul/dropwizard/swagger/bundles/auth/BasicAuthenticator.kt b/src/test/kotlin/com/muliyul/dropwizard/swagger/bundles/auth/BasicAuthenticator.kt new file mode 100644 index 0000000..c3a66e2 --- /dev/null +++ b/src/test/kotlin/com/muliyul/dropwizard/swagger/bundles/auth/BasicAuthenticator.kt @@ -0,0 +1,13 @@ +package com.muliyul.dropwizard.swagger.bundles.auth + +import com.muliyul.dropwizard.swagger.* +import io.dropwizard.auth.* +import io.dropwizard.auth.basic.* +import java.util.* + +object BasicAuthenticator : Authenticator { + override fun authenticate(credentials: BasicCredentials): Optional = + if ("secret" == credentials.password) { + Optional.of(User(credentials.username)) + } else Optional.empty() +} diff --git a/src/test/kotlin/com/muliyul/dropwizard/swagger/ext/Selenium.kt b/src/test/kotlin/com/muliyul/dropwizard/swagger/ext/Selenium.kt index 190779f..849feb3 100644 --- a/src/test/kotlin/com/muliyul/dropwizard/swagger/ext/Selenium.kt +++ b/src/test/kotlin/com/muliyul/dropwizard/swagger/ext/Selenium.kt @@ -5,7 +5,7 @@ import org.openqa.selenium.support.ui.* fun WebDriver.wait(timeoutInSeconds: Long) = WebDriverWait(this, timeoutInSeconds) -fun WebDriverWait.untilError(block: (WebDriver) -> T) = try { +fun WebDriverWait.untilError(block: (WebDriver) -> T): T = try { until { block(it) } } catch (e: Throwable) { throw Error(e) diff --git a/src/test/kotlin/com/muliyul/dropwizard/swagger/integration/SwaggerIntegrationTest.kt b/src/test/kotlin/com/muliyul/dropwizard/swagger/integration/SwaggerIntegrationTest.kt index 9878464..7884364 100644 --- a/src/test/kotlin/com/muliyul/dropwizard/swagger/integration/SwaggerIntegrationTest.kt +++ b/src/test/kotlin/com/muliyul/dropwizard/swagger/integration/SwaggerIntegrationTest.kt @@ -4,26 +4,25 @@ import com.muliyul.dropwizard.swagger.* import io.dropwizard.testing.* import io.dropwizard.testing.junit5.* import io.github.bonigarcia.seljup.* -import org.glassfish.jersey.client.proxy.* import org.junit.jupiter.api.* import org.junit.jupiter.api.extension.* import org.junit.jupiter.api.extension.Extensions import org.openqa.selenium.chrome.* -import kotlin.test.* import kotlin.test.Test @Extensions( ExtendWith(DropwizardExtensionsSupport::class), ExtendWith(SeleniumJupiter::class) ) -@SingleSession +@Disabled class SwaggerIntegrationTest( - @DockerBrowser(type = BrowserType.CHROME) private val webDriver: ChromeDriver + @DockerBrowser(type = BrowserType.CHROME) + private val webDriver: ChromeDriver ) { companion object { @JvmStatic private val ext = DropwizardAppExtension( - TestSwaggerApplication::class.java, + TestKotlinApplication::class.java, "config-test.yaml", ConfigOverride.randomPorts() ) @@ -38,7 +37,5 @@ class SwaggerIntegrationTest( fun `should expose default endpoint`() { SwaggerUiPage(webDriver) .expandSanity() - - println() } } diff --git a/src/test/resources/openapi.yaml b/src/test/resources/openapi.yaml index 3f0fd30..61d873f 100644 --- a/src/test/resources/openapi.yaml +++ b/src/test/resources/openapi.yaml @@ -1,30 +1,802 @@ -prettyPrint: true -cacheTTL: 0 -openAPI: - info: - version: '1.0' - title: Swagger Pet Sample App Config File - description: 'This is a sample server Petstore server. You can find out more - about Swagger at [http://swagger.io](http://swagger.io) or on [irc.freenode.net, - #swagger](http://swagger.io/irc/). For this sample, you can use the api key - `special-key` to test the authorization filters.' - termsOfService: http://swagger.io/terms/ - contact: - email: apiteam@swagger.io - license: - name: Apache 2.0 - url: http://www.apache.org/licenses/LICENSE-2.0.html - components: - securitySchemes: - petstore_auth: - type: oauth2 - flows: - implicit: - authorizationUrl: http://petstore.swagger.io/oauth/dialog - scopes: - write:pets: modify pets in your account - read:pets: read your pets - api_key: - type: apiKey - name: api_key - in: header +openapi: 3.0.1 +info: + title: Swagger Petstore - OpenAPI 3.0 + description: "This is a sample Pet Store Server based on the OpenAPI 3.0 specification.\ + \ You can find out more about\nSwagger at [http://swagger.io](http://swagger.io).\ + \ In the third iteration of the pet store, we've switched to the design first\ + \ approach!\nYou can now help us improve the API whether it's by making changes\ + \ to the definition itself or to the code.\nThat way, with time, we can improve\ + \ the API in general, and expose some of the new features in OAS3.\n\nSome useful\ + \ links:\n- [The Pet Store repository](https://github.com/swagger-api/swagger-petstore)\n\ + - [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml)" + termsOfService: http://swagger.io/terms/ + contact: + email: apiteam@swagger.io + license: + name: Apache 2.0 + url: http://www.apache.org/licenses/LICENSE-2.0.html + version: 1.0.5 +externalDocs: + description: Find out more about Swagger + url: http://swagger.io +servers: + - url: /api/v3 +tags: + - name: pet + description: Everything about your Pets + externalDocs: + description: Find out more + url: http://swagger.io + - name: store + description: Operations about user + - name: user + description: Access to Petstore orders + externalDocs: + description: Find out more about our store + url: http://swagger.io +paths: + /pet: + put: + tags: + - pet + summary: Update an existing pet + description: Update an existing pet by Id + operationId: updatePet + requestBody: + description: Update an existent pet in the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Pet' + required: true + responses: + "200": + description: Successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid ID supplied + "404": + description: Pet not found + "405": + description: Validation exception + security: + - petstore_auth: + - write:pets + - read:pets + post: + tags: + - pet + summary: Add a new pet to the store + description: Add a new pet to the store + operationId: addPet + requestBody: + description: Create a new pet in the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Pet' + required: true + responses: + "200": + description: Successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + "405": + description: Invalid input + security: + - petstore_auth: + - write:pets + - read:pets + /pet/findByStatus: + get: + tags: + - pet + summary: Finds Pets by status + description: Multiple status values can be provided with comma separated strings + operationId: findPetsByStatus + parameters: + - name: status + in: query + description: Status values that need to be considered for filter + required: false + explode: true + schema: + type: string + default: available + enum: + - available + - pending + - sold + responses: + "200": + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid status value + security: + - petstore_auth: + - write:pets + - read:pets + /pet/findByTags: + get: + tags: + - pet + summary: Finds Pets by tags + description: "Multiple tags can be provided with comma separated strings. Use\ + \ tag1, tag2, tag3 for testing." + operationId: findPetsByTags + parameters: + - name: tags + in: query + description: Tags to filter by + required: false + explode: true + schema: + type: array + items: + type: string + responses: + "200": + description: successful operation + content: + application/xml: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid tag value + security: + - petstore_auth: + - write:pets + - read:pets + /pet/{petId}: + get: + tags: + - pet + summary: Find pet by ID + description: Returns a single pet + operationId: getPetById + parameters: + - name: petId + in: path + description: ID of pet to return + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Pet' + application/json: + schema: + $ref: '#/components/schemas/Pet' + "400": + description: Invalid ID supplied + "404": + description: Pet not found + security: + - api_key: [] + - petstore_auth: + - write:pets + - read:pets + post: + tags: + - pet + summary: Updates a pet in the store with form data + description: "" + operationId: updatePetWithForm + parameters: + - name: petId + in: path + description: ID of pet that needs to be updated + required: true + schema: + type: integer + format: int64 + - name: name + in: query + description: Name of pet that needs to be updated + schema: + type: string + - name: status + in: query + description: Status of pet that needs to be updated + schema: + type: string + responses: + "405": + description: Invalid input + security: + - petstore_auth: + - write:pets + - read:pets + delete: + tags: + - pet + summary: Deletes a pet + description: "" + operationId: deletePet + parameters: + - name: api_key + in: header + description: "" + required: false + schema: + type: string + - name: petId + in: path + description: Pet id to delete + required: true + schema: + type: integer + format: int64 + responses: + "400": + description: Invalid pet value + security: + - petstore_auth: + - write:pets + - read:pets + /pet/{petId}/uploadImage: + post: + tags: + - pet + summary: uploads an image + description: "" + operationId: uploadFile + parameters: + - name: petId + in: path + description: ID of pet to update + required: true + schema: + type: integer + format: int64 + - name: additionalMetadata + in: query + description: Additional Metadata + required: false + schema: + type: string + requestBody: + content: + application/octet-stream: + schema: + type: string + format: binary + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/ApiResponse' + security: + - petstore_auth: + - write:pets + - read:pets + /store/inventory: + get: + tags: + - store + summary: Returns pet inventories by status + description: Returns a map of status codes to quantities + operationId: getInventory + responses: + "200": + description: successful operation + content: + application/json: + schema: + type: object + additionalProperties: + type: integer + format: int32 + security: + - api_key: [] + /store/order: + post: + tags: + - store + summary: Place an order for a pet + description: Place a new order in the store + operationId: placeOrder + requestBody: + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/Order' + responses: + "200": + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/Order' + "405": + description: Invalid input + /store/order/{orderId}: + get: + tags: + - store + summary: Find purchase order by ID + description: For valid response try integer IDs with value <= 5 or > 10. Other + values will generated exceptions + operationId: getOrderById + parameters: + - name: orderId + in: path + description: ID of order that needs to be fetched + required: true + schema: + type: integer + format: int64 + responses: + "200": + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/Order' + application/json: + schema: + $ref: '#/components/schemas/Order' + "400": + description: Invalid ID supplied + "404": + description: Order not found + delete: + tags: + - store + summary: Delete purchase order by ID + description: For valid response try integer IDs with value < 1000. Anything + above 1000 or nonintegers will generate API errors + operationId: deleteOrder + parameters: + - name: orderId + in: path + description: ID of the order that needs to be deleted + required: true + schema: + type: integer + format: int64 + responses: + "400": + description: Invalid ID supplied + "404": + description: Order not found + /user: + post: + tags: + - user + summary: Create user + description: This can only be done by the logged in user. + operationId: createUser + requestBody: + description: Created user object + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/User' + responses: + default: + description: successful operation + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + /user/createWithList: + post: + tags: + - user + summary: Creates list of users with given input array + description: Creates list of users with given input array + operationId: createUsersWithListInput + requestBody: + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + responses: + "200": + description: Successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/User' + application/json: + schema: + $ref: '#/components/schemas/User' + default: + description: successful operation + /user/login: + get: + tags: + - user + summary: Logs user into the system + description: "" + operationId: loginUser + parameters: + - name: username + in: query + description: The user name for login + required: false + schema: + type: string + - name: password + in: query + description: The password for login in clear text + required: false + schema: + type: string + responses: + "200": + description: successful operation + headers: + X-Rate-Limit: + description: calls per hour allowed by the user + schema: + type: integer + format: int32 + X-Expires-After: + description: date in UTC when toekn expires + schema: + type: string + format: date-time + content: + application/xml: + schema: + type: string + application/json: + schema: + type: string + "400": + description: Invalid username/password supplied + /user/logout: + get: + tags: + - user + summary: Logs out current logged in user session + description: "" + operationId: logoutUser + parameters: [] + responses: + default: + description: successful operation + /user/{username}: + get: + tags: + - user + summary: Get user by user name + description: "" + operationId: getUserByName + parameters: + - name: username + in: path + description: 'The name that needs to be fetched. Use user1 for testing. ' + required: true + schema: + type: string + responses: + "200": + description: successful operation + content: + application/xml: + schema: + $ref: '#/components/schemas/User' + application/json: + schema: + $ref: '#/components/schemas/User' + "400": + description: Invalid username supplied + "404": + description: User not found + put: + tags: + - user + summary: Update user + description: This can only be done by the logged in user. + operationId: updateUser + parameters: + - name: username + in: path + description: name that need to be deleted + required: true + schema: + type: string + requestBody: + description: Update an existent user in the store + content: + application/json: + schema: + $ref: '#/components/schemas/User' + application/xml: + schema: + $ref: '#/components/schemas/User' + application/x-www-form-urlencoded: + schema: + $ref: '#/components/schemas/User' + responses: + default: + description: successful operation + delete: + tags: + - user + summary: Delete user + description: This can only be done by the logged in user. + operationId: deleteUser + parameters: + - name: username + in: path + description: The name that needs to be deleted + required: true + schema: + type: string + responses: + "400": + description: Invalid username supplied + "404": + description: User not found +components: + schemas: + Order: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + petId: + type: integer + format: int64 + example: 198772 + quantity: + type: integer + format: int32 + example: 7 + shipDate: + type: string + format: date-time + status: + type: string + description: Order Status + example: approved + enum: + - placed + - approved + - delivered + complete: + type: boolean + xml: + name: order + Customer: + type: object + properties: + id: + type: integer + format: int64 + example: 100000 + username: + type: string + example: fehguy + address: + type: array + xml: + name: addresses + wrapped: true + items: + $ref: '#/components/schemas/Address' + xml: + name: customer + Address: + type: object + properties: + street: + type: string + example: 437 Lytton + city: + type: string + example: Palo Alto + state: + type: string + example: CA + zip: + type: string + example: "94301" + xml: + name: address + Category: + type: object + properties: + id: + type: integer + format: int64 + example: 1 + name: + type: string + example: Dogs + xml: + name: category + User: + type: object + properties: + id: + type: integer + format: int64 + example: 10 + username: + type: string + example: theUser + firstName: + type: string + example: John + lastName: + type: string + example: James + email: + type: string + example: john@email.com + password: + type: string + example: "12345" + phone: + type: string + example: "12345" + userStatus: + type: integer + description: User Status + format: int32 + example: 1 + xml: + name: user + Tag: + type: object + properties: + id: + type: integer + format: int64 + name: + type: string + xml: + name: tag + Pet: + required: + - name + - photoUrls + type: object + properties: + id: + type: integer + format: int64 + example: 10 + name: + type: string + example: doggie + category: + $ref: '#/components/schemas/Category' + photoUrls: + type: array + xml: + wrapped: true + items: + type: string + xml: + name: photoUrl + tags: + type: array + xml: + wrapped: true + items: + $ref: '#/components/schemas/Tag' + status: + type: string + description: pet status in the store + enum: + - available + - pending + - sold + xml: + name: pet + ApiResponse: + type: object + properties: + code: + type: integer + format: int32 + type: + type: string + message: + type: string + xml: + name: '##default' + requestBodies: + Pet: + description: Pet object that needs to be added to the store + content: + application/json: + schema: + $ref: '#/components/schemas/Pet' + application/xml: + schema: + $ref: '#/components/schemas/Pet' + UserArray: + description: List of user object + content: + application/json: + schema: + type: array + items: + $ref: '#/components/schemas/User' + securitySchemes: + petstore_auth: + type: oauth2 + flows: + implicit: + authorizationUrl: https://petstore3.swagger.io/oauth/authorize + scopes: + write:pets: modify pets in your account + read:pets: read your pets + api_key: + type: apiKey + name: api_key + in: header From 5ea15a1ee2befad54f7c68395cbc8dea1b6c0d85 Mon Sep 17 00:00:00 2001 From: Muli Yulzary Date: Tue, 16 Feb 2021 05:18:58 +0200 Subject: [PATCH 17/18] format --- .../muliyul/dropwizard/swagger/ui/configuration/Builder.kt | 2 +- .../com/muliyul/dropwizard/swagger/TestKotlinApplication.kt | 2 +- src/test/kotlin/com/muliyul/dropwizard/swagger/User.kt | 2 +- .../muliyul/dropwizard/swagger/bundles/auth/AuthBundle.kt | 5 +---- .../muliyul/dropwizard/swagger/integration/SwaggerUiPage.kt | 4 +--- 5 files changed, 5 insertions(+), 10 deletions(-) diff --git a/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/configuration/Builder.kt b/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/configuration/Builder.kt index 49ff6d1..9fd8c24 100644 --- a/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/configuration/Builder.kt +++ b/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/configuration/Builder.kt @@ -31,7 +31,7 @@ class Builder { private var oauth2RedirectUrl: String? = null private var requestInterceptor: JsFunction? = null -// TODO: these break the UI for some reason. not supported in the meantime. + // TODO: these break the UI for some reason. not supported in the meantime. // @field:JsonProperty("request.curlOptions") // @field:JsonRawValue // var curlOptions: JsOrString = JS_UNDEFINED, diff --git a/src/test/kotlin/com/muliyul/dropwizard/swagger/TestKotlinApplication.kt b/src/test/kotlin/com/muliyul/dropwizard/swagger/TestKotlinApplication.kt index 4103c3f..7b101ab 100644 --- a/src/test/kotlin/com/muliyul/dropwizard/swagger/TestKotlinApplication.kt +++ b/src/test/kotlin/com/muliyul/dropwizard/swagger/TestKotlinApplication.kt @@ -5,7 +5,7 @@ import io.dropwizard.* import io.dropwizard.configuration.* import io.dropwizard.setup.* -class TestKotlinApplication: Application() { +class TestKotlinApplication : Application() { override fun initialize(bootstrap: Bootstrap) { bootstrap.configurationSourceProvider = ResourceConfigurationSourceProvider() bootstrap.addBundle(SwaggerBundle()) diff --git a/src/test/kotlin/com/muliyul/dropwizard/swagger/User.kt b/src/test/kotlin/com/muliyul/dropwizard/swagger/User.kt index 783345b..a5907c3 100644 --- a/src/test/kotlin/com/muliyul/dropwizard/swagger/User.kt +++ b/src/test/kotlin/com/muliyul/dropwizard/swagger/User.kt @@ -4,6 +4,6 @@ import java.security.* class User( private val username: String -): Principal { +) : Principal { override fun getName(): String = username } diff --git a/src/test/kotlin/com/muliyul/dropwizard/swagger/bundles/auth/AuthBundle.kt b/src/test/kotlin/com/muliyul/dropwizard/swagger/bundles/auth/AuthBundle.kt index 26c1872..a30868c 100644 --- a/src/test/kotlin/com/muliyul/dropwizard/swagger/bundles/auth/AuthBundle.kt +++ b/src/test/kotlin/com/muliyul/dropwizard/swagger/bundles/auth/AuthBundle.kt @@ -5,12 +5,9 @@ import io.dropwizard.* import io.dropwizard.auth.* import io.dropwizard.auth.basic.* import io.dropwizard.setup.* -import io.dropwizard.auth.AuthValueFactoryProvider - - -class AuthBundle : ConfiguredBundle { +class AuthBundle : ConfiguredBundle { override fun run(configuration: C, environment: Environment) { environment.jersey().run { diff --git a/src/test/kotlin/com/muliyul/dropwizard/swagger/integration/SwaggerUiPage.kt b/src/test/kotlin/com/muliyul/dropwizard/swagger/integration/SwaggerUiPage.kt index 58839f5..74adb91 100644 --- a/src/test/kotlin/com/muliyul/dropwizard/swagger/integration/SwaggerUiPage.kt +++ b/src/test/kotlin/com/muliyul/dropwizard/swagger/integration/SwaggerUiPage.kt @@ -1,13 +1,11 @@ package com.muliyul.dropwizard.swagger.integration import com.muliyul.dropwizard.swagger.ext.* +import org.openqa.selenium.* import org.openqa.selenium.remote.* import org.openqa.selenium.support.* import org.openqa.selenium.support.ui.* -import org.openqa.selenium.support.FindBy -import org.openqa.selenium.WebElement - class SwaggerUiPage( private val webDriver: RemoteWebDriver From 232664fc50ad3c601265716f16599167cb82a53e Mon Sep 17 00:00:00 2001 From: Muli Yulzary Date: Fri, 2 Apr 2021 02:56:21 +0300 Subject: [PATCH 18/18] - Add openapi resources - Open up DropwizardCompatibleScanner for inheritance - Tidy up some places --- .../swagger/DropwizardCompatibleReader.kt | 5 +- .../swagger/DropwizardCompatibleScanner.kt | 2 +- .../dropwizard/swagger/SwaggerBundle.kt | 28 +- .../DropwizardAcceptHeaderOpenApiResource.kt | 38 + .../resource/DropwizardOpenApiResource.kt | 25 + .../swagger/ui/SwaggerUiResource.kt | 5 +- .../dropwizard/swagger/TestResource.kt | 6 +- .../bundles/auth/AbstractAuthFilter.kt | 31 + .../auth/ApiKeyCredentialAuthFilter.kt | 15 + .../swagger/bundles/auth/AuthBundle.kt | 46 +- .../bundles/auth/BasicAuthenticator.kt | 13 - .../bundles/auth/CredentialsProvider.kt | 7 + .../bundles/auth/GenericAuthFilterBuilder.kt | 7 + .../dropwizard/swagger/ext/Optional.kt | 5 + src/test/resources/openapi.yaml | 839 +----------------- 15 files changed, 240 insertions(+), 832 deletions(-) create mode 100644 src/main/kotlin/com/muliyul/dropwizard/swagger/resource/DropwizardAcceptHeaderOpenApiResource.kt create mode 100644 src/main/kotlin/com/muliyul/dropwizard/swagger/resource/DropwizardOpenApiResource.kt create mode 100644 src/test/kotlin/com/muliyul/dropwizard/swagger/bundles/auth/AbstractAuthFilter.kt create mode 100644 src/test/kotlin/com/muliyul/dropwizard/swagger/bundles/auth/ApiKeyCredentialAuthFilter.kt delete mode 100644 src/test/kotlin/com/muliyul/dropwizard/swagger/bundles/auth/BasicAuthenticator.kt create mode 100644 src/test/kotlin/com/muliyul/dropwizard/swagger/bundles/auth/CredentialsProvider.kt create mode 100644 src/test/kotlin/com/muliyul/dropwizard/swagger/bundles/auth/GenericAuthFilterBuilder.kt create mode 100644 src/test/kotlin/com/muliyul/dropwizard/swagger/ext/Optional.kt diff --git a/src/main/kotlin/com/muliyul/dropwizard/swagger/DropwizardCompatibleReader.kt b/src/main/kotlin/com/muliyul/dropwizard/swagger/DropwizardCompatibleReader.kt index e42cf01..890f19a 100644 --- a/src/main/kotlin/com/muliyul/dropwizard/swagger/DropwizardCompatibleReader.kt +++ b/src/main/kotlin/com/muliyul/dropwizard/swagger/DropwizardCompatibleReader.kt @@ -4,6 +4,7 @@ import com.fasterxml.jackson.annotation.* import io.dropwizard.auth.* import io.swagger.v3.jaxrs2.* import io.swagger.v3.oas.models.* +import io.swagger.v3.oas.models.security.* import java.lang.reflect.* import javax.ws.rs.* @@ -11,6 +12,7 @@ import javax.ws.rs.* * This class instructs Swagger to ignore [Auth] */ open class DropwizardCompatibleReader : Reader() { + override fun getParameters( type: Type?, annotations: MutableList?, @@ -29,8 +31,9 @@ open class DropwizardCompatibleReader : Reader() { methodConsumes, jsonViewAnnotation ).apply { - // instructs Swagger to ignore Auth + // instructs Swagger to ignore @Auth as body param if (isAuthParam) requestBody = null } } + } diff --git a/src/main/kotlin/com/muliyul/dropwizard/swagger/DropwizardCompatibleScanner.kt b/src/main/kotlin/com/muliyul/dropwizard/swagger/DropwizardCompatibleScanner.kt index aa5e2e2..83ae32a 100644 --- a/src/main/kotlin/com/muliyul/dropwizard/swagger/DropwizardCompatibleScanner.kt +++ b/src/main/kotlin/com/muliyul/dropwizard/swagger/DropwizardCompatibleScanner.kt @@ -7,7 +7,7 @@ private val ignoredPackages = setOf("com.papertrail.profiler", "org.glassfish.je private val delegate = JaxrsApplicationAndAnnotationScanner() -class DropwizardCompatibleScanner : OpenApiScanner by delegate { +open class DropwizardCompatibleScanner : OpenApiScanner by delegate { override fun classes(): MutableSet> = delegate.classes() .filter { c -> ignoredPackages.none { p -> c.name.startsWith(p, ignoreCase = true) } } diff --git a/src/main/kotlin/com/muliyul/dropwizard/swagger/SwaggerBundle.kt b/src/main/kotlin/com/muliyul/dropwizard/swagger/SwaggerBundle.kt index c9f95c4..0a564bd 100644 --- a/src/main/kotlin/com/muliyul/dropwizard/swagger/SwaggerBundle.kt +++ b/src/main/kotlin/com/muliyul/dropwizard/swagger/SwaggerBundle.kt @@ -3,14 +3,15 @@ package com.muliyul.dropwizard.swagger import com.muliyul.dropwizard.swagger.ui.* import com.muliyul.dropwizard.swagger.ui.configuration.* import io.dropwizard.* +import io.dropwizard.Configuration import io.dropwizard.assets.* import io.dropwizard.setup.* import io.swagger.v3.jaxrs2.integration.* -import io.swagger.v3.jaxrs2.integration.resources.* +import io.swagger.v3.oas.integration.* +import com.muliyul.dropwizard.swagger.resource.* class SwaggerBundle @JvmOverloads constructor( - private vararg val resourcesPackageNames: String, private val swaggerUiConfiguration: SwaggerUiConfiguration = SwaggerUiConfiguration() ) : ConfiguredBundle where C : Configuration, C : SwaggerBundleConfiguration { @@ -21,11 +22,9 @@ class SwaggerBundle @JvmOverloads constructor( override fun run(configuration: C, environment: Environment) { val swaggerUiConfiguration = configuration.swaggerUiConfiguration ?: swaggerUiConfiguration - val resourcesPackageNames = resourcesPackageNames.toSet() - - JaxrsOpenApiContextBuilder>() - .application(environment.jersey().resourceConfig) - .resourcePackages(resourcesPackageNames) + val resourceConfig = environment.jersey().resourceConfig + val ctx = JaxrsOpenApiContextBuilder>() + .application(resourceConfig) .buildContext(false) .apply { setOpenApiScanner(DropwizardCompatibleScanner()) @@ -33,9 +32,20 @@ class SwaggerBundle @JvmOverloads constructor( } .init() + val resources = listOf( + DropwizardAcceptHeaderOpenApiResource(), + DropwizardOpenApiResource() + ) + environment.jersey().apply { - register(AcceptHeaderOpenApiResource().resourcePackages(resourcesPackageNames)) - register(OpenApiResource().resourcePackages(resourcesPackageNames)) + resources.forEach { resource -> + val swaggerConfiguration = ctx.openApiConfiguration as SwaggerConfiguration + resource.openApiConfiguration = swaggerConfiguration.apply { + scannerClass = scannerClass ?: DropwizardCompatibleScanner::class.qualifiedName + readerClass = readerClass ?: DropwizardCompatibleReader::class.qualifiedName + } + register(resource) + } if (!swaggerUiConfiguration.disabled) { register(SwaggerResource(environment.objectMapper, swaggerUiConfiguration)) } diff --git a/src/main/kotlin/com/muliyul/dropwizard/swagger/resource/DropwizardAcceptHeaderOpenApiResource.kt b/src/main/kotlin/com/muliyul/dropwizard/swagger/resource/DropwizardAcceptHeaderOpenApiResource.kt new file mode 100644 index 0000000..0d241e7 --- /dev/null +++ b/src/main/kotlin/com/muliyul/dropwizard/swagger/resource/DropwizardAcceptHeaderOpenApiResource.kt @@ -0,0 +1,38 @@ +package com.muliyul.dropwizard.swagger.resource + +import io.swagger.v3.jaxrs2.integration.resources.* +import io.swagger.v3.oas.annotations.* +import javax.servlet.* +import javax.ws.rs.* +import javax.ws.rs.core.* + +@Path("/openapi") +class DropwizardAcceptHeaderOpenApiResource : BaseOpenApiResource() { + + @GET + @Consumes(MediaType.WILDCARD) + @Produces(MediaType.APPLICATION_JSON) + @Operation(hidden = true) + fun getOpenApiJson( + @Context headers: HttpHeaders, + @Context uriInfo: UriInfo, + @Context config: ServletConfig, + @Context app: Application + ): Response { + return super.getOpenApi(headers, config, app, uriInfo, "json") + } + + @GET + @Consumes(MediaType.WILDCARD) + @Produces("application/yaml") + @Operation(hidden = true) + fun getOpenApiYaml( + @Context headers: HttpHeaders, + @Context uriInfo: UriInfo, + @Context config: ServletConfig, + @Context app: Application + ): Response { + return super.getOpenApi(headers, config, app, uriInfo, "yaml") + } + +} diff --git a/src/main/kotlin/com/muliyul/dropwizard/swagger/resource/DropwizardOpenApiResource.kt b/src/main/kotlin/com/muliyul/dropwizard/swagger/resource/DropwizardOpenApiResource.kt new file mode 100644 index 0000000..bcc6efa --- /dev/null +++ b/src/main/kotlin/com/muliyul/dropwizard/swagger/resource/DropwizardOpenApiResource.kt @@ -0,0 +1,25 @@ +package com.muliyul.dropwizard.swagger.resource + +import io.swagger.v3.jaxrs2.integration.resources.* +import io.swagger.v3.oas.annotations.* +import javax.servlet.* +import javax.ws.rs.* +import javax.ws.rs.core.* + +@Path("/openapi.{type:json|yaml}") +class DropwizardOpenApiResource : BaseOpenApiResource() { + + @GET + @Produces(MediaType.APPLICATION_JSON, "application/yaml") + @Operation(hidden = true) + fun getOpenApi( + @Context headers: HttpHeaders, + @Context uriInfo: UriInfo, + @PathParam("type") type: String, + @Context config: ServletConfig, + @Context app: Application + ): Response { + return super.getOpenApi(headers, config, app, uriInfo, type) + } + +} diff --git a/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/SwaggerUiResource.kt b/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/SwaggerUiResource.kt index 6f0f7b1..e4fea5b 100644 --- a/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/SwaggerUiResource.kt +++ b/src/main/kotlin/com/muliyul/dropwizard/swagger/ui/SwaggerUiResource.kt @@ -11,6 +11,7 @@ class SwaggerResource( private val mapper: ObjectMapper, private val swaggerUiConfiguration: SwaggerUiConfiguration ) { + @get:GET @get:Consumes(MediaType.WILDCARD) @get:Produces(MediaType.TEXT_HTML) @@ -28,6 +29,8 @@ class SwaggerResource( val (before, after) = swaggerUiConfigurationRegex.split(originalIndex, 2) val prettyPrinter = mapper.writer().withDefaultPrettyPrinter() - "${before}\r\nvar ui = SwaggerUIBundle(${prettyPrinter.writeValueAsString(swaggerUiConfiguration)});\r\n$after" + val configurationJson = prettyPrinter.writeValueAsString(swaggerUiConfiguration) + "${before}\r\nvar ui = SwaggerUIBundle($configurationJson);\r\n$after" } + } diff --git a/src/test/kotlin/com/muliyul/dropwizard/swagger/TestResource.kt b/src/test/kotlin/com/muliyul/dropwizard/swagger/TestResource.kt index 1ef50b4..2596424 100644 --- a/src/test/kotlin/com/muliyul/dropwizard/swagger/TestResource.kt +++ b/src/test/kotlin/com/muliyul/dropwizard/swagger/TestResource.kt @@ -1,6 +1,7 @@ package com.muliyul.dropwizard.swagger import io.dropwizard.auth.* +import io.swagger.v3.oas.annotations.security.* import javax.ws.rs.* import javax.ws.rs.core.* @@ -13,9 +14,10 @@ class TestResource { fun sanity() = "Sanity check!" @GET - @Path("/ignoring_auth") + @Path("/greeting") @Consumes(MediaType.WILDCARD) @Produces(MediaType.TEXT_PLAIN) - fun ignoringAuth(@Auth user: User) = "Hello ${user.name}!" + @SecurityRequirement(name = "api_key") + fun greeting(@Auth user: User) = "Hello ${user.name}!" } diff --git a/src/test/kotlin/com/muliyul/dropwizard/swagger/bundles/auth/AbstractAuthFilter.kt b/src/test/kotlin/com/muliyul/dropwizard/swagger/bundles/auth/AbstractAuthFilter.kt new file mode 100644 index 0000000..dc707bf --- /dev/null +++ b/src/test/kotlin/com/muliyul/dropwizard/swagger/bundles/auth/AbstractAuthFilter.kt @@ -0,0 +1,31 @@ +package com.muliyul.dropwizard.swagger.bundles.auth + +import io.dropwizard.auth.* +import org.slf4j.* +import java.security.* +import javax.ws.rs.* +import javax.ws.rs.container.* + +@Suppress("unused") +abstract class AbstractAuthFilter( + private val scheme: String +) : AuthFilter(), CredentialsProvider { + + private val logger = LoggerFactory.getLogger(AbstractAuthFilter::class.java) + + protected fun throwUnauthorizedException(): Nothing = + throw WebApplicationException(unauthorizedHandler.buildResponse(prefix, realm)) + + override fun filter(requestContext: ContainerRequestContext) { + val credentials = try { + retrieveCredentials(requestContext) + } catch (e: Throwable) { + logger.error("Exception thrown while trying to retrieve credentials!", e) + throwUnauthorizedException() + } + if (!authenticate(requestContext, credentials, scheme)) { + throwUnauthorizedException() + } + } + +} diff --git a/src/test/kotlin/com/muliyul/dropwizard/swagger/bundles/auth/ApiKeyCredentialAuthFilter.kt b/src/test/kotlin/com/muliyul/dropwizard/swagger/bundles/auth/ApiKeyCredentialAuthFilter.kt new file mode 100644 index 0000000..d472b20 --- /dev/null +++ b/src/test/kotlin/com/muliyul/dropwizard/swagger/bundles/auth/ApiKeyCredentialAuthFilter.kt @@ -0,0 +1,15 @@ +package com.muliyul.dropwizard.swagger.bundles.auth + +import com.muliyul.dropwizard.swagger.* +import javax.ws.rs.container.* + +class ApiKeyCredentialAuthFilter : AbstractAuthFilter("apiKey") { + + override fun retrieveCredentials(requestContext: ContainerRequestContext): String = + requestContext.getHeaderString("api_key") ?: throwUnauthorizedException() + + class Builder : GenericAuthFilterBuilder() { + override fun newInstance(): ApiKeyCredentialAuthFilter = ApiKeyCredentialAuthFilter() + } + +} diff --git a/src/test/kotlin/com/muliyul/dropwizard/swagger/bundles/auth/AuthBundle.kt b/src/test/kotlin/com/muliyul/dropwizard/swagger/bundles/auth/AuthBundle.kt index a30868c..25c6602 100644 --- a/src/test/kotlin/com/muliyul/dropwizard/swagger/bundles/auth/AuthBundle.kt +++ b/src/test/kotlin/com/muliyul/dropwizard/swagger/bundles/auth/AuthBundle.kt @@ -1,10 +1,14 @@ package com.muliyul.dropwizard.swagger.bundles.auth import com.muliyul.dropwizard.swagger.* +import com.muliyul.dropwizard.swagger.ext.* import io.dropwizard.* import io.dropwizard.auth.* import io.dropwizard.auth.basic.* +import io.dropwizard.auth.chained.* +import io.dropwizard.auth.oauth.* import io.dropwizard.setup.* +import org.glassfish.jersey.server.filter.* class AuthBundle : ConfiguredBundle { @@ -12,14 +16,50 @@ class AuthBundle : ConfiguredBundle { override fun run(configuration: C, environment: Environment) { environment.jersey().run { val basicAuthFilter = BasicCredentialAuthFilter.Builder() - .setAuthenticator(BasicAuthenticator) + .setAuthenticator { + when { + it.password.contains("secret") -> User("basic-user-${it.username}") + else -> null + }.toOptional() + } .setPrefix("Basic") .buildAuthFilter() - register(AuthDynamicFeature(basicAuthFilter)) + val oauthAuthFilter = OAuthCredentialAuthFilter.Builder() + .setAuthenticator { + when { + it.contains("secret") -> User("oauth-user-$it") + else -> null + }.toOptional() + } + .setPrefix("Bearer") + .buildAuthFilter() + + val apiKeyAuthFilter = ApiKeyCredentialAuthFilter.Builder() + .setAuthenticator { + when { + it.contains("secret") -> User("api_key-user-$it") + else -> null + }.toOptional() + } + .setPrefix("Api") + .buildAuthFilter() + + + register(RolesAllowedDynamicFeature::class.java) + register( + AuthDynamicFeature( + ChainedAuthFilter( + listOf( + apiKeyAuthFilter, + basicAuthFilter, + oauthAuthFilter + ) + ) + ) + ) register(AuthValueFactoryProvider.Binder(User::class.java)) } } } - diff --git a/src/test/kotlin/com/muliyul/dropwizard/swagger/bundles/auth/BasicAuthenticator.kt b/src/test/kotlin/com/muliyul/dropwizard/swagger/bundles/auth/BasicAuthenticator.kt deleted file mode 100644 index c3a66e2..0000000 --- a/src/test/kotlin/com/muliyul/dropwizard/swagger/bundles/auth/BasicAuthenticator.kt +++ /dev/null @@ -1,13 +0,0 @@ -package com.muliyul.dropwizard.swagger.bundles.auth - -import com.muliyul.dropwizard.swagger.* -import io.dropwizard.auth.* -import io.dropwizard.auth.basic.* -import java.util.* - -object BasicAuthenticator : Authenticator { - override fun authenticate(credentials: BasicCredentials): Optional = - if ("secret" == credentials.password) { - Optional.of(User(credentials.username)) - } else Optional.empty() -} diff --git a/src/test/kotlin/com/muliyul/dropwizard/swagger/bundles/auth/CredentialsProvider.kt b/src/test/kotlin/com/muliyul/dropwizard/swagger/bundles/auth/CredentialsProvider.kt new file mode 100644 index 0000000..734143c --- /dev/null +++ b/src/test/kotlin/com/muliyul/dropwizard/swagger/bundles/auth/CredentialsProvider.kt @@ -0,0 +1,7 @@ +package com.muliyul.dropwizard.swagger.bundles.auth + +import javax.ws.rs.container.* + +fun interface CredentialsProvider { + fun retrieveCredentials(requestContext: ContainerRequestContext): C +} diff --git a/src/test/kotlin/com/muliyul/dropwizard/swagger/bundles/auth/GenericAuthFilterBuilder.kt b/src/test/kotlin/com/muliyul/dropwizard/swagger/bundles/auth/GenericAuthFilterBuilder.kt new file mode 100644 index 0000000..82da366 --- /dev/null +++ b/src/test/kotlin/com/muliyul/dropwizard/swagger/bundles/auth/GenericAuthFilterBuilder.kt @@ -0,0 +1,7 @@ +package com.muliyul.dropwizard.swagger.bundles.auth + +import io.dropwizard.auth.* +import java.security.* + +abstract class GenericAuthFilterBuilder> : + AuthFilter.AuthFilterBuilder() diff --git a/src/test/kotlin/com/muliyul/dropwizard/swagger/ext/Optional.kt b/src/test/kotlin/com/muliyul/dropwizard/swagger/ext/Optional.kt new file mode 100644 index 0000000..dc9dbcb --- /dev/null +++ b/src/test/kotlin/com/muliyul/dropwizard/swagger/ext/Optional.kt @@ -0,0 +1,5 @@ +package com.muliyul.dropwizard.swagger.ext + +import java.util.* + +fun T?.toOptional() = Optional.ofNullable(this) diff --git a/src/test/resources/openapi.yaml b/src/test/resources/openapi.yaml index 61d873f..aa35eed 100644 --- a/src/test/resources/openapi.yaml +++ b/src/test/resources/openapi.yaml @@ -1,802 +1,37 @@ -openapi: 3.0.1 -info: - title: Swagger Petstore - OpenAPI 3.0 - description: "This is a sample Pet Store Server based on the OpenAPI 3.0 specification.\ - \ You can find out more about\nSwagger at [http://swagger.io](http://swagger.io).\ - \ In the third iteration of the pet store, we've switched to the design first\ - \ approach!\nYou can now help us improve the API whether it's by making changes\ - \ to the definition itself or to the code.\nThat way, with time, we can improve\ - \ the API in general, and expose some of the new features in OAS3.\n\nSome useful\ - \ links:\n- [The Pet Store repository](https://github.com/swagger-api/swagger-petstore)\n\ - - [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/resources/openapi.yaml)" - termsOfService: http://swagger.io/terms/ - contact: - email: apiteam@swagger.io - license: - name: Apache 2.0 - url: http://www.apache.org/licenses/LICENSE-2.0.html - version: 1.0.5 -externalDocs: - description: Find out more about Swagger - url: http://swagger.io -servers: - - url: /api/v3 -tags: - - name: pet - description: Everything about your Pets - externalDocs: - description: Find out more - url: http://swagger.io - - name: store - description: Operations about user - - name: user - description: Access to Petstore orders - externalDocs: - description: Find out more about our store - url: http://swagger.io -paths: - /pet: - put: - tags: - - pet - summary: Update an existing pet - description: Update an existing pet by Id - operationId: updatePet - requestBody: - description: Update an existent pet in the store - content: - application/json: - schema: - $ref: '#/components/schemas/Pet' - application/xml: - schema: - $ref: '#/components/schemas/Pet' - application/x-www-form-urlencoded: - schema: - $ref: '#/components/schemas/Pet' - required: true - responses: - "200": - description: Successful operation - content: - application/xml: - schema: - $ref: '#/components/schemas/Pet' - application/json: - schema: - $ref: '#/components/schemas/Pet' - "400": - description: Invalid ID supplied - "404": - description: Pet not found - "405": - description: Validation exception - security: - - petstore_auth: - - write:pets - - read:pets - post: - tags: - - pet - summary: Add a new pet to the store - description: Add a new pet to the store - operationId: addPet - requestBody: - description: Create a new pet in the store - content: - application/json: - schema: - $ref: '#/components/schemas/Pet' - application/xml: - schema: - $ref: '#/components/schemas/Pet' - application/x-www-form-urlencoded: - schema: - $ref: '#/components/schemas/Pet' - required: true - responses: - "200": - description: Successful operation - content: - application/xml: - schema: - $ref: '#/components/schemas/Pet' - application/json: - schema: - $ref: '#/components/schemas/Pet' - "405": - description: Invalid input - security: - - petstore_auth: - - write:pets - - read:pets - /pet/findByStatus: - get: - tags: - - pet - summary: Finds Pets by status - description: Multiple status values can be provided with comma separated strings - operationId: findPetsByStatus - parameters: - - name: status - in: query - description: Status values that need to be considered for filter - required: false - explode: true - schema: - type: string - default: available - enum: - - available - - pending - - sold - responses: - "200": - description: successful operation - content: - application/xml: - schema: - type: array - items: - $ref: '#/components/schemas/Pet' - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/Pet' - "400": - description: Invalid status value - security: - - petstore_auth: - - write:pets - - read:pets - /pet/findByTags: - get: - tags: - - pet - summary: Finds Pets by tags - description: "Multiple tags can be provided with comma separated strings. Use\ - \ tag1, tag2, tag3 for testing." - operationId: findPetsByTags - parameters: - - name: tags - in: query - description: Tags to filter by - required: false - explode: true - schema: - type: array - items: - type: string - responses: - "200": - description: successful operation - content: - application/xml: - schema: - type: array - items: - $ref: '#/components/schemas/Pet' - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/Pet' - "400": - description: Invalid tag value - security: - - petstore_auth: - - write:pets - - read:pets - /pet/{petId}: - get: - tags: - - pet - summary: Find pet by ID - description: Returns a single pet - operationId: getPetById - parameters: - - name: petId - in: path - description: ID of pet to return - required: true - schema: - type: integer - format: int64 - responses: - "200": - description: successful operation - content: - application/xml: - schema: - $ref: '#/components/schemas/Pet' - application/json: - schema: - $ref: '#/components/schemas/Pet' - "400": - description: Invalid ID supplied - "404": - description: Pet not found - security: - - api_key: [] - - petstore_auth: - - write:pets - - read:pets - post: - tags: - - pet - summary: Updates a pet in the store with form data - description: "" - operationId: updatePetWithForm - parameters: - - name: petId - in: path - description: ID of pet that needs to be updated - required: true - schema: - type: integer - format: int64 - - name: name - in: query - description: Name of pet that needs to be updated - schema: - type: string - - name: status - in: query - description: Status of pet that needs to be updated - schema: - type: string - responses: - "405": - description: Invalid input - security: - - petstore_auth: - - write:pets - - read:pets - delete: - tags: - - pet - summary: Deletes a pet - description: "" - operationId: deletePet - parameters: - - name: api_key - in: header - description: "" - required: false - schema: - type: string - - name: petId - in: path - description: Pet id to delete - required: true - schema: - type: integer - format: int64 - responses: - "400": - description: Invalid pet value - security: - - petstore_auth: - - write:pets - - read:pets - /pet/{petId}/uploadImage: - post: - tags: - - pet - summary: uploads an image - description: "" - operationId: uploadFile - parameters: - - name: petId - in: path - description: ID of pet to update - required: true - schema: - type: integer - format: int64 - - name: additionalMetadata - in: query - description: Additional Metadata - required: false - schema: - type: string - requestBody: - content: - application/octet-stream: - schema: - type: string - format: binary - responses: - "200": - description: successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/ApiResponse' - security: - - petstore_auth: - - write:pets - - read:pets - /store/inventory: - get: - tags: - - store - summary: Returns pet inventories by status - description: Returns a map of status codes to quantities - operationId: getInventory - responses: - "200": - description: successful operation - content: - application/json: - schema: - type: object - additionalProperties: - type: integer - format: int32 - security: - - api_key: [] - /store/order: - post: - tags: - - store - summary: Place an order for a pet - description: Place a new order in the store - operationId: placeOrder - requestBody: - content: - application/json: - schema: - $ref: '#/components/schemas/Order' - application/xml: - schema: - $ref: '#/components/schemas/Order' - application/x-www-form-urlencoded: - schema: - $ref: '#/components/schemas/Order' - responses: - "200": - description: successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/Order' - "405": - description: Invalid input - /store/order/{orderId}: - get: - tags: - - store - summary: Find purchase order by ID - description: For valid response try integer IDs with value <= 5 or > 10. Other - values will generated exceptions - operationId: getOrderById - parameters: - - name: orderId - in: path - description: ID of order that needs to be fetched - required: true - schema: - type: integer - format: int64 - responses: - "200": - description: successful operation - content: - application/xml: - schema: - $ref: '#/components/schemas/Order' - application/json: - schema: - $ref: '#/components/schemas/Order' - "400": - description: Invalid ID supplied - "404": - description: Order not found - delete: - tags: - - store - summary: Delete purchase order by ID - description: For valid response try integer IDs with value < 1000. Anything - above 1000 or nonintegers will generate API errors - operationId: deleteOrder - parameters: - - name: orderId - in: path - description: ID of the order that needs to be deleted - required: true - schema: - type: integer - format: int64 - responses: - "400": - description: Invalid ID supplied - "404": - description: Order not found - /user: - post: - tags: - - user - summary: Create user - description: This can only be done by the logged in user. - operationId: createUser - requestBody: - description: Created user object - content: - application/json: - schema: - $ref: '#/components/schemas/User' - application/xml: - schema: - $ref: '#/components/schemas/User' - application/x-www-form-urlencoded: - schema: - $ref: '#/components/schemas/User' - responses: - default: - description: successful operation - content: - application/json: - schema: - $ref: '#/components/schemas/User' - application/xml: - schema: - $ref: '#/components/schemas/User' - /user/createWithList: - post: - tags: - - user - summary: Creates list of users with given input array - description: Creates list of users with given input array - operationId: createUsersWithListInput - requestBody: - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/User' - responses: - "200": - description: Successful operation - content: - application/xml: - schema: - $ref: '#/components/schemas/User' - application/json: - schema: - $ref: '#/components/schemas/User' - default: - description: successful operation - /user/login: - get: - tags: - - user - summary: Logs user into the system - description: "" - operationId: loginUser - parameters: - - name: username - in: query - description: The user name for login - required: false - schema: - type: string - - name: password - in: query - description: The password for login in clear text - required: false - schema: - type: string - responses: - "200": - description: successful operation - headers: - X-Rate-Limit: - description: calls per hour allowed by the user - schema: - type: integer - format: int32 - X-Expires-After: - description: date in UTC when toekn expires - schema: - type: string - format: date-time - content: - application/xml: - schema: - type: string - application/json: - schema: - type: string - "400": - description: Invalid username/password supplied - /user/logout: - get: - tags: - - user - summary: Logs out current logged in user session - description: "" - operationId: logoutUser - parameters: [] - responses: - default: - description: successful operation - /user/{username}: - get: - tags: - - user - summary: Get user by user name - description: "" - operationId: getUserByName - parameters: - - name: username - in: path - description: 'The name that needs to be fetched. Use user1 for testing. ' - required: true - schema: - type: string - responses: - "200": - description: successful operation - content: - application/xml: - schema: - $ref: '#/components/schemas/User' - application/json: - schema: - $ref: '#/components/schemas/User' - "400": - description: Invalid username supplied - "404": - description: User not found - put: - tags: - - user - summary: Update user - description: This can only be done by the logged in user. - operationId: updateUser - parameters: - - name: username - in: path - description: name that need to be deleted - required: true - schema: - type: string - requestBody: - description: Update an existent user in the store - content: - application/json: - schema: - $ref: '#/components/schemas/User' - application/xml: - schema: - $ref: '#/components/schemas/User' - application/x-www-form-urlencoded: - schema: - $ref: '#/components/schemas/User' - responses: - default: - description: successful operation - delete: - tags: - - user - summary: Delete user - description: This can only be done by the logged in user. - operationId: deleteUser - parameters: - - name: username - in: path - description: The name that needs to be deleted - required: true - schema: - type: string - responses: - "400": - description: Invalid username supplied - "404": - description: User not found -components: - schemas: - Order: - type: object - properties: - id: - type: integer - format: int64 - example: 10 - petId: - type: integer - format: int64 - example: 198772 - quantity: - type: integer - format: int32 - example: 7 - shipDate: - type: string - format: date-time - status: - type: string - description: Order Status - example: approved - enum: - - placed - - approved - - delivered - complete: - type: boolean - xml: - name: order - Customer: - type: object - properties: - id: - type: integer - format: int64 - example: 100000 - username: - type: string - example: fehguy - address: - type: array - xml: - name: addresses - wrapped: true - items: - $ref: '#/components/schemas/Address' - xml: - name: customer - Address: - type: object - properties: - street: - type: string - example: 437 Lytton - city: - type: string - example: Palo Alto - state: - type: string - example: CA - zip: - type: string - example: "94301" - xml: - name: address - Category: - type: object - properties: - id: - type: integer - format: int64 - example: 1 - name: - type: string - example: Dogs - xml: - name: category - User: - type: object - properties: - id: - type: integer - format: int64 - example: 10 - username: - type: string - example: theUser - firstName: - type: string - example: John - lastName: - type: string - example: James - email: - type: string - example: john@email.com - password: - type: string - example: "12345" - phone: - type: string - example: "12345" - userStatus: - type: integer - description: User Status - format: int32 - example: 1 - xml: - name: user - Tag: - type: object - properties: - id: - type: integer - format: int64 - name: - type: string - xml: - name: tag - Pet: - required: - - name - - photoUrls - type: object - properties: - id: - type: integer - format: int64 - example: 10 - name: - type: string - example: doggie - category: - $ref: '#/components/schemas/Category' - photoUrls: - type: array - xml: - wrapped: true - items: - type: string - xml: - name: photoUrl - tags: - type: array - xml: - wrapped: true - items: - $ref: '#/components/schemas/Tag' - status: - type: string - description: pet status in the store - enum: - - available - - pending - - sold - xml: - name: pet - ApiResponse: - type: object - properties: - code: - type: integer - format: int32 - type: - type: string - message: - type: string - xml: - name: '##default' - requestBodies: - Pet: - description: Pet object that needs to be added to the store - content: - application/json: - schema: - $ref: '#/components/schemas/Pet' - application/xml: - schema: - $ref: '#/components/schemas/Pet' - UserArray: - description: List of user object - content: - application/json: - schema: - type: array - items: - $ref: '#/components/schemas/User' - securitySchemes: - petstore_auth: - type: oauth2 - flows: - implicit: - authorizationUrl: https://petstore3.swagger.io/oauth/authorize - scopes: - write:pets: modify pets in your account - read:pets: read your pets - api_key: - type: apiKey - name: api_key - in: header +prettyPrint: true +cacheTTL: 30000 +openAPI: + info: + title: Swagger Petstore - OpenAPI 3.0 + description: "This is a sample Pet Store Server based on the OpenAPI 3.0 specification.\ + \ You can find out more about\nSwagger at [http://swagger.io](http://swagger.io).\ + \ In the third iteration of the pet store, we've switched to the design first\ + \ approach!\nYou can now help us improve the API whether it's by making changes\ + \ to the definition itself or to the code.\nThat way, with time, we can improve\ + \ the API in general, and expose some of the new features in OAS3.\n\nSome useful\ + \ links:\n- [The Pet Store repository](https://github.com/swagger-api/swagger-petstore)\n\ + - [The source API definition for the Pet Store](https://github.com/swagger-api/swagger-petstore/blob/master/src/main/com.muliyul.dropwizard.swagger.resources/openapi.yaml)" + termsOfService: http://swagger.io/terms/ + contact: + email: apiteam@swagger.io + license: + name: Apache 2.0 + url: http://www.apache.org/licenses/LICENSE-2.0.html + version: 1.0.5 + externalDocs: + description: Find out more about Swagger + url: http://swagger.io + components: + securitySchemes: + petstore_auth: + type: oauth2 + flows: + implicit: + authorizationUrl: https://petstore3.swagger.io/oauth/authorize + scopes: + write:pets: modify pets in your account + read:pets: read your pets + api_key: + type: apiKey + name: api_key + in: header