Skip to content

Commit

Permalink
feat(SwaggerUI): Add Swagger UI configuration parameters
Browse files Browse the repository at this point in the history
  • Loading branch information
aoudiamoncef committed Dec 22, 2019
1 parent 54eb2f5 commit 08c6fb1
Show file tree
Hide file tree
Showing 3 changed files with 164 additions and 24 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
package io.quarkus.swaggerui.deployment;

import java.util.Optional;
import java.util.OptionalInt;

import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.runtime.annotations.ConfigRoot;

/**
* Please @see <a href="https://swagger.io/docs/open-source-tools/swagger-ui/usage/configuration"</a>
*/
@ConfigRoot
class SwaggerUiConfig {
/**
* The path where Swagger UI is available.
* <p>
* The value `/` is not allowed as it blocks the application from serving anything else.
*/
@ConfigItem(defaultValue = "/swagger-ui")
String path;

/**
* If this should be included every time. By default, this is only included when the application is running
* in dev mode.
*/
@ConfigItem(defaultValue = "false")
boolean alwaysInclude;

/**
* URL to fetch external configuration document from.
*/
@ConfigItem
Optional<String> configUrl;

/**
* Enables deep linking for tags and operations.
*
* @see <a href="https://swagger.io/docs/open-source-tools/swagger-ui/usage/deep-linking</a>
*/
@ConfigItem
Optional<Boolean> deepLinking;

/**
* The default expansion depth for models (set to -1 completely hide the models).
*/
@ConfigItem
OptionalInt defaultModelsExpandDepth;

/**
* The default expansion depth for the model on the model-example section.
*/
@ConfigItem
OptionalInt defaultModelExpandDepth;

/**
* Controls how the model is shown when the API is first rendered.
*/
@ConfigItem
Optional<String> defaultModelRendering;

/**
* Controls the display of operationId in operations list.
*/
@ConfigItem
Optional<Boolean> displayOperationId;

/**
* Controls the display of the request duration (in milliseconds) for Try-It-Out requests.
*/
@ConfigItem
Optional<Boolean> displayRequestDuration;

/**
* Controls the default expansion setting for the operations and tags.
*/
@ConfigItem
Optional<String> docExpansion;

/**
* If set, enables filtering. The top bar will show an edit box that could be used to filter
* the tagged operations that are shown.
*/
@ConfigItem
Optional<String> filter;

/**
* If set, limits the number of tagged operations displayed to at most this many.
*/
@ConfigItem
Optional<Integer> maxDisplayedTags;

/**
* Controls the display of vendor extension (x-) fields and values.
*/
@ConfigItem
Optional<Boolean> showExtensions;

/**
* Controls the display of extensions
*/
@ConfigItem
Optional<Boolean> showCommonExtensions;

/**
* The url pointing to API definition (normally swagger.json/swagger.yaml/openapi.json/openapi.yaml).
*/
@ConfigItem
Optional<String> url;

/**
* Set a different validator URL, for example for locally deployed validators
*/
@ConfigItem
Optional<String> validatorUrl;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,15 @@
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.Enumeration;
import java.util.Map;
import java.util.TreeMap;
import java.util.jar.JarEntry;
import java.util.jar.JarFile;
import java.util.regex.Matcher;
Expand All @@ -33,8 +37,6 @@
import io.quarkus.deployment.index.ClassPathArtifactResolver;
import io.quarkus.deployment.index.ResolvedArtifact;
import io.quarkus.deployment.util.FileUtil;
import io.quarkus.runtime.annotations.ConfigItem;
import io.quarkus.runtime.annotations.ConfigRoot;
import io.quarkus.smallrye.openapi.common.deployment.SmallRyeOpenApiConfig;
import io.quarkus.swaggerui.runtime.SwaggerUiRecorder;
import io.quarkus.vertx.http.deployment.HttpRootPathBuildItem;
Expand Down Expand Up @@ -119,15 +121,15 @@ public void registerSwaggerUiServletExtension(SwaggerUiRecorder recorder,
}
}
Handler<RoutingContext> handler = recorder.handler(cached.cachedDirectory,
httpRootPathBuildItem.adjustPath(swaggerUiConfig.path));
httpRootPathBuildItem.adjustPath(swaggerUiConfig.path), getSwaggerUiParams(swaggerUiConfig));
routes.produce(new RouteBuildItem(swaggerUiConfig.path, handler));
routes.produce(new RouteBuildItem(swaggerUiConfig.path + "/*", handler));
displayableEndpoints.produce(new NotFoundPageDisplayableEndpointBuildItem(swaggerUiConfig.path + "/"));
} else if (swaggerUiConfig.alwaysInclude) {
ResolvedArtifact artifact = getSwaggerUiArtifact();
//we are including in a production artifact
//just stick the files in the generated output
//we could do this for dev mode as well but then we need to extract them every time
//we could do this for dev mode as well, but then we need to extract them every time
File artifactFile = artifact.getArtifactPath().toFile();
try (JarFile jarFile = new JarFile(artifactFile)) {
Enumeration<JarEntry> entries = jarFile.entries();
Expand All @@ -154,7 +156,8 @@ public void registerSwaggerUiServletExtension(SwaggerUiRecorder recorder,
}

Handler<RoutingContext> handler = recorder
.handler(SWAGGER_UI_FINAL_DESTINATION, httpRootPathBuildItem.adjustPath(swaggerUiConfig.path));
.handler(SWAGGER_UI_FINAL_DESTINATION, httpRootPathBuildItem.adjustPath(swaggerUiConfig.path),
getSwaggerUiParams(swaggerUiConfig));
routes.produce(new RouteBuildItem(swaggerUiConfig.path, handler));
routes.produce(new RouteBuildItem(swaggerUiConfig.path + "/*", handler));
}
Expand Down Expand Up @@ -201,22 +204,44 @@ public String updateApiUrl(String original, String openApiPath) {
}
}

