Skip to content

Commit c0e9330

Browse files
committed
- improved support for multiple schemas
- test coverage for uri fragments
1 parent 2496de4 commit c0e9330

File tree

7 files changed

+159
-44
lines changed

7 files changed

+159
-44
lines changed

‎pom.xml

+14-2
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@
2525
<groupId>com.amartus.sonata</groupId>
2626
<artifactId>blender</artifactId>
2727
<description>SonataBlendingTool</description>
28-
<version>1.10.2</version>
28+
<version>1.11.0-SNAPSHOT</version>
2929
<properties>
3030
<openapi.generator.version>6.2.0</openapi.generator.version>
3131
<jackson.version>2.14.2</jackson.version>
@@ -84,6 +84,12 @@
8484
<version>1.18.26</version>
8585
<scope>provided</scope>
8686
</dependency>
87+
<dependency>
88+
<groupId>org.junit.jupiter</groupId>
89+
<artifactId>junit-jupiter-api</artifactId>
90+
<version>5.9.0</version>
91+
<scope>test</scope>
92+
</dependency>
8793
<dependency>
8894
<groupId>org.junit.jupiter</groupId>
8995
<artifactId>junit-jupiter-engine</artifactId>
@@ -123,7 +129,7 @@
123129
<path>
124130
<groupId>org.projectlombok</groupId>
125131
<artifactId>lombok</artifactId>
126-
<version>1.18.22</version>
132+
<version>1.18.26</version>
127133
</path>
128134
</annotationProcessorPaths>
129135
</configuration>
@@ -146,6 +152,12 @@
146152
</archive>
147153
</configuration>
148154
</plugin>
155+
<plugin>
156+
<groupId>org.apache.maven.plugins</groupId>
157+
<artifactId>maven-surefire-plugin</artifactId>
158+
<version>3.1.0</version>
159+
160+
</plugin>
149161
<plugin>
150162
<groupId>org.apache.maven.plugins</groupId>
151163
<artifactId>maven-shade-plugin</artifactId>

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

+35-23
Original file line numberDiff line numberDiff line change
@@ -21,19 +21,16 @@
2121
import com.amartus.sonata.blender.cmd.AbstractBlend;
2222
import com.amartus.sonata.blender.impl.util.OasUtils;
2323
import com.amartus.sonata.blender.impl.util.OasWrapper;
24-
import io.swagger.models.properties.Property;
2524
import io.swagger.v3.oas.models.OpenAPI;
2625
import io.swagger.v3.oas.models.media.Discriminator;
2726
import io.swagger.v3.oas.models.media.Schema;
2827
import io.swagger.v3.oas.models.media.StringSchema;
2928
import org.slf4j.Logger;
3029
import org.slf4j.LoggerFactory;
3130

32-
import java.util.Collection;
33-
import java.util.Map;
34-
import java.util.Objects;
35-
import java.util.Optional;
31+
import java.util.*;
3632
import java.util.function.Function;
33+
import java.util.stream.Collectors;
3734

3835

