Skip to content

Commit 7e7ac03

Browse files
committed
- support for OAS 3.0 nullable
- better error messages for duplicated schemas - improved support for in-schema target definitions
1 parent 36fcf38 commit 7e7ac03

13 files changed

+112
-79
lines changed

‎src/main/java/com/amartus/sonata/blender/cmd/AbstractBlend.java

+71-4
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,15 @@
1919
package com.amartus.sonata.blender.cmd;
2020

2121
import com.amartus.sonata.blender.impl.ProductSpecReader;
22-
import com.amartus.sonata.blender.impl.util.Pair;
2322
import com.amartus.sonata.blender.impl.util.PathResolver;
2423
import com.amartus.sonata.blender.parser.DeserializerProvider;
2524
import com..rvesse.airline.annotations.Option;
2625
import com..rvesse.airline.annotations.restrictions.MutuallyExclusiveWith;
2726
import com..rvesse.airline.annotations.restrictions.Once;
2827
import io.swagger.v3.oas.models.media.Schema;
28+
import org.apache.commons.lang3.StringUtils;
29+
import org.apache.commons.lang3.tuple.Pair;
30+
import org.apache.commons.lang3.tuple.Triple;
2931
import org.slf4j.Logger;
3032
import org.slf4j.LoggerFactory;
3133

@@ -34,6 +36,7 @@
3436
import java.util.ArrayList;
3537
import java.util.List;
3638
import java.util.Map;
39+
import java.util.function.Function;
3740
import java.util.stream.Collectors;
3841
import java.util.stream.Stream;
3942

@@ -95,14 +98,78 @@ public abstract class AbstractBlend {
9598
protected Map<String, Schema> toProductSpecifications() {
9699
var config = new ProductSpecReader.Options(modelToAugment, autodiscover);
97100

98-
return toSchemaPaths(blendingSchemas())
99-
.flatMap(schema -> new ProductSpecReader(config, schema.first(), schema.second(), new DeserializerProvider(), ProductSpecReader.defaultOptions()).readSchemas().entrySet().stream())
101+
var foundSchemas = toSchemaPaths(blendingSchemas())
102+
.map(schema -> {
103+
var resolved = new ProductSpecReader(config,
104+
schema.getLeft(), schema.getRight(),
105+
new DeserializerProvider(), ProductSpecReader.defaultOptions())
106+
.readSchemas();
107+
return Pair.of(schema, resolved);
108+
})
109+
.collect(Collectors.toList());
110+
111+
validateResolvedSchemas(foundSchemas);
112+
113+
var allSchemas = foundSchemas.stream().flatMap(it -> it.getRight().entrySet().stream());
114+
115+
return allSchemas
100116
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> {
101117
if (a.equals(b)) return a;
102118
throw new IllegalArgumentException(String.format("Object for the same key does not match %s %s", a, b));
103119
}));
104120
}
105121

