diff --git a/pom.xml b/pom.xml
index a5ad6e9..99a162e 100644
--- a/pom.xml
+++ b/pom.xml
@@ -25,9 +25,9 @@
com.amartus.sonata
blender
SonataBlendingTool
- 1.9.3
+ 1.9.4-SNAPSHOT
- 6.0.1
+ 6.2.0
2.14.0
@@ -51,7 +51,7 @@
com.github.rvesse
airline
- 2.8.5
+ 2.9.0
ch.qos.logback
diff --git a/src/main/java/com/amartus/sonata/blender/impl/postprocess/ComposedPostprocessor.java b/src/main/java/com/amartus/sonata/blender/impl/postprocess/ComposedPostprocessor.java
index cd9a1f5..4f92632 100644
--- a/src/main/java/com/amartus/sonata/blender/impl/postprocess/ComposedPostprocessor.java
+++ b/src/main/java/com/amartus/sonata/blender/impl/postprocess/ComposedPostprocessor.java
@@ -18,15 +18,27 @@
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 {
+ private static final Logger log = LoggerFactory.getLogger(ComposedPostprocessor.class);
private List> postprocessors = List.of(
new RemoveSuperflousTypeDeclarations(),
+ new RenameTypesPostprocessor(converter()),
new PropertyEnumExternalize(),
new ComposedPropertyToType(),
new SingleEnumToDiscriminatorValue(),
@@ -34,12 +46,23 @@ public class ComposedPostprocessor implements Consumer {
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);
+ }
}
diff --git a/src/main/java/com/amartus/sonata/blender/impl/postprocess/ComposedPropertyToType.java b/src/main/java/com/amartus/sonata/blender/impl/postprocess/ComposedPropertyToType.java
index 769c1ec..cf79a6b 100644
--- a/src/main/java/com/amartus/sonata/blender/impl/postprocess/ComposedPropertyToType.java
+++ b/src/main/java/com/amartus/sonata/blender/impl/postprocess/ComposedPropertyToType.java
@@ -72,7 +72,7 @@ protected Map.Entry 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);
}
diff --git a/src/main/java/com/amartus/sonata/blender/impl/postprocess/ConvertOneOfToAllOffInheritance.java b/src/main/java/com/amartus/sonata/blender/impl/postprocess/ConvertOneOfToAllOffInheritance.java
index baf5a7e..98a0c0e 100644
--- a/src/main/java/com/amartus/sonata/blender/impl/postprocess/ConvertOneOfToAllOffInheritance.java
+++ b/src/main/java/com/amartus/sonata/blender/impl/postprocess/ConvertOneOfToAllOffInheritance.java
@@ -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;
@@ -131,7 +132,7 @@ private Map> 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> convertToOneOf(Map.Entry e) {
diff --git a/src/main/java/com/amartus/sonata/blender/impl/postprocess/PropertyPostProcessor.java b/src/main/java/com/amartus/sonata/blender/impl/postprocess/PropertyPostProcessor.java
index 5153f60..22f6815 100644
--- a/src/main/java/com/amartus/sonata/blender/impl/postprocess/PropertyPostProcessor.java
+++ b/src/main/java/com/amartus/sonata/blender/impl/postprocess/PropertyPostProcessor.java
@@ -47,6 +47,7 @@ protected void process(String type, Schema schema) {
Map 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) {
diff --git a/src/main/java/com/amartus/sonata/blender/impl/postprocess/RenameTypesPostprocessor.java b/src/main/java/com/amartus/sonata/blender/impl/postprocess/RenameTypesPostprocessor.java
new file mode 100644
index 0000000..7be878b
--- /dev/null
+++ b/src/main/java/com/amartus/sonata/blender/impl/postprocess/RenameTypesPostprocessor.java
@@ -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 {
+ private static final Logger log = LoggerFactory.getLogger(RenameTypesPostprocessor.class);
+ public interface NameConverter {
+ String convert(String input);
+ }
+ private Map 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 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 processProperty(String name, Schema property) {
+ return Map.entry(name, tryConverting(property));
+ }
+ }
+
+ private class RenamePathReferences implements Consumer {
+
+ @Override
+ public void accept(OpenAPI openAPI) {
+ Stream 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 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 schemas(ApiResponses resp) {
+ return Optional.ofNullable(resp)
+ .map(LinkedHashMap::values).stream()
+ .flatMap(Collection::stream)
+ .flatMap(this::schemas);
+ }
+ private Stream schemas(ApiResponse r) {
+ if(r.get$ref() != null) return Stream.empty();
+ return schemas(r.getContent());
+ }
+
+ private Stream schemas(Content c) {
+ if(c == null) Stream.empty();
+ return c.values().stream().map(MediaType::getSchema);
+ }
+
+ private Stream schemas(RequestBody rb) {
+ return Optional.ofNullable(rb).stream().flatMap(r -> {
+ if(r.get$ref() != null) return Stream.empty();
+ return schemas(r.getContent());
+ });
+ }
+ }
+}
diff --git a/src/main/java/com/amartus/sonata/blender/impl/specifications/FragmentBasedNamingStrategy.java b/src/main/java/com/amartus/sonata/blender/impl/specifications/FragmentBasedNamingStrategy.java
index 36386fc..7c0d279 100644
--- a/src/main/java/com/amartus/sonata/blender/impl/specifications/FragmentBasedNamingStrategy.java
+++ b/src/main/java/com/amartus/sonata/blender/impl/specifications/FragmentBasedNamingStrategy.java
@@ -28,15 +28,11 @@ public Optional 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 fromText(String fragment) {
+ return Optional.ofNullable(NameConverter.lastSegment.apply(fragment));
}
}
diff --git a/src/main/java/com/amartus/sonata/blender/impl/specifications/NameConverter.java b/src/main/java/com/amartus/sonata/blender/impl/specifications/NameConverter.java
new file mode 100644
index 0000000..2478142
--- /dev/null
+++ b/src/main/java/com/amartus/sonata/blender/impl/specifications/NameConverter.java
@@ -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 {
+
+ 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;
+}
diff --git a/src/main/java/com/amartus/sonata/blender/impl/specifications/PathBaseNamingStrategy.java b/src/main/java/com/amartus/sonata/blender/impl/specifications/PathBaseNamingStrategy.java
index f9bb96e..f6b32db 100644
--- a/src/main/java/com/amartus/sonata/blender/impl/specifications/PathBaseNamingStrategy.java
+++ b/src/main/java/com/amartus/sonata/blender/impl/specifications/PathBaseNamingStrategy.java
@@ -28,15 +28,11 @@ public Optional 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 fromText(String path) {
+ return Optional.ofNullable(NameConverter.lastSegment.apply(path));
}
}
diff --git a/src/main/java/com/amartus/sonata/blender/impl/specifications/ProductSpecificationNamingStrategy.java b/src/main/java/com/amartus/sonata/blender/impl/specifications/ProductSpecificationNamingStrategy.java
index 626031e..a65441a 100644
--- a/src/main/java/com/amartus/sonata/blender/impl/specifications/ProductSpecificationNamingStrategy.java
+++ b/src/main/java/com/amartus/sonata/blender/impl/specifications/ProductSpecificationNamingStrategy.java
@@ -28,6 +28,7 @@
*/
public interface ProductSpecificationNamingStrategy {
Optional provideNameAndDiscriminator(URI schemaLocation, JsonNode fileContent);
+ Optional fromText(String id);
class NameAndDiscriminator {
private final String name;
diff --git a/src/main/java/com/amartus/sonata/blender/impl/specifications/UrnBasedNamingStrategy.java b/src/main/java/com/amartus/sonata/blender/impl/specifications/UrnBasedNamingStrategy.java
index b57f6e5..f5d0d9f 100644
--- a/src/main/java/com/amartus/sonata/blender/impl/specifications/UrnBasedNamingStrategy.java
+++ b/src/main/java/com/amartus/sonata/blender/impl/specifications/UrnBasedNamingStrategy.java
@@ -29,43 +29,17 @@
import java.util.stream.Stream;
public class UrnBasedNamingStrategy implements ProductSpecificationNamingStrategy {
- private static final Logger log = LoggerFactory.getLogger(UrnBasedNamingStrategy.class);
-
@Override
public Optional provideNameAndDiscriminator(URI schemaLocation, JsonNode fileContent) {
return Optional.ofNullable(fileContent)
.map(fc -> fc.get("$id"))
.flatMap(i -> Optional.ofNullable(i.textValue()))
- .flatMap(this::convert);
- }
-
- private Optional convert(String 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 Optional.of(new NameAndDiscriminator(name, id));
- } catch (NullPointerException | IndexOutOfBoundsException e) {
- log.info("{} is not MEF urn", id);
- }
- }
-
- }
- return Optional.empty();
- }
-
- private String toName(String type) {
- return split(type, '-').map(WordUtils::capitalize)
- .collect(Collectors.joining(""));
+ .flatMap(this::fromText);
}
- private Stream split(String word, char... separators) {
- if (separators.length == 0) return Stream.of(word);
- String regex = "[" + new String(separators) + "]";
- return Arrays.stream(word.split(regex));
+ @Override
+ public Optional fromText(String id) {
+ return Optional.ofNullable(NameConverter.urn.apply(id));
}
}
diff --git a/src/main/java/com/amartus/sonata/blender/impl/util/Collections.java b/src/main/java/com/amartus/sonata/blender/impl/util/Collections.java
new file mode 100644
index 0000000..97e74e8
--- /dev/null
+++ b/src/main/java/com/amartus/sonata/blender/impl/util/Collections.java
@@ -0,0 +1,17 @@
+package com.amartus.sonata.blender.impl.util;
+
+import java.util.LinkedHashMap;
+import java.util.Map;
+import java.util.function.BinaryOperator;
+import java.util.stream.Collector;
+import java.util.stream.Collectors;
+
+public interface Collections {
+ static Collector, ?, LinkedHashMap> mapCollector() {
+ return mapCollector((a,b) -> a);
+ }
+
+ static Collector, ?, LinkedHashMap> mapCollector(BinaryOperator merger) {
+ return Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, merger, LinkedHashMap::new);
+ }
+}
diff --git a/src/main/java/com/amartus/sonata/blender/impl/util/OasUtils.java b/src/main/java/com/amartus/sonata/blender/impl/util/OasUtils.java
index 866bb1d..24567a1 100644
--- a/src/main/java/com/amartus/sonata/blender/impl/util/OasUtils.java
+++ b/src/main/java/com/amartus/sonata/blender/impl/util/OasUtils.java
@@ -87,11 +87,11 @@ static boolean isReferencingSchema(Schema> schema) {
return false;
}
- static long countReferences(Schema schema) {
- final var counter = Helpers.safeConvert.andThen(Helpers.references);
+ static long countReferences(Schema> schema) {
if (schema instanceof ObjectSchema) {
return Helpers.references.apply(Stream.of(schema));
}
+ final var counter = Helpers.safeConvert.andThen(Helpers.references);
if (schema instanceof ComposedSchema) {
var cs = (ComposedSchema) schema;
return Stream.of(
diff --git a/src/main/java/com/amartus/sonata/blender/impl/util/TextUtils.java b/src/main/java/com/amartus/sonata/blender/impl/util/TextUtils.java
index eb086da..dd3f69f 100644
--- a/src/main/java/com/amartus/sonata/blender/impl/util/TextUtils.java
+++ b/src/main/java/com/amartus/sonata/blender/impl/util/TextUtils.java
@@ -19,9 +19,11 @@
package com.amartus.sonata.blender.impl.util;
import java.util.ArrayList;
+import java.util.Arrays;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import java.util.stream.Stream;
public interface TextUtils {
static List splitPhrases(String subject) {
@@ -42,4 +44,10 @@ static List splitPhrases(String subject) {
}
return matchList;
}
+
+ static Stream split(String word, char... separators) {
+ if (separators.length == 0) return Stream.of(word);
+ String regex = "[" + new String(separators) + "]";
+ return Arrays.stream(word.split(regex));
+ }
}
diff --git a/src/main/java/com/amartus/sonata/blender/parser/DeserializerProvider.java b/src/main/java/com/amartus/sonata/blender/parser/DeserializerProvider.java
index d1f449e..05d826c 100644
--- a/src/main/java/com/amartus/sonata/blender/parser/DeserializerProvider.java
+++ b/src/main/java/com/amartus/sonata/blender/parser/DeserializerProvider.java
@@ -12,19 +12,22 @@
public class DeserializerProvider {
private static final Logger log = LoggerFactory.getLogger(DeserializerProvider.class);
static class AmartusDeserializer extends OpenAPIDeserializer {
+ private Optional getByName(ObjectNode node, String name) {
+ return Optional.ofNullable(node.get(name))
+ .map(JsonNode::textValue);
+ }
@Override
public Schema getSchema(ObjectNode node, String location, ParseResult result) {
var schema = super.getSchema(node, location, result);
if (schema.get$ref() != null) {
- var description = Optional.ofNullable(node.get("description"))
- .map(JsonNode::textValue);
- description.ifPresent(d -> {
+ getByName(node, "description").ifPresent(d -> {
log.debug("Adding description of a $ref property {}", schema.get$ref());
schema.setDescription(d);
});
}
+ getByName(node,"$id").ifPresent(id -> schema.addExtension("x-try-renaming-on", id));
return schema;
}
}
diff --git a/src/test/resources/ref-model/common/common.json b/src/test/resources/ref-model/common/common.json
index 6606245..cdc329f 100644
--- a/src/test/resources/ref-model/common/common.json
+++ b/src/test/resources/ref-model/common/common.json
@@ -4,6 +4,12 @@
"properties": {
"c1": {
"type": "string"
+ },
+ "a1": {
+ "type": "string"
+ },
+ "d1" : {
+ "type": "integer"
}
}
}