Skip to content

Commit ce30d56

Browse files
newtorka-drpanackal
authored
feat: [OpenAPI] Exclusion of paths and properties (#745)
Co-authored-by: Alexander Dümont <[email protected]> Co-authored-by: Roshin Rajan Panackal <[email protected]> Co-authored-by: Roshin Rajan Panackal <[email protected]>
1 parent 144c7fd commit ce30d56

File tree

8 files changed

+1579
-2
lines changed

8 files changed

+1579
-2
lines changed

datamodel/openapi/openapi-generator/src/main/java/com/sap/cloud/sdk/datamodel/openapi/generator/CustomJavaClientCodegen.java

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
package com.sap.cloud.sdk.datamodel.openapi.generator;
22

33
import static com.sap.cloud.sdk.datamodel.openapi.generator.GeneratorCustomProperties.FIX_REDUNDANT_IS_BOOLEAN_PREFIX;
4+
import static com.sap.cloud.sdk.datamodel.openapi.generator.GeneratorCustomProperties.FIX_REMOVE_UNUSED_COMPONENTS;
5+
import static com.sap.cloud.sdk.datamodel.openapi.generator.GeneratorCustomProperties.USE_EXCLUDE_PATHS;
6+
import static com.sap.cloud.sdk.datamodel.openapi.generator.GeneratorCustomProperties.USE_EXCLUDE_PROPERTIES;
47
import static com.sap.cloud.sdk.datamodel.openapi.generator.GeneratorCustomProperties.USE_FLOAT_ARRAYS;
58
import static com.sap.cloud.sdk.datamodel.openapi.generator.GeneratorCustomProperties.USE_ONE_OF_CREATORS;
69

10+
import java.util.Arrays;
711
import java.util.HashSet;
12+
import java.util.LinkedHashSet;
13+
import java.util.LinkedList;
814
import java.util.List;
915
import java.util.Map;
1016
import java.util.Set;
@@ -23,9 +29,11 @@
2329

2430
import com.sap.cloud.sdk.datamodel.openapi.generator.model.GenerationConfiguration;
2531

32+
import io.swagger.v3.oas.models.OpenAPI;
2633
import io.swagger.v3.oas.models.media.Schema;
2734
import lombok.extern.slf4j.Slf4j;
2835

36+
@SuppressWarnings( "PMD.TooManyStaticImports" )
2937
@Slf4j
3038
class CustomJavaClientCodegen extends JavaClientCodegen
3139
{
@@ -38,6 +46,33 @@ public CustomJavaClientCodegen( @Nonnull final GenerationConfiguration config )
3846
this.config = config;
3947
}
4048

49+
@Override
50+
public void preprocessOpenAPI( @Nonnull final OpenAPI openAPI )
51+
{
52+
if( USE_EXCLUDE_PROPERTIES.isEnabled(config) ) {
53+
final String[] exclusions = USE_EXCLUDE_PROPERTIES.getValue(config).trim().split("[,\\s]+");
54+
for( final String exclusion : exclusions ) {
55+
final String[] split = exclusion.split("\\.", 2);
56+
preprocessRemoveProperty(openAPI, split[0], split[1]);
57+
}
58+
}
59+
60+
if( USE_EXCLUDE_PATHS.isEnabled(config) ) {
61+
final String[] exclusions = USE_EXCLUDE_PATHS.getValue(config).trim().split("[,\\s]+");
62+
for( final String exclusion : exclusions ) {
63+
if( !openAPI.getPaths().keySet().remove(exclusion) ) {
64+
log.error("Could not remove path {}", exclusion);
65+
}
66+
}
67+
}
68+
69+
super.preprocessOpenAPI(openAPI);
70+
71+
if( FIX_REMOVE_UNUSED_COMPONENTS.isEnabled(config) ) {
72+
preprocessRemoveRedundancies(openAPI);
73+
}
74+
}
75+
4176
@Override
4277
protected
4378
void
@@ -104,6 +139,124 @@ protected void updateModelForComposedSchema(
104139
}
105140
}
106141