122+
Function<Pair<Path, String>, String> toPointer = it -> {
123+
var path = it.getLeft().toAbsolutePath().toString();
124+
var schema = it.getRight();
125+
return StringUtils.isBlank(schema) ? path : path + "#" + schema;
126+
};
127+
128+
private void validateResolvedSchemas(List<Pair<Pair<Path, String>, Map<String, Schema<?>>>> foundSchemas) {
129+
var byKeys = foundSchemas.stream()
130+
.flatMap(it -> {
131+
var key = it.getLeft();
132+
return it.getRight().entrySet().stream()
133+
.map(e -> Triple.of(e.getKey(), e.getValue(), key));
134+
}).collect(Collectors.groupingBy(Triple::getLeft));
135+
136+
List<String> messages = byKeys.entrySet().stream()
137+
.filter(it -> it.getValue().size() > 1)
138+
.map(it -> {
139+
var groups = it.getValue().stream().collect(Collectors.groupingBy(
140+
Triple::getMiddle,
141+
Collectors.mapping(t -> toPointer.apply((Pair<Path, String>) t.getRight()), Collectors.toList())
142+
));
143+
144+
var strings = groups.values().stream().map(list -> String.join("\n", list)).collect(Collectors.toList());
145+
return Pair.of(it.getKey(), strings);
146+
})
147+
.peek(this::logReferencingFiles)
148+
.filter(it -> ((List<String>)it.getValue()).size() > 1)
149+
.map(it -> {
150+
var groupsCount = it.getValue().size();
151+
StringBuilder sb = new StringBuilder();
152+
var idx = 1;
153+
154+
for(var group : it.getValue()) {
155+
sb.append(String.format("Group %d:\n%s\n", idx++, group));
156+
}
157+
158+
return String.format("Schema %s defined %d times and referenced from files groups:\n%s",
159+
it.getKey(), groupsCount,
160+
sb);
161+
})
162+
.collect(Collectors.toList());
163+
if(messages.isEmpty()) return;
164+
165+
throw new IllegalArgumentException(String.format("Found conflicting schema definitions\n %s", String.join("\n", messages)));
166+
}
167+
168+
private void logReferencingFiles(Pair<String, List<String>> it) {
169+
var files = String.join("\n", ((List<String>) it.getValue()));
170+
log.debug("Schema {} found in files:\n{}", it.getKey(), files);
171+
}
172+
106173
protected Stream<Pair<Path, String>> toSchemaPaths(Stream<String> path) {
107174
return new PathResolver(specificationsRootDir).toSchemaPaths(path);
108175
}
@@ -112,7 +179,7 @@ protected Stream<String> blendingSchemas() {
112179

113180
protected void validateProductSpecs() {
114181
var incorrectFilesExists = toSchemaPaths(blendingSchemas())
115-
.map(Pair::first)
182+
.map(Pair::getLeft)
116183
.filter(p -> !Files.isRegularFile(p))
117184
.peek(p -> log.warn("{} is not a file", p))
118185
.count() > 0;

‎src/main/java/com/amartus/sonata/blender/cmd/Merge.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ protected Map<String, Schema> toProductSpecifications() {
171171
var config = new ProductSpecReader.Options(modelToAugment, autodiscover);
172172

173173
return paths
174-
.flatMap(schema -> new ProductSpecReader(config, schema.first(), schema.second(), new DeserializerProvider(), ProductSpecReader.defaultOptions()).readSchemas().entrySet().stream())
174+
.flatMap(schema -> new ProductSpecReader(config, schema.getLeft(), schema.getRight(), new DeserializerProvider(), ProductSpecReader.defaultOptions()).readSchemas().entrySet().stream())
175175
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, (a, b) -> {
176176
if (a.equals(b)) return a;
177177
throw new IllegalArgumentException(String.format("Object for the same key does not match %s %s", a, b));

‎src/main/java/com/amartus/sonata/blender/impl/MergeSchemasAction.java

+14-4
Original file line numberDiff line numberDiff line change
@@ -118,11 +118,21 @@ public void execute() {
118118

119119
private Set<String> findTargets() {
120120
Set<String> targets = schemasToInject.values().stream()
121-
.map(s -> Optional.ofNullable(s.getExtensions())
122-
.map(em -> (String) em.get(ProductSpecReader.TARGET_NAME)).orElse(null))
123-
.filter(Objects::nonNull)
121+
.flatMap(s -> OasUtils.extensionByName(ProductSpecReader.TARGET_NAME, s).stream())
124122
.collect(Collectors.toSet());
125-
targets.add(modelToAugment);
123+
var noRootSchemaWithDefinedTarget = schemasToInject.values().stream()
124+
.filter(s -> OasUtils.extensionByName(ProductSpecReader.DISCRIMINATOR_VALUE, s).isPresent())
125+
.anyMatch(s -> OasUtils.extensionByName(ProductSpecReader.TARGET_NAME, s).isEmpty());
126+
127+
if(noRootSchemaWithDefinedTarget) {
128+
targets = new HashSet<>(targets);
129+
targets.add(modelToAugment);
130+
}
131+
132+
if(targets.isEmpty() && ! schemasToInject.isEmpty()) {
133+
throw new IllegalStateException("No targets identified for schemas to inject");
134+
}
135+
126136
return targets;
127137
}
128138

‎src/main/java/com/amartus/sonata/blender/impl/postprocess/RemoveDefaultParameterValues.java

+1-1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,6 @@ private void process(Parameter parameter) {
8383
}
8484

8585
private boolean defaultExplode(Parameter.StyleEnum defStyle, Boolean explode) {
86-
return (defStyle == Parameter.StyleEnum.FORM) == explode;
86+
return Boolean.valueOf(defStyle == Parameter.StyleEnum.FORM) == explode;
8787
}
8888
}

‎src/main/java/com/amartus/sonata/blender/impl/postprocess/RenameTypesPostprocessor.java

+2-2
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22

33
import com.amartus.sonata.blender.impl.util.OasUtils;
44
import com.amartus.sonata.blender.impl.util.OasWrapper;
5-
import com.amartus.sonata.blender.impl.util.Pair;
65
import io.swagger.v3.oas.models.OpenAPI;
76
import io.swagger.v3.oas.models.Operation;
87
import io.swagger.v3.oas.models.media.ArraySchema;
@@ -12,6 +11,7 @@
1211
import io.swagger.v3.oas.models.parameters.RequestBody;
1312
import io.swagger.v3.oas.models.responses.ApiResponse;
1413
import io.swagger.v3.oas.models.responses.ApiResponses;
14+
import org.apache.commons.lang3.tuple.Pair;
1515
import org.slf4j.Logger;
1616
import org.slf4j.LoggerFactory;
1717

@@ -64,7 +64,7 @@ public void accept(OpenAPI openAPI) {
6464
private void renameSchemas(OpenAPI oas) {
6565
var schemas = new OasWrapper(oas).schemas().entrySet().stream()
6666
.map(e -> Pair.of(substitutions.getOrDefault(e.getKey(), e.getKey()), e.getValue()))
67-
.collect(Collectors.toMap(Pair::first, Pair::second));
67+
.collect(Collectors.toMap(Pair::getLeft, Pair::getRight));
6868
oas.getComponents().setSchemas(schemas);
6969
}
7070

‎src/main/java/com/amartus/sonata/blender/impl/postprocess/SecureEndpointsWithOAuth2.java

+4-4
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@
1818

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

21-
import com.amartus.sonata.blender.impl.util.Pair;
2221
import io.swagger.v3.oas.models.OpenAPI;
2322
import io.swagger.v3.oas.models.Operation;
2423
import io.swagger.v3.oas.models.security.*;
24+
import org.apache.commons.lang3.tuple.Pair;
2525

2626
import java.nio.charset.StandardCharsets;
2727
import java.util.Base64;
@@ -92,10 +92,10 @@ private PerOperationSecurityModifier(OpenAPI oas) {
9292
@Override
9393
protected void addRequirements(List<Pair<Operation, String>> operations) {
9494
operations.forEach(e -> {
95-
var operation = e.first();
95+
var operation = e.getLeft();
9696
operation.addSecurityItem(
9797
new SecurityRequirement()
98-
.addList(schemeName, e.second())
98+
.addList(schemeName, e.getRight())
9999
);
100100
});
101101
}
@@ -105,7 +105,7 @@ protected Scopes toScopes(List<Pair<Operation, String>> operations) {
105105
var scopes = new Scopes();
106106

107107
operations.stream()
108-
.map(Pair::second)
108+
.map(Pair::getRight)
109109
.forEach(name -> scopes.addString(name, String.format("Scope for operation %s", name)));
110110
return scopes;
111111
}

‎src/main/java/com/amartus/sonata/blender/impl/util/FileWalker.java

+2
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.amartus.sonata.blender.impl.util;
22

3+
import org.apache.commons.lang3.tuple.Pair;
4+
35
import java.io.IOException;
46
import java.nio.file.Files;
57
import java.nio.file.Path;

‎src/main/java/com/amartus/sonata/blender/impl/util/IdSchemaResolver.java

+3-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
import com.amartus.sonata.blender.impl.specifications.UrnBasedNamingStrategy;
2222
import com.fasterxml.jackson.databind.JsonNode;
2323
import com.fasterxml.jackson.databind.ObjectMapper;
24+
import org.apache.commons.lang3.tuple.Pair;
2425
import org.slf4j.Logger;
2526
import org.slf4j.LoggerFactory;
2627

@@ -52,8 +53,8 @@ public List<Path> findProductSpecifications(Path rootPath) {
5253

5354
try (Stream<Pair<Path, Boolean>> allFiles = walker.walk(rootPath)) {
5455
return allFiles
55-
.filter(Pair::second)
56-
.map(Pair::first)
56+
.filter(Pair::getRight)
57+
.map(Pair::getLeft)
5758
.map(Path::toAbsolutePath)
5859
.collect(Collectors.toList());
5960
} catch (IOException e) {

‎src/main/java/com/amartus/sonata/blender/impl/util/OasUtils.java

+5
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,11 @@ static long countReferences(Schema<?> schema) {
103103
return 0;
104104
}
105105

106+
107+
static Optional<String> extensionByName(String name, Schema schema) {
108+
return Optional.ofNullable(schema.getExtensions())
109+
.flatMap(ex -> Optional.ofNullable((String) ex.get(name)));
110+
}
106111
static Stream<Schema> allSchemas(ComposedSchema cs) {
107112
return Stream.of(
108113
cs.getAllOf(),

‎src/main/java/com/amartus/sonata/blender/impl/util/Pair.java

-59
This file was deleted.

‎src/main/java/com/amartus/sonata/blender/impl/util/PathResolver.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
package com.amartus.sonata.blender.impl.util;
2020

21+
import org.apache.commons.lang3.tuple.Pair;
22+
2123
import java.nio.file.Path;
2224
import java.util.Collection;
2325
import java.util.stream.Stream;
@@ -37,7 +39,7 @@ public Stream<Pair<Path, String>> toSchemaPaths(Stream<String> paths) {
3739
final var rootPath = Path.of(root).toAbsolutePath();
3840
return paths
3941
.map(this::pathWithFragment)
40-
.map(p -> Pair.of(toPath(p.first(), rootPath), p.second()));
42+
.map(p -> Pair.of(toPath(p.getLeft(), rootPath), p.getRight()));
4143
}
4244

4345
private Path toPath(String path, Path root) {

‎src/main/java/com/amartus/sonata/blender/impl/util/SerializationUtils.java

+3
Original file line numberDiff line numberDiff line change
@@ -24,8 +24,10 @@
2424
import com.fasterxml.jackson.databind.MapperFeature;
2525
import com.fasterxml.jackson.databind.ObjectMapper;
2626
import com.fasterxml.jackson.databind.module.SimpleModule;
27+
import io.swagger.v3.core.jackson.mixin.SchemaMixin;
2728
import io.swagger.v3.core.util.Json31;
2829
import io.swagger.v3.oas.models.OpenAPI;
30+
import io.swagger.v3.oas.models.media.Schema;
2931
import org.openapitools.codegen.serializer.OpenAPISerializer;
3032

3133
public abstract class SerializationUtils {
@@ -43,6 +45,7 @@ private static ObjectMapper enhance(ObjectMapper mapper) {
4345
module.addSerializer(OpenAPI.class, new OpenAPISerializer());
4446
mapper.registerModule(module)
4547
.addMixIn(Object.class, IgnorePropertyMixin.class)
48+
.addMixIn(Schema.class, SchemaMixin.class)
4649
.setSerializationInclusion(JsonInclude.Include.NON_EMPTY)
4750
.configure(MapperFeature.SORT_PROPERTIES_ALPHABETICALLY, true);
4851
return mapper;

‎src/test/java/com/amartus/sonata/blender/impl/MergeSchemasActionTest.java

+3-1
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,10 @@ private MergeSchemasAction action(String modelToAugment, OpenAPI oas, MergeSchem
102102
}
103103

104104
private Map<String, Schema> schemas() {
105+
var schema = new ObjectSchema();
106+
schema.addExtension(ProductSpecReader.DISCRIMINATOR_VALUE, "some-discriminator");
105107
return Map.of(
106-
"foo", new ObjectSchema()
108+
"foo", schema
107109
);
108110
}
109111
}

0 commit comments

Comments
 (0)