Skip to content

Commit 1a22085

Browse files
committed
Process profiles in filter phase
Signed-off-by: Michael Edgar <michael@xlate.io>
1 parent b696ab5 commit 1a22085

9 files changed

Lines changed: 184 additions & 41 deletions

File tree

README.adoc

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,7 +169,9 @@ Set this boolean value to enable or disable the expansion of YAML aliases when l
169169
mp.openapi.extensions.smallrye.scan.profiles
170170
mp.openapi.extensions.smallrye.scan.exclude.profiles
171171
----
172-
These properties are used together with the <<user-content-x-smallrye-profile,x-smallrye-profile>> extension to provide a way to limit which REST endpoints and associated resources are included in the OpenAPI model.
172+
These properties are used together with the <<user-content-x-smallrye-profile,x-smallrye-profile>> extension to provide a way to limit which REST endpoints and associated resources are included in the OpenAPI model. Path items where all operations have been excluded will also be removed from the model.
173+
174+
Note: Profiles will be processed after the OpenAPI model is fully merged from the static YAML/JSON file, the `OASReader` output, and annotation scanning but prior to any user-provided filters. This means that user filters will receive the model after non-included profiles have been removed.
173175

174176
=== Extensions
175177

core/src/main/java/io/smallrye/openapi/api/OpenApiDocument.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import io.smallrye.openapi.api.util.UnusedSchemaFilter;
1616
import io.smallrye.openapi.model.Extensions;
1717
import io.smallrye.openapi.model.ReferenceType;
18+
import io.smallrye.openapi.runtime.util.ProfileFilter;
1819

1920
/**
2021
* Holds the final OpenAPI document produced during the startup of the app.
@@ -190,6 +191,10 @@ private OpenAPI filterModel(OpenAPI model) {
190191
return model;
191192
}
192193

194+
if (!config.getScanProfiles().isEmpty() || !config.getScanExcludeProfiles().isEmpty()) {
195+
model = FilterUtil.applyFilter(new ProfileFilter(config), model);
196+
}
197+
193198
if (config.removeUnusedSchemas()) {
194199
model = FilterUtil.applyFilter(new UnusedSchemaFilter(), model);
195200
}

core/src/main/java/io/smallrye/openapi/runtime/scanner/spi/AbstractAnnotationScanner.java

Lines changed: 4 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -104,30 +104,15 @@ protected static String createPathFromSegments(String... segments) {
104104
}
105105

106106
/**
107-
* Checks if the given extensible contains profiles, and if the extensible should be included in the final openapi document.
108-
* Any extension containing a profile is removed from the extensible.
109-
* inclusion is then calculated based on all collected profiles.
107+
* Checks if the given extensible contains profiles, and if the extensible should be included in the final OpenAPI document.
108+
* Inclusion is calculated based on all collected profiles.
110109
*
111110
* @param config current config
112111
* @param extensible the extensible to check for profiles
113-
* @return true, if the given extensible should be included in the final openapi document, otherwise false
112+
* @return true, if the given extensible should be included in the final OpenAPI document, otherwise false
114113
*/
115114
protected static boolean processProfiles(OpenApiConfig config, Extensible<?> extensible) {
116-
Set<String> profiles = Extensions.getProfiles(extensible);
117-
Extensions.removeProfiles(extensible);
118-
return profileIncluded(config, profiles);
119-
}
120-
121-
private static boolean profileIncluded(OpenApiConfig config, Set<String> profiles) {
122-
if (!config.getScanExcludeProfiles().isEmpty()) {
123-
return config.getScanExcludeProfiles().stream().noneMatch(profiles::contains);
124-
}
125-
126-
if (config.getScanProfiles().isEmpty()) {
127-
return true;
128-
}
129-
130-
return config.getScanProfiles().stream().anyMatch(profiles::contains);
115+
return Extensions.includedProfile(extensible, config.getScanProfiles(), config.getScanExcludeProfiles());
131116
}
132117