142+
/**
143+
* Remove property from specification.
144+
*
145+
* @param openAPI
146+
* The OpenAPI specification to update.
147+
* @param schemaName
148+
* The name of the schema to update.
149+
* @param propertyName
150+
* The name of the property to remove.
151+
*/
152+
@SuppressWarnings( { "rawtypes", "unchecked", "ReplaceInefficientStreamCount" } )
153+
private void preprocessRemoveProperty(
154+
@Nonnull final OpenAPI openAPI,
155+
@Nonnull final String schemaName,
156+
@Nonnull final String propertyName )
157+
{
158+
final var schema = openAPI.getComponents().getSchemas().get(schemaName);
159+
if( schema == null ) {
160+
log.error("Could not find schema {} to remove property {} from.", schemaName, propertyName);
161+
return;
162+
}
163+
boolean removed = false;
164+
165+
final Predicate<Schema> remove =
166+
s -> s != null && s.getProperties() != null && s.getProperties().remove(propertyName) != null;
167+
final var schemasQueued = new LinkedList<Schema>();
168+
final var schemasDone = new HashSet<Schema>();
169+
schemasQueued.add(schema);
170+
171+
while( !schemasQueued.isEmpty() ) {
172+
final var s = schemasQueued.remove();
173+
if( s == null || !schemasDone.add(s) ) {
174+
continue;
175+
}
176+
// check removal of direct schema property
177+
removed |= remove.test(s);
178+
179+
// check for allOf, anyOf, oneOf
180+
for( final List<Schema> list : Arrays.asList(s.getAllOf(), s.getAnyOf(), s.getOneOf()) ) {
181+
if( list != null ) {
182+
schemasQueued.addAll(list);
183+
}
184+
}
185+
}
186+
if( !removed ) {
187+
log.error("Could not remove property {} from schema {}.", propertyName, schemaName);
188+
}
189+
}
190+
191+
/**
192+
* Remove unused schema components.
193+
*
194+
* @param openAPI
195+
* The OpenAPI specification to update.
196+
*/
197+
@SuppressWarnings( { "rawtypes", "unchecked" } )
198+
private void preprocessRemoveRedundancies( @Nonnull final OpenAPI openAPI )
199+
{
200+
final var queue = new LinkedList<Schema>();
201+
final var done = new HashSet<Schema>();
202+
final var refs = new LinkedHashSet<String>();
203+
final var pattern = Pattern.compile("\\$ref: #/components/schemas/(\\w+)");
204+
205+
// find and queue schemas nested in paths
206+
for( final var path : openAPI.getPaths().values() ) {
207+
final var m = pattern.matcher(path.toString());
208+
while( m.find() ) {
209+
final var name = m.group(1);
210+
final var schema = openAPI.getComponents().getSchemas().get(name);
211+
queue.add(schema);
212+
refs.add(m.group(0).split(" ")[1]);
213+
}
214+
}
215+
216+
while( !queue.isEmpty() ) {
217+
final var s = queue.remove();
218+
if( s == null || !done.add(s) ) {
219+
continue;
220+
}
221+
222+
// check for $ref attribute
223+
final var ref = s.get$ref();
224+
if( ref != null ) {
225+
refs.add(ref);
226+
final var refName = ref.substring(ref.lastIndexOf('/') + 1);
227+
queue.add(openAPI.getComponents().getSchemas().get(refName));
228+
}
229+
230+
// check for direct properties
231+
if( s.getProperties() != null ) {
232+
for( final var s1 : s.getProperties().values() ) {
233+
queue.add((Schema) s1);
234+
}
235+
}
236+
237+
// check for array items
238+
if( s.getItems() != null ) {
239+
queue.add(s.getItems());
240+
}
241+
242+
// check for allOf, anyOf, oneOf
243+
for( final List<Schema> list : Arrays.asList(s.getAllOf(), s.getAnyOf(), s.getOneOf()) ) {
244+
if( list != null ) {
245+
queue.addAll(list);
246+
}
247+
}
248+
}
249+
250+
// remove all schemas that have not been marked "used"
251+
openAPI.getComponents().getSchemas().keySet().removeIf(schema -> {
252+
if( !refs.contains("#/components/schemas/" + schema) ) {
253+
log.info("Removing unused schema {}", schema);
254+
return true;
255+
}
256+
return false;
257+
});
258+
}
259+
107260
/**
108261
* Use JsonCreator for interface sub-types in case there are any primitives.
109262
*

datamodel/openapi/openapi-generator/src/main/java/com/sap/cloud/sdk/datamodel/openapi/generator/GeneratorCustomProperties.java

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,22 @@ enum GeneratorCustomProperties
2525
/**
2626
* Use float arrays instead of big-decimal lists.
2727
*/
28-
USE_FLOAT_ARRAYS("useFloatArrays", "false");
28+
USE_FLOAT_ARRAYS("useFloatArrays", "false"),
29+
30+
/**
31+
* Exclude generation of properties, e.g. `schemaName1.propertyNameA, schemaName2.propertyNameB`.
32+
*/
33+
USE_EXCLUDE_PROPERTIES("excludeProperties", "false"),
34+
35+
/**
36+
* Exclude generation of APIs that match a provided path, e.g. `/api/v1/health, /deployments/{id}/completions`.
37+
*/
38+
USE_EXCLUDE_PATHS("excludePaths", "false"),
39+
40+
/**
41+
* Remove schema components that are unused, before generating them.
42+
*/
43+
FIX_REMOVE_UNUSED_COMPONENTS("removeUnusedComponents", "false");
2944

3045
private final String key;
3146
private final String defaultValue;

datamodel/openapi/openapi-generator/src/test/java/com/sap/cloud/sdk/datamodel/openapi/generator/DataModelGeneratorIntegrationTest.java

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.sap.cloud.sdk.datamodel.openapi.generator;
22

3+
import static java.util.Map.entry;
4+
35
import static org.assertj.core.api.Assertions.assertThat;
46

57
import java.nio.file.Files;
@@ -55,6 +57,20 @@ private enum TestCase
5557
true,
5658
6,
5759
Map.of()),
60+
PARTIAL_GENERATION(
61+
"partial-generation",
62+
"sodastore.json",
63+
"com.sap.cloud.sdk.services.builder.api",
64+
"com.sap.cloud.sdk.services.builder.model",
65+
ApiMaturity.RELEASED,
66+
true,
67+
true,
68+
4,
69+
Map
70+
.ofEntries(
71+
entry("excludePaths", "/sodas,/foobar/{baz}"),
72+
entry("excludeProperties", "Foo.bar,Soda.embedding,Soda.flavor,UpdateSoda.flavor,SodaWithFoo.foo"),
73+
entry("removeUnusedComponents", "true"))),
5874
INPUT_SPEC_WITH_UPPERCASE_FILE_EXTENSION(
5975
"input-spec-with-uppercase-file-extension",
6076
"sodastore.JSON",
@@ -207,7 +223,7 @@ void generateDataModelForComparison( final TestCase testCase )
207223
testCase.additionalProperties.forEach(generationConfiguration::additionalProperty);
208224

209225
GenerationConfiguration build = generationConfiguration.build();
210-
new DataModelGenerator().generateDataModel(build);
226+
new DataModelGenerator().generateDataModel(build).onFailure(Throwable::printStackTrace);
211227
}
212228

213229
private static Path getInputDirectory( final TestCase testCase )

0 commit comments

Comments
 (0)