Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
7077014
Test with
a-d Feb 25, 2025
5e50752
Revert changes
a-d Feb 25, 2025
592a389
Add fix
a-d Feb 25, 2025
0076de5
Add test
a-d Feb 25, 2025
ed8532a
Format
a-d Feb 26, 2025
5f8c291
Fix PMD
a-d Feb 26, 2025
947ee19
Fix merge
a-d Feb 26, 2025
54fb0b3
Delete datamodel/openapi/openapi-generator/src/test/resources/DataMod…
newtork Feb 26, 2025
9f8eb8a
Update datamodel/openapi/openapi-generator/src/test/java/com/sap/clou…
newtork Feb 26, 2025
b9c0813
Merge remote-tracking branch 'origin/main' into float-array-instead-o…
a-d Feb 27, 2025
a7dd05f
Merge
a-d Feb 27, 2025
4b51e58
Merge remote-tracking branch 'origin/float-array-instead-of-bigdecima…
a-d Feb 27, 2025
2b502a1
Adapting equals and hashcode generation for float[] (#737)
rpanackal Feb 28, 2025
d27fa30
Move out the custom gen feature login
rpanackal Feb 28, 2025
9966ed8
change access to package private
rpanackal Feb 28, 2025
fd64019
service loader pattern, visitor like pattern
a-d Feb 28, 2025
c6ea658
PoC
a-d Feb 28, 2025
693f5d0
Refine chained strategy pattern
a-d Mar 3, 2025
15919d9
Fix naming; add Javadoc
a-d Mar 3, 2025
83276ee
Fix log level
a-d Mar 4, 2025
9dcd3d4
Merge remote-tracking branch 'origin/main' into openapi/partial-gener…
a-d Mar 4, 2025
2b2ad23
Fix merge; Move files
a-d Mar 4, 2025
1fb0561
Fix PMD, re-order methods
a-d Mar 4, 2025
9118f7e
Minor format improvement
a-d Mar 4, 2025
a912116
Fix checkstyle
a-d Mar 4, 2025
231f62b
Fix PMD
a-d Mar 4, 2025
85bdc58
Merge remote-tracking branch 'origin/main' into openapi/partial-gener…
a-d Mar 10, 2025
2cb2e39
Revert refactoring
a-d Mar 10, 2025
a72b09d
Fix PMD
a-d Mar 10, 2025
d354645
Employ feedback
a-d Mar 11, 2025
4c24133
Fix case of missing schema when attempting to remove property
a-d Mar 11, 2025
3f591b1
Add test case BEFORE
a-d Mar 11, 2025
dc7f90c
Update test case AFTER
a-d Mar 11, 2025
791c124
Format
a-d Mar 11, 2025
9124c26
Add comments; Minor code style improvement
a-d Mar 11, 2025
72d8343
Merge remote-tracking branch 'origin/main' into openapi/partial-gener…
a-d Mar 11, 2025
3ecafe3
Null check, minor simplification
a-d Mar 12, 2025
2bf64ff
Add test case BEFORE
a-d Mar 12, 2025
f7da471
Add test case AFTER
a-d Mar 12, 2025
68f86d6
Fix assertion
a-d Mar 12, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
package com.sap.cloud.sdk.datamodel.openapi.generator;

import static com.sap.cloud.sdk.datamodel.openapi.generator.GeneratorCustomProperties.FIX_REDUNDANT_IS_BOOLEAN_PREFIX;
import static com.sap.cloud.sdk.datamodel.openapi.generator.GeneratorCustomProperties.FIX_REMOVE_UNUSED_COMPONENTS;
import static com.sap.cloud.sdk.datamodel.openapi.generator.GeneratorCustomProperties.USE_EXCLUDE_PATHS;
import static com.sap.cloud.sdk.datamodel.openapi.generator.GeneratorCustomProperties.USE_EXCLUDE_PROPERTIES;
import static com.sap.cloud.sdk.datamodel.openapi.generator.GeneratorCustomProperties.USE_FLOAT_ARRAYS;
import static com.sap.cloud.sdk.datamodel.openapi.generator.GeneratorCustomProperties.USE_ONE_OF_CREATORS;

import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
Expand All @@ -23,9 +29,11 @@

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

import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.media.Schema;
import lombok.extern.slf4j.Slf4j;

@SuppressWarnings( "PMD.TooManyStaticImports" )
@Slf4j
class CustomJavaClientCodegen extends JavaClientCodegen
{
Expand All @@ -38,6 +46,33 @@ public CustomJavaClientCodegen( @Nonnull final GenerationConfiguration config )
this.config = config;
}

@Override
public void preprocessOpenAPI( @Nonnull final OpenAPI openAPI )
{
if( USE_EXCLUDE_PROPERTIES.isEnabled(config) ) {
final String[] exclusions = USE_EXCLUDE_PROPERTIES.getValue(config).trim().split("[,\\s]+");
for( final String exclusion : exclusions ) {
final String[] split = exclusion.split("\\.", 2);
preprocessRemoveProperty(openAPI, split[0], split[1]);
}
}

if( USE_EXCLUDE_PATHS.isEnabled(config) ) {
final String[] exclusions = USE_EXCLUDE_PATHS.getValue(config).trim().split("[,\\s]+");
for( final String exclusion : exclusions ) {
if( !openAPI.getPaths().keySet().remove(exclusion) ) {
log.error("Could not remove path {}", exclusion);
}
}
}

super.preprocessOpenAPI(openAPI);

if( FIX_REMOVE_UNUSED_COMPONENTS.isEnabled(config) ) {
preprocessRemoveRedundancies(openAPI);
}
}

@Override
protected
void
Expand Down Expand Up @@ -104,6 +139,124 @@ protected void updateModelForComposedSchema(
}
}