133118
@Override
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
package io.smallrye.openapi.runtime.util;
2+
3+
import java.util.Set;
4+
5+
import org.eclipse.microprofile.openapi.OASFilter;
6+
import org.eclipse.microprofile.openapi.models.Operation;
7+
import org.eclipse.microprofile.openapi.models.PathItem;
8+
import org.eclipse.microprofile.openapi.models.PathItem.HttpMethod;
9+
10+
import io.smallrye.openapi.api.OpenApiConfig;
11+
import io.smallrye.openapi.model.Extensions;
12+
13+
public class ProfileFilter implements OASFilter {
14+
15+
private final Set<String> included;
16+
private final Set<String> excluded;
17+
18+
public ProfileFilter(OpenApiConfig config) {
19+
included = config.getScanProfiles();
20+
excluded = config.getScanExcludeProfiles();
21+
}
22+
23+
@Override
24+
public PathItem filterPathItem(PathItem pathItem) {
25+
boolean operationExcluded = false;
26+
27+
for (HttpMethod method : Set.copyOf(pathItem.getOperations().keySet())) {
28+
Operation o = pathItem.getOperations().get(method);
29+
30+
if (!Extensions.includedProfile(o, included, excluded)) {
31+
operationExcluded = true;
32+
pathItem.setOperation(method, null);
33+
}
34+
}
35+
36+
if (operationExcluded && pathItem.getOperations().isEmpty()) {
37+
return null;
38+
}
39+
40+
return pathItem;
41+
}
42+
}

core/src/test/java/io/smallrye/openapi/runtime/scanner/spi/AbstractAnnotationScannerTest.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,6 @@ void testNoConfiguredProfile() {
121121
boolean result = AbstractAnnotationScanner.processProfiles(config, operation);
122122

123123
assertTrue(result);
124-
assertEquals(0, operation.getExtensions().size());
125124
}
126125

127126
@Test
@@ -142,7 +141,6 @@ public Set<String> getScanProfiles() {
142141
result = AbstractAnnotationScanner.processProfiles(config, operation);
143142

144143
assertTrue(result);
145-
assertEquals(0, operation.getExtensions().size());
146144
}
147145

148146
@Test
@@ -163,7 +161,6 @@ public Set<String> getScanExcludeProfiles() {
163161
result = AbstractAnnotationScanner.processProfiles(config, operation);
164162

165163
assertFalse(result);
166-
assertEquals(0, operation.getExtensions().size());
167164
}
168165

169166
@ParameterizedTest

