Skip to content

Commit

Permalink
Merge pull request #5 from funkyFangs/SC-4-add-icon-customization
Browse files Browse the repository at this point in the history
SC-4: add swagger UI customization
  • Loading branch information
funkyFangs authored Jun 1, 2023
2 parents 7debf95 + 4a20ddb commit 8248feb
Show file tree
Hide file tree
Showing 40 changed files with 463 additions and 39 deletions.
4 changes: 3 additions & 1 deletion .idea/gradle.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

7 changes: 7 additions & 0 deletions gradle/libs.versions.toml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ springdoc = '2.1.0'
# Swagger
swagger = '2.2.9'

# Tomcat
tomcat-embed = '10.1.7'

[libraries]

# AssertJ
Expand Down Expand Up @@ -63,6 +66,7 @@ spring-boot-starter-test = {group = 'org.springframework.boot', name = 'spring-b
spring-boot-starter-validation = {group = 'org.springframework.boot', name = 'spring-boot-starter-validation', version.ref = 'spring-boot'}
spring-boot-starter-web = {group = 'org.springframework.boot', name = 'spring-boot-starter-web', version.ref = 'spring-boot'}
spring-boot-starter-webflux = {group = 'org.springframework.boot', name = 'spring-boot-starter-webflux', version.ref = 'spring-boot'}
spring-boot-starter-webmvc = {group = 'org.springframework.boot', name = 'spring-boot-starter-webflux', version.ref = 'spring-boot'}
spring-web = {group = 'org.springframework', name = 'spring-web', version.ref = 'spring'}

# SpringDoc
Expand All @@ -73,6 +77,9 @@ springdoc-openapi-starter-webflux-ui = {group = 'org.springdoc', name = 'springd
# Swagger
swagger-annotations = {group = 'io.swagger.core.v3', name = 'swagger-annotations', version.ref = 'swagger'}

# Tomcat
tomcat-embed-core = {group = 'org.apache.tomcat.embed', name = 'tomcat-embed-core', version.ref = 'tomcat-embed'}

[plugins]

# Lombok
Expand Down
6 changes: 4 additions & 2 deletions settings.gradle
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
rootProject.name = 'springdoc-customizer'
include 'springdoc-customizer'
include 'springdoc-customizer-webmvc-demo'
include 'springdoc-customizer-common'
include 'springdoc-customizer-webmvc-demo'
include 'springdoc-customizer-webmvc'
include 'springdoc-customizer-webflux'
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ dependencies {

// SpringDoc
implementation libs.springdoc.openapi.starter.common

// Tomcat
implementation libs.tomcat.embed.core
}

test {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.funky.fangs.springdoc.customizer.configuration;

import io.funky.fangs.springdoc.customizer.customizer.ExamplesOpenApiCustomizer;
import jakarta.validation.Validator;
import lombok.Getter;
import lombok.Setter;
Expand Down Expand Up @@ -35,4 +36,6 @@ public class ExamplesCustomizerConfigurationProperties {
* where applicable.
*/
private boolean includeDefaultProducesMediaType = true;

private boolean enabled;
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package io.funky.fangs.springdoc.customizer.configuration;

import io.funky.fangs.springdoc.customizer.customizer.ExamplesOpenApiCustomizer;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Info;
import jakarta.validation.Validator;
Expand All @@ -26,7 +27,10 @@
* @since 2.1.0
*/
@AutoConfiguration
@EnableConfigurationProperties(SpringDocCustomizerConfigurationProperties.class)
@EnableConfigurationProperties({
SpringDocCustomizerConfigurationProperties.class,
SpringDocCustomizerSwaggerUiConfigurationProperties.class
})
@ConditionalOnProperty(prefix = SpringDocCustomizerConfigurationProperties.PREFIX, name = "enabled",
matchIfMissing = true)
public class SpringDocCustomizerAutoConfiguration {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,4 +23,6 @@ public class SpringDocCustomizerConfigurationProperties {

@NestedConfigurationProperty
private ExamplesCustomizerConfigurationProperties examples = new ExamplesCustomizerConfigurationProperties();

private boolean enabled;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.funky.fangs.springdoc.customizer.configuration;

import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
* Configuration properties related to the Swagger UI.
*
* @author Harper Price
* @since 2.1.0
*/
@Getter
@Setter
@ConfigurationProperties(prefix = "springdoc.swagger-ui")
public class SpringDocCustomizerSwaggerUiConfigurationProperties {
/**
* Sets the title of the Swagger UI webpage to this value.
*/
private String title;
private String largeIconPath;
private String smallIconPath;
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.funky.fangs.springdoc.customizer.configuration;
package io.funky.fangs.springdoc.customizer.customizer;

import com.google.common.annotations.VisibleForTesting;
import io.funky.fangs.springdoc.customizer.annotation.ExampleDetails;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package io.funky.fangs.springdoc.customizer.configuration;
package io.funky.fangs.springdoc.customizer.customizer;

import io.swagger.v3.oas.models.examples.Example;
import lombok.Data;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,25 +1,22 @@
package io.funky.fangs.springdoc.customizer.model;

import com.fasterxml.jackson.annotation.JsonInclude;
import io.funky.fangs.springdoc.customizer.annotation.ExampleDetails;

import java.util.Collection;
import java.util.List;
import java.util.Optional;

import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY;
import static java.util.Collections.emptyList;

/**
* A record used for storing {@link ExampleDetails} data at run-time.
*
* @param name
* @param summary
* @param description
* @param targets
* @author Harper Price
* @see ExampleDetails
* @since 2.1.0
*/
public record ExampleDetailsRecord(String name, String summary, String description,
@JsonInclude(NON_EMPTY) Collection<ExampleTargetRecord> targets) {
Collection<ExampleTargetRecord> targets) {
public ExampleDetailsRecord(String name, String summary, String description,
Collection<ExampleTargetRecord> targets) {
this.name = name;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,21 @@
package io.funky.fangs.springdoc.customizer.model;

import com.fasterxml.jackson.annotation.JsonInclude;
import io.funky.fangs.springdoc.customizer.annotation.ExampleMethod;

import java.util.Collection;
import java.util.List;
import java.util.Optional;

import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY;
import static java.util.Collections.emptyList;

public record ExampleMethodRecord(String name, @JsonInclude(NON_EMPTY) Collection<ExampleTypeRecord> types) {
/**
* A record used for storing {@link ExampleMethod} data at run-time.
*
* @author Harper Price
* @see ExampleMethod
* @since 2.1.0
*/
public record ExampleMethodRecord(String name, Collection<ExampleTypeRecord> types) {
public ExampleMethodRecord(String name, Collection<ExampleTypeRecord> types) {
this.name = name;
this.types = Optional.ofNullable(types)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,21 +1,21 @@
package io.funky.fangs.springdoc.customizer.model;

import com.fasterxml.jackson.annotation.JsonInclude;
import io.funky.fangs.springdoc.customizer.annotation.ExampleTarget;

import java.util.Collection;
import java.util.List;
import java.util.Optional;

import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY;
import static java.util.Collections.emptyList;

/**
* @param controller
* @param methods
* A record used for storing {@link ExampleTarget} data at run-time.
*
* @author Harper Price
* @see ExampleTarget
* @since 2.1.0
*/
public record ExampleTargetRecord(String controller, @JsonInclude(NON_EMPTY) Collection<ExampleMethodRecord> methods) {
public record ExampleTargetRecord(String controller, Collection<ExampleMethodRecord> methods) {
public ExampleTargetRecord(String controller, Collection<ExampleMethodRecord> methods) {
this.controller = controller;
this.methods = Optional.ofNullable(methods)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,29 +1,33 @@
package io.funky.fangs.springdoc.customizer.model;

import com.fasterxml.jackson.annotation.JsonInclude;
import io.funky.fangs.springdoc.customizer.annotation.ExampleType;
import io.funky.fangs.springdoc.customizer.annotation.ExampleType.Type;
import org.springframework.http.HttpStatus;

import java.util.Collection;
import java.util.EnumSet;
import java.util.Optional;
import java.util.Set;
import java.util.*;

import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY;
import static java.util.Collections.emptySet;
import static java.util.Collections.unmodifiableSet;
import static java.util.function.Predicate.not;

public record ExampleTypeRecord(Type type, @JsonInclude(NON_EMPTY) Collection<String> mediaTypes,
@JsonInclude(NON_EMPTY) Collection<HttpStatus> responses) {
/**
* A record used for storing {@link ExampleType} data at run-time.
*
* @author Harper Price
* @see ExampleType
* @since 2.1.0
*/
public record ExampleTypeRecord(Type type, Collection<String> mediaTypes, Collection<HttpStatus> responses) {
public ExampleTypeRecord(Type type, Collection<String> mediaTypes, Collection<HttpStatus> responses) {
this.type = type;
this.mediaTypes = Optional.ofNullable(mediaTypes)
.map(Set::copyOf)
.orElse(emptySet());
this.responses = Optional.ofNullable(responses)
.filter(not(Collection::isEmpty))
.map(collection -> unmodifiableSet(EnumSet.copyOf(collection)))
// Creates an unmodifiable view of an EnumSet for data integrity and performance
.map(EnumSet::copyOf)
.map(Collections::unmodifiableSet)
.orElse(unmodifiableSet(EnumSet.noneOf(HttpStatus.class)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import java.lang.reflect.Field;
import java.util.*;

import static com.fasterxml.jackson.annotation.JsonInclude.Include.NON_EMPTY;
import static java.util.Collections.emptyMap;
import static javax.tools.StandardLocation.CLASS_OUTPUT;

Expand All @@ -31,7 +32,8 @@ public class ExamplesProcessor extends AbstractProcessor {
@VisibleForTesting
static final String EXAMPLES_FILE_NAME = "examples.json";
private static final TypeReference<Map<String, Map<String, ExampleDetailsRecord>>> EXAMPLES_MAP_TYPE_REFERENCE = new TypeReference<>() {};
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper();
private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper()
.setSerializationInclusion(NON_EMPTY);

private FileObject examplesResourceFile;

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package io.funky.fangs.springdoc.customizer.utility;

import lombok.experimental.UtilityClass;
import org.springframework.core.io.ClassPathResource;

import java.io.IOException;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.regex.Pattern;

@UtilityClass
public class ResourceUtilities {
public final int LARGE_ICON_SIZE = 32;
public final int SMALL_ICON_SIZE = 16;
private final String ICON_FORMAT = "(<link rel=\"icon\" type=\"image/png\" href=\")"
+ "./favicon-%dx%d.png"
+ "(\" sizes=\"%dx%d\" />)";
public final Pattern SMALL_ICON_PATTERN = getIconPattern(SMALL_ICON_SIZE);
public final Pattern LARGE_ICON_PATTERN = getIconPattern(LARGE_ICON_SIZE);
public final Pattern TITLE_PATTERN = Pattern.compile("(<title>)Swagger UI(</title>)");
public final String INDEX_FILE = "index.html";
public final String ROOT_PATH = "/";

private final Path BASE_PATH;

static {
Path path;
try {
path = Paths.get(new ClassPathResource("static").getURI()).normalize();
} catch (IOException e) {
path = null;
}
BASE_PATH = path;
}

private Pattern getIconPattern(int size) {
return Pattern.compile(ICON_FORMAT.formatted(size, size, size, size));
}

public String replaceLargeIcon(String document, String largeIconPath) {
return replaceIcon(document, largeIconPath, LARGE_ICON_PATTERN);
}

public String replaceSmallIcon(String document, String smallIconPath) {
return replaceIcon(document, smallIconPath, SMALL_ICON_PATTERN);
}

private String replaceIcon(String document, String iconPath, Pattern pattern) {
return replaceEnclosedToken(document, iconPath, pattern);
}

public String replaceTitle(String document, String title) {
return replaceEnclosedToken(document, title, TITLE_PATTERN);
}

private String replaceEnclosedToken(String document, String replacement, Pattern pattern) {
return pattern.matcher(document)
.replaceFirst(matchResult -> matchResult.group(1) + replacement + matchResult.group(2));
}

public String resolvePath(String pathName) {
// Resolve and normalize path from base path
var resolvedPath = BASE_PATH.resolve(pathName).normalize();

// Ensure that normalized path is still within scope, and that the path points to an existing file
if (resolvedPath.startsWith(BASE_PATH) && resolvedPath.toFile().exists()) {
// Make path relative to base path; this simplifies redundant traversals within the given path
// For example: "../static/icon.png" -> "icon.png"
return BASE_PATH.relativize(resolvedPath).toString();
}
else {
return null;
}
}
}
13 changes: 13 additions & 0 deletions springdoc-customizer-webflux/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
plugins {
alias(libs.plugins.lombok)
id 'java-library'
}

dependencies {
// SpringDoc
implementation libs.springdoc.openapi.starter.webflux.ui

// SpringDoc Customizer
api project(':springdoc-customizer-common')
annotationProcessor project(':springdoc-customizer-common')
}
Loading

0 comments on commit 8248feb

Please sign in to comment.