Skip to content

Commit

Permalink
implementation of enhancement #8
Browse files Browse the repository at this point in the history
  • Loading branch information
bartoszm committed Dec 23, 2022
1 parent daa8652 commit 9e02953
Show file tree
Hide file tree
Showing 16 changed files with 290 additions and 62 deletions.
6 changes: 3 additions & 3 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,9 @@
<groupId>com.amartus.sonata</groupId>
<artifactId>blender</artifactId>
<description>SonataBlendingTool</description>
<version>1.9.3</version>
<version>1.9.4-SNAPSHOT</version>
<properties>
<openapi.generator.version>6.0.1</openapi.generator.version>
<openapi.generator.version>6.2.0</openapi.generator.version>
<jackson.version>2.14.0</jackson.version>
</properties>
<dependencies>
Expand All @@ -51,7 +51,7 @@
<dependency>
<groupId>com.github.rvesse</groupId>
<artifactId>airline</artifactId>
<version>2.8.5</version>
<version>2.9.0</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,28 +18,51 @@

package com.amartus.sonata.blender.impl.postprocess;

import com.amartus.sonata.blender.impl.specifications.FragmentBasedNamingStrategy;
import com.amartus.sonata.blender.impl.specifications.PathBaseNamingStrategy;
import com.amartus.sonata.blender.impl.specifications.ProductSpecificationNamingStrategy;
import com.amartus.sonata.blender.impl.specifications.UrnBasedNamingStrategy;
import io.swagger.v3.oas.models.OpenAPI;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.List;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import static com.amartus.sonata.blender.impl.postprocess.RenameTypesPostprocessor.*;