extension-jaxrs/src/main/java/io/smallrye/openapi/jaxrs/JaxRsAnnotationScanner.java

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -496,6 +496,19 @@ private void processResourceMethod(final ClassInfo resourceClass,
496496
// Process tags - @Tag and @Tags annotations combines with the resource tags we've already found (passed in)
497497
processOperationTags(context, method, context.getOpenApi(), resourceTags, operation);
498498

499+
// Process @Extension annotations
500+
processExtensions(context, method, operation);
501+
502+
if (!processProfiles(context.getConfig(), operation)) {
503+
// Any operations not included by a profile (if configured) will also be filtered out
504+
// later during the filter stage so that profile included/exclusion takes into account
505+
// the static file and the OASReader models. This short-circuit is left in as an
506+
// optimization to avoid additional scanning when the operation would have been
507+
// excluded anyway. As a result, profile configurations are built-time only for Quarkus
508+
// applications.
509+
return;
510+
}
511+
499512
// Process @Parameter annotations.
500513
List<Parameter> operationParams = params.getOperationParameters();
501514
operation.setParameters(operationParams);
@@ -524,19 +537,12 @@ private void processResourceMethod(final ClassInfo resourceClass,
524537
// Process @Server annotations
525538
processServerAnnotation(context, method, operation);
526539

527-
// Process @Extension annotations
528-
processExtensions(context, method, operation);
529-
530540
// Process Security Roles
531541
context.getJavaSecurityProcessor().processSecurityRoles(method, operation);
532542

533543
// Now set the operation on the PathItem as appropriate based on the Http method type
534544
pathItem.setOperation(methodType, operation);
535545

536-
if (!processProfiles(context.getConfig(), operation)) {
537-
return;
538-
}
539-
540546
// When processing a sub-resource tree, ignore any @Path information from the current class
541547
List<String> operationPaths = this.subResourceStack.isEmpty() ? params.getFullOperationPaths()
542548
: params.getOperationPaths();
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package io.smallrye.openapi.runtime.scanner;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertTrue;
5+
6+
import jakarta.ws.rs.GET;
7+
import jakarta.ws.rs.Path;
8+
9+
import org.eclipse.microprofile.openapi.annotations.extensions.Extension;
10+
import org.eclipse.microprofile.openapi.models.OpenAPI;
11+
import org.jboss.jandex.Index;
12+
import org.junit.jupiter.api.Test;
13+
14+
import io.smallrye.openapi.api.SmallRyeOASConfig;
15+
import io.smallrye.openapi.api.SmallRyeOpenAPI;
16+
17+
class ProfileSelectionWithStaticModelTest {
18+
19+
@Test
20+
void testStaticModelOperationExcluded() throws Exception {
21+
@Path("/api")
22+
class MyResource {
23+
@Path("/users")
24+
@GET
25+
@Extension(name = "x-smallrye-profile-user", value = "")
26+
public void listUsers() {
27+
}
28+
}
29+
30+
SmallRyeOpenAPI result;
31+
32+
try {
33+
System.setProperty(SmallRyeOASConfig.SCAN_PROFILES, "user");
34+
result = SmallRyeOpenAPI.builder()
35+
.enableModelReader(false)
36+
.enableStandardFilter(false)
37+
.enableStandardStaticFiles(false)
38+
.withCustomStaticFile(() -> {
39+
return getClass()
40+
.getClassLoader()
41+
.getResourceAsStream(
42+
"io/smallrye/openapi/runtime/scanner/static/profile-selection-static-model.yaml");
43+
})
44+
.enableAnnotationScan(true)
45+
.withIndex(Index.of(MyResource.class))
46+
.build();
47+
} finally {
48+
System.clearProperty(SmallRyeOASConfig.SCAN_PROFILES);
49+
}
50+
51+
OpenAPI model = result.model();
52+
var paths = model.getPaths().getPathItems();
53+
assertEquals(1, paths.size());
54+
assertTrue(paths.containsKey("/api/users"));
55+
}
56+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
openapi: 3.0.3
2+
info:
3+
title: echo
4+
version: '1.0.0'
5+
description: ""
6+
paths:
7+
/echo:
8+
post:
9+
summary: Echo
10+
operationId: echo
11+
requestBody:
12+
content:
13+
application/json:
14+
schema:
15+
$ref: "#/components/schemas/Message"
16+
responses:
17+
"200":
18+
description: OK
19+
content:
20+
application/json:
21+
schema:
22+
$ref: '#/components/schemas/Echo'
23+
components:
24+
schemas:
25+
Echo:
26+
type: object
27+
properties:
28+
echo:
29+
type: string
30+
Message:
31+
type: object
32+
properties:
33+
message:
34+
type: string

model/src/main/java/io/smallrye/openapi/model/Extensions.java

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,8 @@ private static <T> void set(Extensible<?> extensible, String name, T value) {
8383
}
8484
}
8585

86+
// Profiles
87+
8688
public static Set<String> getProfiles(Extensible<?> extensible) {
8789
Set<String> profiles = new HashSet<>(2);
8890

@@ -95,6 +97,20 @@ public static Set<String> getProfiles(Extensible<?> extensible) {
9597
return profiles;
9698
}
9799

100+
public static boolean includedProfile(Extensible<?> extensible, Set<String> included, Set<String> excluded) {
101+
Set<String> profiles = getProfiles(extensible);
102+
103+
if (!excluded.isEmpty()) {
104+
return excluded.stream().noneMatch(profiles::contains);
105+
}
106+
107+
if (included.isEmpty()) {
108+
return true;
109+
}
110+
111+
return included.stream().anyMatch(profiles::contains);
112+
}
113+
98114
public static void removeProfiles(Extensible<?> extensible) {
99115
for (String name : extensionNames(extensible)) {
100116
if (name.startsWith(EXT_PROFILE_PREFIX)) {
@@ -103,7 +119,7 @@ public static void removeProfiles(Extensible<?> extensible) {
103119
}
104120
}
105121

106-
//////
122+
// Name
107123

108124
public static String getName(Extensible<?> extensible) {
109125
return get(extensible, PRIVATE_EXT_PREFIX + "name", String.class);
@@ -113,7 +129,7 @@ public static void setName(Extensible<?> extensible, String name) {
113129
set(extensible, PRIVATE_EXT_PREFIX + "name", name);
114130
}
115131

116-
//////
132+
// Hidden
117133

118134
public static boolean isHidden(Extensible<?> extensible) {
119135
return Boolean.TRUE.equals(get(extensible, PRIVATE_EXT_PREFIX + "hidden", Boolean.class));
@@ -123,7 +139,7 @@ public static void setHidden(Extensible<?> extensible, Boolean hidden) {
123139
set(extensible, PRIVATE_EXT_PREFIX + "hidden", hidden);
124140
}
125141

126-
//////
142+
// Response-Code
127143

128144
public static String getResponseCode(APIResponse response) {
129145
return get(response, PRIVATE_EXT_PREFIX + "response-code", String.class);
@@ -133,7 +149,7 @@ public static void setResponseCode(APIResponse response, String responseCode) {
133149
set(response, PRIVATE_EXT_PREFIX + "response-code", responseCode);
134150
}
135151

136-
//////
152+
// Required-Default
137153

138154
public static Boolean getRequiredDefault(RequestBody requestBody) {
139155
return get(requestBody, PRIVATE_EXT_PREFIX + "required-default", Boolean.class);
@@ -151,7 +167,7 @@ public static void setRequiredDefault(RequestBody requestBody, Boolean requiredD
151167
set(requestBody, PRIVATE_EXT_PREFIX + "required-default", requiredDefault);
152168
}
153169

154-
//////
170+
// Is-Required-Set
155171

156172
/**
157173
* Returns whether {@link RequestBody#setRequired(Boolean)} has been called on a request body.
@@ -167,7 +183,7 @@ public static void setIsRequiredSet(RequestBody requestBody, Boolean requiredDef
167183
set(requestBody, PRIVATE_EXT_PREFIX + "is-required-set", requiredDefault);
168184
}
169185

170-
//////
186+
// Param-Ref
171187

172188
/**
173189
* Implementation specific, set a reference to the Java method parameter, so that we can bind back to it later if needed
@@ -183,7 +199,7 @@ public static void setParamRef(Parameter parameter, AnnotationTarget source) {
183199
set(parameter, PRIVATE_EXT_PREFIX + "param-ref", ref);
184200
}
185201

186-
//////
202+
// Method-Ref
187203

188204
/**
189205
* Implementation specific, set a reference to the Java method, so that we can bind back to it later if needed
@@ -199,7 +215,7 @@ public static void setMethodRef(Operation operation, ClassInfo resourceClass, Me
199215
set(operation, PRIVATE_EXT_PREFIX + "method-ref", ref);
200216
}
201217

202-
//////
218+
// Schema-Type-Observers
203219

204220
@SuppressWarnings("unchecked")
205221
public static List<Schema> getTypeObservers(Schema schema) {
@@ -210,7 +226,7 @@ public static void setTypeObservers(Schema schema, List<Schema> observers) {
210226
set(schema, PRIVATE_EXT_PREFIX + "schema-type-observers", observers);
211227
}
212228

213-
//////
229+
// Private extension accessors/mutator
214230

215231
public static boolean isPrivateExtension(String name) {
216232
return name.startsWith(PRIVATE_EXT_PREFIX);
@@ -224,7 +240,7 @@ public static void setPrivateExtension(Extensible<?> extensible, String name, Ob
224240
extensible.addExtension(PRIVATE_EXT_PREFIX + name, value);
225241
}
226242

227-
///////
243+
// Directives
228244

229245
@SuppressWarnings("unchecked")
230246
public static Collection<String> getDirectives(Extensible<?> extensible) {
@@ -239,7 +255,7 @@ public static Collection<String> getDirectives(Extensible<?> extensible) {
239255
return Collections.emptySet();
240256
}
241257

242-
///////
258+
// Reference helpers
243259

244260
private static String createUniqueAnnotationTargetRef(AnnotationTarget annotationTarget) {
245261
switch (annotationTarget.kind()) {

0 commit comments

Comments
 (0)