Skip to content

Commit

Permalink
maven-plugin make it easier to scan dependend schemas
Browse files Browse the repository at this point in the history
Added new options scan.profiles and scan.exclude.profiles to filter the operations that are kept in the schema.
Only one of the configured profiles is needed to either include, or exclude, the operation. Exclusions are checked first, then inclusions. Extensions containing profiles have to start with "x-smallrye-profile", and will not be included in the openapi document.
Naming of these new options are kept in line with the openapi scan.package and scan.external.package options.
  • Loading branch information
Postremus committed Nov 15, 2021
1 parent b28a859 commit b6c1361
Show file tree
Hide file tree
Showing 13 changed files with 277 additions and 39 deletions.
8 changes: 8 additions & 0 deletions core/src/main/java/io/smallrye/openapi/api/OpenApiConfig.java
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,14 @@ default Optional<Boolean> allowNakedPathParameter() {
return Optional.empty();
}

default Set<String> getScanProfiles() {
return new HashSet<>();
}

default Set<String> getScanExcludeProfiles() {
return new HashSet<>();
}

default void doAllowNakedPathParameter() {
}

Expand Down
26 changes: 26 additions & 0 deletions core/src/main/java/io/smallrye/openapi/api/OpenApiConfigImpl.java
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ public class OpenApiConfigImpl implements OpenApiConfig {
private String infoLicenseName;
private String infoLicenseUrl;
private OperationIdStrategy operationIdStrategy;
private Set<String> scanProfiles;
private Set<String> scanExcludeProfiles;
private Optional<String[]> defaultProduces = UNSET;
private Optional<String[]> defaultConsumes = UNSET;
private Optional<Boolean> allowNakedPathParameter = Optional.empty();
Expand Down Expand Up @@ -408,6 +410,30 @@ public Optional<String[]> getDefaultConsumes() {
return defaultConsumes;
}

@Override
public Set<String> getScanProfiles() {
if (scanProfiles == null) {
String classes = getStringConfigValue(OpenApiConstants.SCAN_PROFILES);
if (classes == null) {
classes = getStringConfigValue(OpenApiConstants.SCAN_PROFILES);
}
scanProfiles = asCsvSet(classes);
}
return scanProfiles;
}

@Override
public Set<String> getScanExcludeProfiles() {
if (scanExcludeProfiles == null) {
String classes = getStringConfigValue(OpenApiConstants.SCAN_EXCLUDE_PROFILES);
if (classes == null) {
classes = getStringConfigValue(OpenApiConstants.SCAN_EXCLUDE_PROFILES);
}
scanExcludeProfiles = asCsvSet(classes);
}
return scanExcludeProfiles;
}

/**
* getConfig().getOptionalValue(key) can return "" if optional {@link Converter}s are used. Enforce a null value if
* we get an empty string back.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ public final class OpenApiConstants {
public static final String SMALLRYE_PRIVATE_PROPERTIES_ENABLE = SMALLRYE_PREFIX + SUFFIX_PRIVATE_PROPERTIES_ENABLE;
public static final String SMALLRYE_PROPERTY_NAMING_STRATEGY = SMALLRYE_PREFIX + SUFFIX_PROPERTY_NAMING_STRATEGY;
public static final String SMALLRYE_SORTED_PROPERTIES_ENABLE = SMALLRYE_PREFIX + SUFFIX_SORTED_PROPERTIES_ENABLE;
public static final String SCAN_PROFILES = SMALLRYE_PREFIX + "scan.profiles";
public static final String SCAN_EXCLUDE_PROFILES = SMALLRYE_PREFIX + "scan.exclude.profiles";

public static final String VERSION = SMALLRYE_PREFIX + "openapi";
public static final String INFO_TITLE = SMALLRYE_PREFIX + "info.title";
Expand Down Expand Up @@ -108,6 +110,8 @@ public final class OpenApiConstants {
// Used by both Jax-rs and openapi
public static final String REF = "ref";

public static final String EXTENSION_PROFILE_PREFIX = "x-smallrye-profile-";

/**
* Constructor.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,11 +1,22 @@
package io.smallrye.openapi.runtime.scanner.spi;

import io.smallrye.openapi.api.OpenApiConfig;
import io.smallrye.openapi.api.constants.OpenApiConstants;
import org.eclipse.microprofile.openapi.models.Extensible;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
* Abstract base class for annotation scanners
*
* @author Phillip Kruger ([email protected])
*/
public abstract class AbstractAnnotationScanner implements AnnotationScanner {
private static final String EMPTY = "";

protected String currentAppPath = EMPTY;
private String contextRoot = EMPTY;

Expand All @@ -20,7 +31,7 @@ protected String makePath(String operationPath) {

/**
* Make a path out of a number of path segments.
*
*
* @param segments String paths
* @return Path built from the segments
*/
Expand All @@ -46,5 +57,45 @@ protected static String createPathFromSegments(String... segments) {
return rval;
}

private static final String EMPTY = "";
/**
* Checks if the given extensible contains profiles, and if the extensible should be included in the final openapi document.
* Any extension containing a profile is removed from the extensible.
* inclusion is then calculated based on all collected profiles.
*
* @param config current config
* @param extensible the extensible to check for profiles
* @return true, if the given extensible should be included in the final openapi document, otherwise false
*/
protected static boolean processProfiles(OpenApiConfig config, Extensible<?> extensible) {

Set<String> profiles = new HashSet<>();
Map<String, Object> extensions = extensible.getExtensions();
if (extensions != null && !extensions.isEmpty()) {
extensions = new HashMap<>(extensions);

for (String name : extensions.keySet()) {
if (!name.startsWith(OpenApiConstants.EXTENSION_PROFILE_PREFIX)) {
continue;
}

String profile = name.substring(OpenApiConstants.EXTENSION_PROFILE_PREFIX.length());
profiles.add(profile);
extensible.removeExtension(name);
}
}

return profileIncluded(config, profiles);
}

private static boolean profileIncluded(OpenApiConfig config, Set<String> profiles) {
if (!config.getScanExcludeProfiles().isEmpty()) {
return config.getScanExcludeProfiles().stream().noneMatch(profiles::contains);
}

if (config.getScanProfiles().isEmpty()) {
return true;
}

return config.getScanProfiles().stream().anyMatch(profiles::contains);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package io.smallrye.openapi.runtime.scanner.spi;

import io.smallrye.openapi.api.OpenApiConfig;
import io.smallrye.openapi.api.models.OperationImpl;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.util.Collections;
import java.util.Set;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue;

class AbstractAnnotationScannerTest {
/**
* Test method for {@link AbstractAnnotationScanner#makePath(String)}.
*/
@Test
void testMakePath() {

String path = AbstractAnnotationScanner.createPathFromSegments("", "", "");
Assertions.assertEquals("/", path);

path = AbstractAnnotationScanner.createPathFromSegments("/", "/");
Assertions.assertEquals("/", path);

path = AbstractAnnotationScanner.createPathFromSegments("", "/bookings");
Assertions.assertEquals("/bookings", path);

path = AbstractAnnotationScanner.createPathFromSegments("/api", "/bookings");
Assertions.assertEquals("/api/bookings", path);

path = AbstractAnnotationScanner.createPathFromSegments("api", "bookings");
Assertions.assertEquals("/api/bookings", path);

path = AbstractAnnotationScanner.createPathFromSegments("/", "/bookings", "{id}");
Assertions.assertEquals("/bookings/{id}", path);
}

@Test
void testNoConfiguredProfile() {
OpenApiConfig config = new OpenApiConfig() {
};

OperationImpl operation = new OperationImpl();
operation.setExtensions(Collections.singletonMap("x-smallrye-profile-external", ""));

boolean result = AbstractAnnotationScanner.processProfiles(config, operation);

assertTrue(result);
assertEquals(0, operation.getExtensions().size());
}

@Test
void testConfiguredIncludeProfile() {
OpenApiConfig config = new OpenApiConfig() {
@Override
public Set<String> getScanProfiles() {
return Collections.singleton("external");
}
};

OperationImpl operation = new OperationImpl();

boolean result = AbstractAnnotationScanner.processProfiles(config, operation);
assertFalse(result);

operation.setExtensions(Collections.singletonMap("x-smallrye-profile-external", ""));
result = AbstractAnnotationScanner.processProfiles(config, operation);

assertTrue(result);
assertEquals(0, operation.getExtensions().size());
}

@Test
void testConfiguredExcludeProfile() {
OpenApiConfig config = new OpenApiConfig() {
@Override
public Set<String> getScanExcludeProfiles() {
return Collections.singleton("external");
}
};

OperationImpl operation = new OperationImpl();

boolean result = AbstractAnnotationScanner.processProfiles(config, operation);
assertTrue(result);

operation.setExtensions(Collections.singletonMap("x-smallrye-profile-external", ""));
result = AbstractAnnotationScanner.processProfiles(config, operation);

assertFalse(result);
assertEquals(0, operation.getExtensions().size());
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -458,6 +458,10 @@ private void processResourceMethod(final AnnotationScannerContext context,
// Now set the operation on the PathItem as appropriate based on the Http method type
setOperationOnPathItem(methodType, pathItem, operation);

if (!processProfiles(context.getConfig(), operation)) {
return;
}

// Figure out the path for the operation. This is a combination of the App, Resource, and Method @Path annotations
final String path;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,22 @@
import java.util.UUID;

import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;

import org.eclipse.microprofile.openapi.OASConfig;
import org.eclipse.microprofile.openapi.annotations.extensions.Extension;
import org.eclipse.microprofile.openapi.models.OpenAPI;
import org.eclipse.microprofile.openapi.models.media.Schema;
import org.jboss.jandex.Index;
import org.jboss.jandex.Indexer;
import org.jboss.jandex.Type;
import org.jboss.jandex.Type.Kind;
import org.json.JSONException;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.CsvSource;
Expand Down Expand Up @@ -392,4 +396,51 @@ public void registerCustomSchemas(SchemaRegistry schemaRegistry) {
}

}

/**************************************************************************/

@Test
void testIncludeProfile() {
Index index = indexOf(ProfileResource.class);
OpenApiConfig config = dynamicConfig(OpenApiConstants.SCAN_PROFILES,
"external");

OpenApiAnnotationScanner scanner = new OpenApiAnnotationScanner(config, index);

OpenAPI result = scanner.scan();

Assertions.assertEquals(1, result.getPaths().getPathItems().size());
Assertions.assertTrue(result.getPaths().getPathItems().containsKey("/profile/{id}"));
}

@Test
void testExcludeProfile() {
Index index = indexOf(ProfileResource.class);
OpenApiConfig config = dynamicConfig(OpenApiConstants.SCAN_EXCLUDE_PROFILES,
"external");

OpenApiAnnotationScanner scanner = new OpenApiAnnotationScanner(config, index);

OpenAPI result = scanner.scan();

Assertions.assertEquals(1, result.getPaths().getPathItems().size());
Assertions.assertTrue(result.getPaths().getPathItems().containsKey("/profile"));
}

@Path("/profile")
static class ProfileResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
public String read() {
return "";
}

@Path("{id}")
@POST
@Produces(MediaType.APPLICATION_JSON)
@Extension(name = "x-smallrye-profile-external", value = "")
public String create(@PathParam("id") Long id) {
return "";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,10 @@ private void processControllerMethod(final AnnotationScannerContext context,
// Now set the operation on the PathItem as appropriate based on the Http method type
setOperationOnPathItem(methodType, pathItem, operation);

if (!processProfiles(context.getConfig(), operation)) {
return;
}

// Figure out the path for the operation. This is a combination of the App, Resource, and Method @Path annotations
String path = super.makePath(params.getOperationPath());

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,10 @@ private void processRouteMethod(final AnnotationScannerContext context,
// Now set the operation on the PathItem as appropriate based on the Http method type
setOperationOnPathItem(methodType, pathItem, operation);

if (!processProfiles(context.getConfig(), operation)) {
return;
}

// Figure out the path for the operation. This is a combination of the App, Resource, and Method @Path annotations
String path = super.makePath(params.getOperationPath());

Expand Down
Loading

0 comments on commit b6c1361

Please sign in to comment.