/**
* Remove property from specification.
*
* @param openAPI
* The OpenAPI specification to update.
* @param schemaName
* The name of the schema to update.
* @param propertyName
* The name of the property to remove.
*/
@SuppressWarnings( { "rawtypes", "unchecked", "ReplaceInefficientStreamCount" } )
private void preprocessRemoveProperty(
@Nonnull final OpenAPI openAPI,
@Nonnull final String schemaName,
@Nonnull final String propertyName )
{
final var schema = openAPI.getComponents().getSchemas().get(schemaName);
if( schema == null ) {
log.error("Could not find schema {} to remove property {} from.", schemaName, propertyName);
return;
}
boolean removed = false;

final Predicate<Schema> remove =
s -> s != null && s.getProperties() != null && s.getProperties().remove(propertyName) != null;
final var schemasQueued = new LinkedList<Schema>();
final var schemasDone = new HashSet<Schema>();
schemasQueued.add(schema);

while( !schemasQueued.isEmpty() ) {
final var s = schemasQueued.remove();
if( s == null || !schemasDone.add(s) ) {
continue;
}
// check removal of direct schema property
removed |= remove.test(s);

// check for allOf, anyOf, oneOf
for( final List<Schema> list : Arrays.asList(s.getAllOf(), s.getAnyOf(), s.getOneOf()) ) {
Copy link
Contributor

@CharlesDuboisSAP CharlesDuboisSAP Mar 12, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How could you pass an opportunity for recursion? (the while)
Also the allOf case is not tested.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How could you pass an opportunity for recursion? (the while)

Recursion is a poisoned fruit. Looks nice on the outside, but makes you sick when problems appear.

Also the allOf case is not tested.

I added a test case: 2bf64ff -> f7da471

if( list != null ) {
schemasQueued.addAll(list);
}
}
}
if( !removed ) {
log.error("Could not remove property {} from schema {}.", propertyName, schemaName);
}
}