3936
public class MergeSchemasAction {
@@ -50,7 +47,7 @@ public enum Mode {
5047
private OasWrapper wrapper;
5148

5249
private interface Handler {
53-
void handle(Discriminator disc, Schema prop);
50+
void handle(Discriminator disc, Schema prop, Schema subject);
5451
}
5552

5653
public MergeSchemasAction(String modelToAugment, Mode mode) {
@@ -62,7 +59,7 @@ public MergeSchemasAction(String modelToAugment, Mode mode) {
6259
private Handler handler(Mode mode, String model) {
6360
switch (mode) {
6461
case FIX: return this::fixTargetSchema;
65-
case RELAXED: return (d, p) -> {
62+
case RELAXED: return (d, p, t) -> {
6663
var issues = 0;
6764
if(d == null) {
6865
issues += 1;
@@ -77,10 +74,10 @@ private Handler handler(Mode mode, String model) {
7774
throw new IllegalStateException("Discriminator property not found");
7875
}
7976

80-
fixTargetSchema(d, p);
77+
fixTargetSchema(d, p, t);
8178

8279
};
83-
default: return (s, p) -> {
80+
default: return (s, p, _n) -> {
8481
if(s == null || p == null) {
8582
log.error("No discriminator defined for {} ", modelToAugment);
8683
throw new IllegalStateException("Discriminator not found");
@@ -103,45 +100,60 @@ public MergeSchemasAction target(OpenAPI api) {
103100
}
104101

105102
public void execute() {
103+
if (schemasToInject.isEmpty()) return;
104+
106105
log.debug("Injecting {} schemas",
107106
schemasToInject.size());
107+
Set<String> targets = findTargets();
108+
109+
targets.forEach(targetName -> {
110+
validateTargetExists(targetName);
111+
Schema schema = this.openAPI.getComponents().getSchemas().get(targetName);
112+
log.info("Evaluating target {}", targetName);
113+
handler.handle(discriminator(schema), prop(schema), schema);
114+
});
108115

109-
if (!schemasToInject.isEmpty()) {
110-
validateTargetExists();
111-
Schema schema = this.openAPI.getComponents().getSchemas().get(modelToAugment);
112-
handler.handle(discriminator(schema), prop(schema));
113-
}
114116
this.openAPI.getComponents().getSchemas().putAll(schemasToInject);
115117
}
116118

117-
private void validateTargetExists() {
118-
if (getTargetSchema().isEmpty()) {
119+
private Set<String> findTargets() {
120+
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)
124+
.collect(Collectors.toSet());
125+
targets.add(modelToAugment);
126+
return targets;
127+
}
128+
129+
private void validateTargetExists(String targetName) {
130+
if (getTargetSchema(targetName).isEmpty()) {
119131
log.error("Schema with name '{}' is not present in the API", modelToAugment);
120132
throw new IllegalStateException(String.format("Schema '%s' not found in the specification", modelToAugment));
121133
}
122134
}
123135

124136
@SuppressWarnings("OptionalGetWithoutIsPresent")
125-
private void fixTargetSchema(Discriminator discriminator, Schema property) {
126-
var schema = getTargetSchema().get();
137+
private void fixTargetSchema(Discriminator discriminator, Schema property, Schema targetSchema) {
138+
127139
if(property == null) {
128140
log.info("Adding field {} to the {}", DISCRIMINATOR_NAME, modelToAugment);
129-
schema.addProperty(DISCRIMINATOR_NAME,
141+
targetSchema.addProperty(DISCRIMINATOR_NAME,
130142
new StringSchema().description("Used as a discriminator to support polymorphic definitions"));
131143
}
132144
if(discriminator == null) {
133145
log.info("Adding discriminator to the {}", modelToAugment);
134-
schema.setDiscriminator(new Discriminator().propertyName(DISCRIMINATOR_NAME));
146+
targetSchema.setDiscriminator(new Discriminator().propertyName(DISCRIMINATOR_NAME));
135147
}
136148
}
137149

138150
@SuppressWarnings("rawtypes")
139-
private Optional<Schema> getTargetSchema() {
151+
private Optional<Schema> getTargetSchema(String modelName) {
140152
var allSchemas = Optional.ofNullable(this.openAPI.getComponents())
141153
.flatMap(c -> Optional.ofNullable(c.getSchemas()));
142154

143155
return allSchemas
144-
.map(all -> Optional.ofNullable(all.get(modelToAugment)))
156+
.map(all -> Optional.ofNullable(all.get(modelName)))
145157
.orElseThrow(() -> new IllegalStateException("API schemas are not resolved"));
146158
}
147159

@@ -165,7 +177,7 @@ private <T> T recursiveSearch(Schema schema, Function<Schema, T> extractor) {
165177
}
166178

167179
private Discriminator discriminator(Schema schema) {
168-
return recursiveSearch(schema, Schema::getDiscriminator);
180+
return schema.getDiscriminator();
169181
}
170182

171183
private Schema prop(Schema schema) {

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

+4-4
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ protected void process(String name, Schema schema) {
4343
.findFirst();
4444

4545
parentCandidate.ifPresent(parent -> {
46-
var discValue = discrininatorValue(schema).orElse(name);
46+
var discValue = discriminatorValue(schema).orElse(name);
4747
log.debug("Trying {} as parent discriminator update for {}", parent.get$ref(), name);
4848
updateMapping(name, discValue, parent.get$ref());
4949
});
@@ -60,13 +60,13 @@ private void updateMapping(String schemaName, String discriminator, String paren
6060
.flatMap(s -> Optional.ofNullable(s.getDiscriminator()))
6161
.filter(d -> d.getPropertyName() != null);
6262

63-
disc.ifPresent(d -> {
63+
disc.ifPresentOrElse(d -> {
6464
log.info("Updating {} discriminator mapping for {} with value '{}'", parentRef, schemaName, discriminator);
6565
d.mapping(discriminator, OasUtils.toSchemRef(schemaName));
66-
});
66+
}, () -> log.warn("Discriminator definition for '{}' not present at {}", schemaName, discriminator));
6767
}
6868

69-
private Optional<String> discrininatorValue(Schema schema) {
69+
private Optional<String> discriminatorValue(Schema schema) {
7070
return Optional.ofNullable(schema.getExtensions())
7171
.flatMap(e -> Optional.ofNullable((String) e.get("x-discriminator-value")));
7272
}

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

+20
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
import java.util.stream.Stream;
3333

3434
import static com.amartus.sonata.blender.impl.ProductSpecReader.DISCRIMINATOR_VALUE;
35+
import static org.assertj.core.api.Assertions.assertThat;
3536
import static org.junit.jupiter.api.Assertions.assertEquals;
3637
import static org.junit.jupiter.api.Assertions.assertNotNull;
3738

@@ -53,6 +54,7 @@ public void testReaderForSchemaUsingRelativePaths() {
5354
var schemas = new ProductSpecReader("testToAugment", dirPath.resolve("model-js.json"))
5455
.readSchemas();
5556
singleRootSchema(schemas);
57+
validateDiscriminatedModel(schemas.get("model-js.json"), "model-js.json");
5658
assertEquals(7, schemas.size());
5759
}
5860

@@ -62,6 +64,16 @@ public void testReaderForSchemaOas() {
6264
var schemas = new ProductSpecReader(ProductSpecReader.Options.forName("testToAugment"), dirPath.resolve("model-oas.yaml"), "#/components/schemas/ModelOAS", deserializerProvider, ProductSpecReader.defaultOptions())
6365
.readSchemas();
6466
singleRootSchema(schemas);
67+
validateDiscriminatedModel(schemas.get("ModelOAS"), "ModelOAS");
68+
assertEquals(7, schemas.size());
69+
}
70+
@Test
71+
public void testReaderForSchemaOasWithXDiscriminator() {
72+
var dirPath = Utils.toPath("mini-model");
73+
var schemas = new ProductSpecReader(ProductSpecReader.Options.forName("testToAugment"), dirPath.resolve("model-oas.yaml"), "#/components/schemas/ModelOASWithDiscriminator", deserializerProvider, ProductSpecReader.defaultOptions())
74+
.readSchemas();
75+
singleRootSchema(schemas);
76+
validateDiscriminatedModel(schemas.get("ModelOASWithDiscriminator"), "urn:mef:lso:spec:cantata-sonata:model-with-discriminator:v0.3.0:all");
6577
assertEquals(7, schemas.size());
6678
}
6779

@@ -100,4 +112,12 @@ private Stream<String> markedSchemas(Map<String, Schema<?>> allSchemas) {
100112
.map(Map.Entry::getKey);
101113
}
102114

115+
private void validateDiscriminatedModel(Schema<?> modelOAS, String discriminator) {
116+
assertThat(modelOAS)
117+
.isNotNull()
118+
.extracting(Schema::getExtensions)
119+
.satisfies(m -> assertThat(m).containsEntry("x-discriminator-value", discriminator));
120+
121+
}
122+
103123
}

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

+66-14
Original file line numberDiff line numberDiff line change
@@ -32,20 +32,18 @@
3232

3333
public class ValidateSpecificationTest {
3434
private static final Logger log = LoggerFactory.getLogger(ValidateSpecificationTest.class);
35-
private static final Path source;
35+
private static final Path source = classPath("/oas/test-spec.yaml");
36+
private Path target;
3637

37-
static {
38+
private static Path classPath(String path) {
3839
try {
39-
//noinspection DataFlowIssue
40-
source = Paths.get(ValidateSpecificationTest.class.getResource("/oas/test-spec.yaml").toURI());
40+
return Paths.get(ValidateSpecificationTest.class.getResource(path).toURI());
4141
} catch (URISyntaxException e) {
4242
throw new RuntimeException(e);
4343
}
4444
}
4545

46-
private Path target;
47-
48-
private static Stream<String> args(Path target) {
46+
private static Stream<String> commonArgs(Path target) {
4947
var api = source.toAbsolutePath();
5048
var search = api.getParent();
5149
return Stream.of(
@@ -54,15 +52,41 @@ private static Stream<String> args(Path target) {
5452
api.toString(),
5553
"-d",
5654
search.toString(),
57-
"--model-name",
58-
"Placeholder",
59-
"--all-schemas",
60-
target.getParent().toAbsolutePath().toString(),
6155
"-o",
6256
target.toAbsolutePath().toString()
6357
);
6458
}
6559

60+
private static Stream<String> args(Path target) {
61+
return args("Placeholder", target);
62+
}
63+
64+
private static Stream<String> args(String model, Path target) {
65+
return Stream.concat(
66+
commonArgs(target),
67+
Stream.of(
68+
"--model-name",
69+
model,
70+
"--all-schemas",
71+
"all"
72+
)
73+
);
74+
}
75+
76+
77+
78+
private static Stream<String> args(Path target, String file) {
79+
return Stream.concat(
80+
commonArgs(target),
81+
Stream.of(
82+
"--model-name",
83+
"Placeholder",
84+
"-b",
85+
file
86+
)
87+
);
88+
}
89+
6690
@BeforeEach
6791
public void setup() throws IOException {
6892
var tempDir = Files.createTempDirectory("oas");
@@ -104,9 +128,32 @@ public void smokeTest() throws Exception {
104128
);
105129
}
106130

131+
@Test
132+
public void checkFragmentSchema() throws Exception {
133+
var fileFragment = classPath("/mini-model/model-oas.yaml") + "#/components/schemas/ModelOASWithDiscriminator";
134+
Stream<String> args = args(target, fileFragment);
135+
136+
var builder = builder();
137+
138+
builder.build().parse(args.toArray(String[]::new)).run();
139+
140+
var generated = SerializationUtils.yamlMapper().readTree(target.toFile());
141+
assertThatJson(generated)
142+
.inPath("$.components.schemas.ModelOASWithDiscriminator")
143+
.isObject()
144+
.satisfies(s ->
145+
assertThatJson(s).node("allOf[0].$ref").isEqualTo("#/components/schemas/Placeholder")
146+
);
147+
assertThatJson(generated)
148+
.inPath("$.components.schemas.Placeholder.discriminator.mapping")
149+
.isObject()
150+
.satisfies(e ->
151+
assertThat(e).containsEntry("urn:mef:lso:spec:cantata-sonata:model-with-discriminator:v0.3.0:all", "#/components/schemas/ModelOASWithDiscriminator"));
152+
}
153+
107154
@Test
108155
public void strictModeSupportsInheritance() throws IOException {
109-
Stream<String> args = Stream.concat(args(target), Stream.of("--strict-mode"));
156+
Stream<String> args = Stream.concat(args("TargetParentName", target), Stream.of("--strict-mode"));
110157
var builder = builder();
111158

112159
builder.build().parse(args.toArray(String[]::new)).run();
@@ -116,12 +163,12 @@ public void strictModeSupportsInheritance() throws IOException {
116163
.inPath("$.components.schemas.ToInject")
117164
.isObject()
118165
.satisfies(s ->
119-
assertThatJson(s).node("allOf[0].$ref").isEqualTo("#/components/schemas/Placeholder")
166+
assertThatJson(s).node("allOf[0].$ref").isEqualTo("#/components/schemas/TargetParentName")
120167
);
121168
}
122169

123170
@Test
124-
public void doesNotHaveDefaultValuesForParameters() throws IOException {
171+
public void doesNotHaveDefaultValuesForParameters() {
125172
Stream<String> args = args(target);
126173
var builder = builder();
127174
builder.build().parse(args.toArray(String[]::new)).run();
@@ -143,6 +190,11 @@ public void respectTarget() throws Exception {
143190
.satisfies(s ->
144191
assertThatJson(s).node("allOf[0].$ref").isEqualTo("#/components/schemas/TargetParentName")
145192
);
193+
assertThatJson(generated)
194+
.inPath("$.components.schemas.TargetParentName.discriminator.mapping")
195+
.isObject()
196+
.satisfies(e ->
197+
assertThat(e).containsEntry("urn:mef:lso:spec:cantata-sonata:to-inject:v0.3.0:all", "#/components/schemas/ToInject"));
146198
}
147199

148200
@Test

0 commit comments

Comments
 (0)