public class ComposedPostprocessor implements Consumer<OpenAPI> {
private static final Logger log = LoggerFactory.getLogger(ComposedPostprocessor.class);

private List<Consumer<OpenAPI>> postprocessors = List.of(
new RemoveSuperflousTypeDeclarations(),
new RenameTypesPostprocessor(converter()),
new PropertyEnumExternalize(),
new ComposedPropertyToType(),
new SingleEnumToDiscriminatorValue(),
new ConvertOneOfToAllOffInheritance(),
new UpdateDiscriminatorMapping(),
new ConstrainDiscriminatorValueWithEnum()
);


@Override
public void accept(OpenAPI openAPI) {
log.info("Running {} OAS post-processors", postprocessors.size());
for (var p : postprocessors) {
log.debug("Running {}", p.getClass().getSimpleName());
p.accept(openAPI);
}
}
private NameConverter converter() {
var converters= Stream.of(
new UrnBasedNamingStrategy(),
new FragmentBasedNamingStrategy(),
new PathBaseNamingStrategy()
).collect(Collectors.toList());

return input -> converters.stream().flatMap(it -> it.fromText(input).stream()
.map(ProductSpecificationNamingStrategy.NameAndDiscriminator::getName))
.findFirst().orElse(null);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ protected Map.Entry<String, Schema> processProperty(String name, Schema property
if (property instanceof ArraySchema) {
var s = ((ArraySchema) property).getItems();
var converted = convertProperty(name, s);
((ArraySchema) property).setItems(converted.getValue());
property.setItems(converted.getValue());
return Map.entry(converted.getKey(), property);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
*/
package com.amartus.sonata.blender.impl.postprocess;

import com.amartus.sonata.blender.impl.util.Collections;
import com.amartus.sonata.blender.impl.util.OasUtils;
import com.amartus.sonata.blender.impl.util.OasWrapper;
import io.swagger.v3.oas.models.OpenAPI;
Expand Down Expand Up @@ -131,7 +132,7 @@ private Map<String, Set<String>> prepare() {
.map(this::convertToOneOf)
.filter(e -> e.getValue().stream().allMatch(isReference))
.map(e -> convertValues(e, v -> OasUtils.toSchemaName(v.get$ref())))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
.collect(Collections.mapCollector());
}

private Map.Entry<String, Set<Schema>> convertToOneOf(Map.Entry<String, Schema> e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@ protected void process(String type, Schema schema) {
Map<String, Schema> properties = toProperties(schema)
.entrySet().stream()
.map(e -> processProperty(e.getKey(), e.getValue()))

.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
schema.setProperties(properties);
if (schema instanceof ComposedSchema) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,154 @@
package com.amartus.sonata.blender.impl.postprocess;

import com.amartus.sonata.blender.impl.util.OasUtils;
import com.amartus.sonata.blender.impl.util.OasWrapper;
import com.amartus.sonata.blender.impl.util.Pair;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.Operation;
import io.swagger.v3.oas.models.media.ArraySchema;
import io.swagger.v3.oas.models.media.Content;
import io.swagger.v3.oas.models.media.MediaType;
import io.swagger.v3.oas.models.media.Schema;
import io.swagger.v3.oas.models.parameters.RequestBody;
import io.swagger.v3.oas.models.responses.ApiResponse;
import io.swagger.v3.oas.models.responses.ApiResponses;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.*;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;

public class RenameTypesPostprocessor implements Consumer<OpenAPI> {
private static final Logger log = LoggerFactory.getLogger(RenameTypesPostprocessor.class);
public interface NameConverter {
String convert(String input);
}
private Map<String, String> substitutions;
private final NameConverter converter;
public RenameTypesPostprocessor(NameConverter converter) {
this.converter = converter;
}

private static final String extensionName = "x-try-renaming-on";

@Override
public void accept(OpenAPI openAPI) {

var schemas = new OasWrapper(openAPI).schemas();
this.substitutions = schemas.entrySet().stream()
.map(e -> Map.entry(e.getKey(), toName(e.getValue()).orElse(e.getKey())))
.filter(p -> ! p.getKey().equals(p.getValue()))
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
if(substitutions.isEmpty()) return;

if(log.isDebugEnabled()) {
substitutions.forEach((a,b) -> log.debug("Renaming {} to {}", a, b));
}

var newNames = new HashSet(substitutions.values());
if(newNames.size() < substitutions.size()) {
log.warn("Conflicting substitution names. Skipping.");
return;
}
if(schemas.keySet().stream().anyMatch(newNames::contains)) {
log.warn("Name substitution already defined in the model. Skipping.");
return;
}
schemas.values().forEach(s -> Optional.ofNullable(s.getExtensions()).ifPresent(e -> e.remove(extensionName)));
renameSchemas(openAPI);
renameReferences(openAPI);
}

private void renameSchemas(OpenAPI oas) {
var schemas = new OasWrapper(oas).schemas().entrySet().stream()
.map(e -> Pair.of(substitutions.getOrDefault(e.getKey(), e.getKey()), e.getValue()))
.collect(Collectors.toMap(Pair::first, Pair::second));
oas.getComponents().setSchemas(schemas);
}

protected Schema tryConverting(Schema schema) {
if(schema instanceof ArraySchema) {
var items = tryConverting(schema.getItems());
schema.setItems(items);
}
schema.set$ref(convert(schema.get$ref()));
return schema;
}

protected String convert(String ref) {
return Optional.ofNullable(ref)
.flatMap(r -> {
var name = OasUtils.toSchemaName(ref);
return Optional.ofNullable(substitutions.get(name))
.map(OasUtils::toSchemRef);
}).orElse(ref);
}

private void renameReferences(OpenAPI openAPI) {
new RenameReferences().accept(openAPI);
new RenamePathReferences().accept(openAPI);
//FIXME rename refrences in response ref and body ref
}

protected Optional<String> toName(Schema schema) {
var name = Optional.ofNullable(schema.getExtensions())
.flatMap(e -> Optional.ofNullable(e.get(extensionName)).map(it -> (String)it));
return name.flatMap(n -> Optional.ofNullable(converter.convert(n)));
}

private class RenameReferences extends PropertyPostProcessor {
protected Map.Entry<String, Schema> processProperty(String name, Schema property) {
return Map.entry(name, tryConverting(property));
}
}

private class RenamePathReferences implements Consumer<OpenAPI> {

@Override
public void accept(OpenAPI openAPI) {
Stream<Schema> schemas = toOperations(openAPI).flatMap(o -> {
var bs = schemas(o.getRequestBody());
var rs = schemas(o.getResponses());
return Stream.concat(bs, rs);
});

schemas.forEach(RenameTypesPostprocessor.this::tryConverting);

openAPI.getPaths().entrySet().stream().map(Map.Entry::getValue)
.flatMap(pi -> pi.readOperations().stream());

}

private Stream<Operation> toOperations(OpenAPI oas) {
return Optional.ofNullable(oas.getPaths())
.map(p -> p.entrySet().stream()
.map(Map.Entry::getValue).flatMap(pi -> pi.readOperations().stream())
).orElse(Stream.empty());
}

private Stream<Schema> schemas(ApiResponses resp) {
return Optional.ofNullable(resp)
.map(LinkedHashMap::values).stream()
.flatMap(Collection::stream)
.flatMap(this::schemas);
}
private Stream<Schema> schemas(ApiResponse r) {
if(r.get$ref() != null) return Stream.empty();
return schemas(r.getContent());
}

private Stream<Schema> schemas(Content c) {
if(c == null) Stream.empty();
return c.values().stream().map(MediaType::getSchema);
}

private Stream<Schema> schemas(RequestBody rb) {
return Optional.ofNullable(rb).stream().flatMap(r -> {
if(r.get$ref() != null) return Stream.empty();
return schemas(r.getContent());
});
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,11 @@ public Optional<NameAndDiscriminator> provideNameAndDiscriminator(URI schemaLoca
if (schemaLocation == null) {
return Optional.empty();
}
return Optional.ofNullable(schemaLocation.getFragment())
.map(f -> {
var idx = f.lastIndexOf("/");
if (idx < 0 || idx == f.length() - 1) {
//TODO rethink
return f;
}
return f.substring(idx + 1);
})
.map(n -> new NameAndDiscriminator(n, null));
return fromText(schemaLocation.getFragment());
}

@Override
public Optional<NameAndDiscriminator> fromText(String fragment) {
return Optional.ofNullable(NameConverter.lastSegment.apply(fragment));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.amartus.sonata.blender.impl.specifications;

import org.apache.commons.lang3.text.WordUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.net.URI;
import java.util.Optional;
import java.util.function.Function;
import java.util.stream.Collectors;

import static com.amartus.sonata.blender.impl.util.TextUtils.split;

public interface NameConverter extends Function<String, ProductSpecificationNamingStrategy.NameAndDiscriminator> {

Logger log = LoggerFactory.getLogger(UrnBasedNamingStrategy.class);

private static String toName(String type) {
return split(type, '-').map(WordUtils::capitalize)
.collect(Collectors.joining(""));
};

NameConverter urn = id -> {
var uri = URI.create(id);

if ("urn".equals(uri.getScheme())) {
String[] segments = uri.getRawSchemeSpecificPart().split(":");
if ("mef".equals(segments[0]) && segments.length == 7) {
try {
var name = toName(segments[4]);
return new ProductSpecificationNamingStrategy.NameAndDiscriminator(name, id);
} catch (NullPointerException | IndexOutOfBoundsException e) {
log.info("{} is not MEF urn", id);
}
}
}
return null;
};

private static ProductSpecificationNamingStrategy.NameAndDiscriminator lastSegment(String s) {
return Optional.ofNullable(s)
.flatMap(p -> split(p, '/').reduce((acc,b) -> b))
.map(ProductSpecificationNamingStrategy.NameAndDiscriminator::new)
.orElse(null);
}

NameConverter lastSegment = NameConverter::lastSegment;
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,11 @@ public Optional<NameAndDiscriminator> provideNameAndDiscriminator(URI schemaLoca
if (schemaLocation == null) {
return Optional.empty();
}
return Optional.ofNullable(schemaLocation.getPath())
.map(f -> {
var idx = f.lastIndexOf("/");
if (idx < 0 || idx == f.length() - 1) {
//TODO rethink
return f;
}
return f.substring(idx + 1);
})
.map(n -> new NameAndDiscriminator(n, null));
return fromText(schemaLocation.getPath());
}

@Override
public Optional<NameAndDiscriminator> fromText(String path) {
return Optional.ofNullable(NameConverter.lastSegment.apply(path));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
*/
public interface ProductSpecificationNamingStrategy {
Optional<NameAndDiscriminator> provideNameAndDiscriminator(URI schemaLocation, JsonNode fileContent);
Optional<NameAndDiscriminator> fromText(String id);

class NameAndDiscriminator {
private final String name;
Expand Down
Loading

0 comments on commit 9e02953

Please sign in to comment.