/**
* Remove unused schema components.
*
* @param openAPI
* The OpenAPI specification to update.
*/
@SuppressWarnings( { "rawtypes", "unchecked" } )
private void preprocessRemoveRedundancies( @Nonnull final OpenAPI openAPI )
{
final var queue = new LinkedList<Schema>();
final var done = new HashSet<Schema>();
final var refs = new LinkedHashSet<String>();
final var pattern = Pattern.compile("\\$ref: #/components/schemas/(\\w+)");

// find and queue schemas nested in paths
for( final var path : openAPI.getPaths().values() ) {
final var m = pattern.matcher(path.toString());
while( m.find() ) {
final var name = m.group(1);
final var schema = openAPI.getComponents().getSchemas().get(name);
queue.add(schema);
refs.add(m.group(0).split(" ")[1]);
}
}

while( !queue.isEmpty() ) {
final var s = queue.remove();
if( s == null || !done.add(s) ) {
continue;
}

// check for $ref attribute
final var ref = s.get$ref();
if( ref != null ) {
refs.add(ref);
final var refName = ref.substring(ref.lastIndexOf('/') + 1);
queue.add(openAPI.getComponents().getSchemas().get(refName));
}

// check for direct properties
if( s.getProperties() != null ) {
for( final var s1 : s.getProperties().values() ) {
queue.add((Schema) s1);
}
}

// check for array items
if( s.getItems() != null ) {
queue.add(s.getItems());
}

// check for allOf, anyOf, oneOf
for( final List<Schema> list : Arrays.asList(s.getAllOf(), s.getAnyOf(), s.getOneOf()) ) {
if( list != null ) {
queue.addAll(list);
}
}
}

// remove all schemas that have not been marked "used"
openAPI.getComponents().getSchemas().keySet().removeIf(schema -> {
if( !refs.contains("#/components/schemas/" + schema) ) {
log.info("Removing unused schema {}", schema);
return true;
}
return false;
});
}

/**
* Use JsonCreator for interface sub-types in case there are any primitives.
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,22 @@ enum GeneratorCustomProperties
/**
* Use float arrays instead of big-decimal lists.
*/
USE_FLOAT_ARRAYS("useFloatArrays", "false");
USE_FLOAT_ARRAYS("useFloatArrays", "false"),

/**
* Exclude generation of properties, e.g. `schemaName1.propertyNameA, schemaName2.propertyNameB`.
*/
USE_EXCLUDE_PROPERTIES("excludeProperties", "false"),

/**
* Exclude generation of APIs that match a provided path, e.g. `/api/v1/health, /deployments/{id}/completions`.
*/
USE_EXCLUDE_PATHS("excludePaths", "false"),

/**
* Remove schema components that are unused, before generating them.
*/
FIX_REMOVE_UNUSED_COMPONENTS("removeUnusedComponents", "false");

private final String key;
private final String defaultValue;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.sap.cloud.sdk.datamodel.openapi.generator;

import static java.util.Map.entry;

import static org.assertj.core.api.Assertions.assertThat;

import java.nio.file.Files;
Expand Down Expand Up @@ -55,6 +57,20 @@ private enum TestCase
true,
6,
Map.of()),
PARTIAL_GENERATION(
"partial-generation",
"sodastore.json",
"com.sap.cloud.sdk.services.builder.api",
"com.sap.cloud.sdk.services.builder.model",
ApiMaturity.RELEASED,
true,
true,
4,
Map
.ofEntries(
entry("excludePaths", "/sodas,/foobar/{baz}"),
entry("excludeProperties", "Foo.bar,Soda.embedding,Soda.flavor,UpdateSoda.flavor,SodaWithFoo.foo"),
entry("removeUnusedComponents", "true"))),
INPUT_SPEC_WITH_UPPERCASE_FILE_EXTENSION(
"input-spec-with-uppercase-file-extension",
"sodastore.JSON",
Expand Down Expand Up @@ -207,7 +223,7 @@ void generateDataModelForComparison( final TestCase testCase )
testCase.additionalProperties.forEach(generationConfiguration::additionalProperty);

GenerationConfiguration build = generationConfiguration.build();
new DataModelGenerator().generateDataModel(build);
new DataModelGenerator().generateDataModel(build).onFailure(Throwable::printStackTrace);
}

private static Path getInputDirectory( final TestCase testCase )
Expand Down
Loading
Loading