@ConfigRoot
static final class SwaggerUiConfig {
/**
* The path where Swagger UI is available.
* <p>
* The value `/` is not allowed as it blocks the application from serving anything else.
*/
@ConfigItem(defaultValue = "/swagger-ui")
String path;

/**
* If this should be included every time. By default this is only included when the application is running
* in dev mode.
*/
@ConfigItem(defaultValue = "false")
boolean alwaysInclude;
private String getSwaggerUiParams(SwaggerUiConfig config) {
Map<String, String> props = new TreeMap<>();
config.url.ifPresent(s -> props.put("url", s));
config.configUrl.ifPresent(s -> props.put("configUrl", s));
config.filter.ifPresent(s -> props.put("filter", s));
config.deepLinking.ifPresent(b -> props.put("deepLinking", b.toString()));
config.displayOperationId.ifPresent(b -> props.put("displayOperationId", b.toString()));
config.defaultModelsExpandDepth.ifPresent(i -> props.put("defaultModelsExpandDepth", String.valueOf(i)));
config.defaultModelExpandDepth.ifPresent(i -> props.put("defaultModelExpandDepth", String.valueOf(i)));
config.defaultModelRendering.ifPresent(s -> props.put("defaultModelRendering", s));
config.displayRequestDuration.ifPresent(b -> props.put("displayRequestDuration", b.toString()));
config.docExpansion.ifPresent(s -> props.put("docExpansion", s));
config.maxDisplayedTags.ifPresent(i -> props.put("maxDisplayedTags", String.valueOf(i)));
config.showExtensions.ifPresent(b -> props.put("showExtensions", b.toString()));
config.showCommonExtensions.ifPresent(b -> props.put("showCommonExtensions", b.toString()));
config.validatorUrl.ifPresent(s -> props.put("validatorUrl", s));
if (props.isEmpty()) {
return "";
} else {
return mapToQueryString(props);
}
}

private String mapToQueryString(Map<String, String> props) {
StringBuilder sb = new StringBuilder();
sb.append('?');
for (Map.Entry<String, String> entry : props.entrySet()) {
if (sb.length() > 1) {
sb.append('&');
}
try {
sb.append(URLEncoder.encode(entry.getKey(), "UTF-8")).append('=')
.append(URLEncoder.encode(entry.getValue(), "UTF-8"));
} catch (UnsupportedEncodingException e) {
log.error("Failed to build Swagger UI query parameters", e);
}
}
return sb.toString();
}

private static final class CachedSwaggerUI implements Runnable {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@

@Recorder
public class SwaggerUiRecorder {
public Handler<RoutingContext> handler(String swaggerUiFinalDestination, String swaggerUiPath) {
public Handler<RoutingContext> handler(String swaggerUiFinalDestination, String swaggerUiPath,
String swaggerUiParams) {

Handler<RoutingContext> handler = new ThreadLocalHandler(new Supplier<Handler<RoutingContext>>() {
@Override
Expand All @@ -26,9 +27,8 @@ public Handler<RoutingContext> get() {
@Override
public void handle(RoutingContext event) {
if (event.normalisedPath().length() == swaggerUiPath.length()) {

event.response().setStatusCode(302);
event.response().headers().set(HttpHeaders.LOCATION, swaggerUiPath + "/");
event.response().headers().set(HttpHeaders.LOCATION, swaggerUiPath + "/" + swaggerUiParams);
event.response().end();
return;
} else if (event.normalisedPath().length() == swaggerUiPath.length() + 1) {
Expand Down

0 comments on commit 08c6fb1

Please sign in to comment.