Skip to content

Commit 99b784d

Browse files
authored
fix(core): resolve referenced schemas in grouping (#1192)
1 parent 30f8ff8 commit 99b784d

File tree

4 files changed

+262
-36
lines changed

4 files changed

+262
-36
lines changed

springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/grouping/GroupingService.java

Lines changed: 21 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@
2626
import java.util.Objects;
2727
import java.util.Set;
2828
import java.util.stream.Collectors;
29+
import java.util.stream.Stream;
2930

3031
@AllArgsConstructor
3132
public class GroupingService {
@@ -187,19 +188,31 @@ private void markSchemas(AsyncAPI fullAsyncApi, MarkingContext markingContext, S
187188
.forEach(schemaEntry -> {
188189
markingContext.markedComponentSchemaIds.add(schemaEntry.getKey());
189190

190-
if (schemaEntry.getValue().getProperties() != null) {
191-
Set<String> nestedSchemas = findUnmarkedNestedSchemas(markingContext, schemaEntry.getValue());
192-
if (!nestedSchemas.isEmpty()) {
193-
markSchemas(fullAsyncApi, markingContext, nestedSchemas);
194-
}
191+
Set<String> nestedSchemas = findUnmarkedNestedSchemas(markingContext, schemaEntry.getValue());
192+
if (!nestedSchemas.isEmpty()) {
193+
markSchemas(fullAsyncApi, markingContext, nestedSchemas);
195194
}
196195
});
197196
}
198197

199198
private static Set<String> findUnmarkedNestedSchemas(MarkingContext markingContext, SchemaObject schema) {
200-
return schema.getProperties().values().stream()
201-
.filter(el -> el instanceof ComponentSchema)
202-
.map(el -> (ComponentSchema) el)
199+
final Stream<ComponentSchema> propertySchemas;
200+
if (schema.getProperties() != null) {
201+
propertySchemas = schema.getProperties().values().stream()
202+
.filter(el -> el instanceof ComponentSchema)
203+
.map(el -> (ComponentSchema) el);
204+
} else {
205+
propertySchemas = Stream.empty();
206+
}
207+
208+
Stream<ComponentSchema> referencedSchemas = Stream.of(schema.getAllOf(), schema.getAnyOf(), schema.getOneOf())
209+
.filter(Objects::nonNull)
210+
.flatMap(List::stream);
211+
212+
Stream<ComponentSchema> referencedSchemaElements =
213+
Stream.of(schema.getNot(), schema.getItems()).filter(Objects::nonNull);
214+
215+
return Stream.concat(propertySchemas, Stream.concat(referencedSchemas, referencedSchemaElements))
203216
.map(ComponentSchema::getReference)
204217
.filter(Objects::nonNull)
205218
.map(MessageReference::getRef)

springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/grouping/GroupingServiceTest.java

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
import io.github.springwolf.asyncapi.v3.model.channel.message.MessageObject;
99
import io.github.springwolf.asyncapi.v3.model.channel.message.MessagePayload;
1010
import io.github.springwolf.asyncapi.v3.model.channel.message.MessageReference;
11+
import io.github.springwolf.asyncapi.v3.model.components.ComponentSchema;
1112
import io.github.springwolf.asyncapi.v3.model.components.Components;
1213
import io.github.springwolf.asyncapi.v3.model.info.Info;
1314
import io.github.springwolf.asyncapi.v3.model.operation.Operation;
@@ -213,6 +214,40 @@ void shouldCopyEverythingForEmptyFilter() {
213214
assertThat(grouped).isEqualTo(fullApi);
214215
}
215216

217+
@Test
218+
void shouldResolveReferencedSchemas1FromSchema4() {
219+
SchemaObject schema4 = SchemaObject.builder()
220+
.title("Schema4")
221+
.oneOf(List.of(ComponentSchema.of(MessageReference.toSchema(schema1.getTitle()))))
222+
.build();
223+
MessageObject message = MessageObject.builder()
224+
.messageId("messageId1")
225+
.payload(MessagePayload.of(MultiFormatSchema.builder()
226+
.schema(MessageReference.toSchema(schema4.getTitle()))
227+
.build()))
228+
.build();
229+
230+
AsyncAPI api = AsyncAPI.builder()
231+
.channels(Map.of())
232+
.operations(Map.of())
233+
.components(Components.builder()
234+
.messages(Map.of(message.getMessageId(), message))
235+
.schemas(Map.of(schema1.getTitle(), schema1, schema4.getTitle(), schema4))
236+
.build())
237+
.build();
238+
239+
AsyncApiGroup messageFilterGroup = AsyncApiGroup.builder()
240+
.messageNamesToKeep(List.of(Pattern.compile("message.*")))
241+
.build();
242+
243+
// when
244+
AsyncAPI grouped = groupingService.groupAPI(api, messageFilterGroup);
245+
246+
// then
247+
assertThat(grouped.getComponents().getSchemas()).containsKey(schema1.getTitle());
248+
assertThat(grouped.getComponents().getSchemas()).containsKey(schema4.getTitle());
249+
}
250+
216251
@Nested
217252
class ActionFiltering {
218253

springwolf-examples/springwolf-kafka-example/src/test/resources/groups/vehicles.json

Lines changed: 172 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -201,6 +201,177 @@
201201
"title": "VehicleBase",
202202
"type": "object"
203203
}
204+
},
205+
"io.github.springwolf.examples.kafka.dtos.discriminator.VehicleElectricPayloadDto": {
206+
"type": "object",
207+
"description": "Electric vehicle implementation of VehicleBase",
208+
"examples": [
209+
{
210+
"batteryCapacity": 0,
211+
"chargeTime": 0,
212+
"enginePower": {
213+
"hp": 0,
214+
"torque": 0
215+
},
216+
"powerSource": "string",
217+
"topSpeed": 0,
218+
"vehicleType": "string"
219+
}
220+
],
221+
"allOf": [
222+
{
223+
"$ref": "#/components/schemas/io.github.springwolf.examples.kafka.dtos.discriminator.VehicleBase"
224+
},
225+
{
226+
"type": "object",
227+
"properties": {
228+
"batteryCapacity": {
229+
"type": "integer",
230+
"format": "int32"
231+
},
232+
"chargeTime": {
233+
"type": "integer",
234+
"format": "int32"
235+
}
236+
}
237+
}
238+
],
239+
"x-json-schema": {
240+
"$schema": "https://json-schema.org/draft-04/schema#",
241+
"allOf": [
242+
{
243+
"description": "Demonstrates the use of discriminator for polymorphic deserialization (not publishable)",
244+
"oneOf": [
245+
{ },
246+
{
247+
"allOf": [
248+
{ },
249+
{
250+
"properties": {
251+
"fuelCapacity": {
252+
"format": "int32",
253+
"type": "integer"
254+
}
255+
},
256+
"type": "object"
257+
}
258+
],
259+
"description": "Gasoline vehicle implementation of VehicleBase",
260+
"type": "object"
261+
}
262+
],
263+
"properties": {
264+
"enginePower": {
265+
"properties": {
266+
"hp": { },
267+
"torque": { }
268+
},
269+
"title": "EnginePower",
270+
"type": "object"
271+
},
272+
"powerSource": {
273+
"type": "string"
274+
},
275+
"topSpeed": { },
276+
"vehicleType": { }
277+
},
278+
"title": "VehicleBase",
279+
"type": "object"
280+
},
281+
{
282+
"properties": {
283+
"batteryCapacity": { },
284+
"chargeTime": { }
285+
},
286+
"type": "object"
287+
}
288+
],
289+
"description": "Electric vehicle implementation of VehicleBase",
290+
"type": "object"
291+
}
292+
},
293+
"io.github.springwolf.examples.kafka.dtos.discriminator.VehicleGasolinePayloadDto": {
294+
"type": "object",
295+
"description": "Gasoline vehicle implementation of VehicleBase",
296+
"examples": [
297+
{
298+
"enginePower": {
299+
"hp": 0,
300+
"torque": 0
301+
},
302+
"fuelCapacity": 0,
303+
"powerSource": "string",
304+
"topSpeed": 0,
305+
"vehicleType": "string"
306+
}
307+
],
308+
"allOf": [
309+
{
310+
"$ref": "#/components/schemas/io.github.springwolf.examples.kafka.dtos.discriminator.VehicleBase"
311+
},
312+
{
313+
"type": "object",
314+
"properties": {
315+
"fuelCapacity": {
316+
"type": "integer",
317+
"format": "int32"
318+
}
319+
}
320+
}
321+
],
322+
"x-json-schema": {
323+
"$schema": "https://json-schema.org/draft-04/schema#",
324+
"allOf": [
325+
{
326+
"description": "Demonstrates the use of discriminator for polymorphic deserialization (not publishable)",
327+
"oneOf": [
328+
{
329+
"allOf": [
330+
{ },
331+
{
332+
"properties": {
333+
"batteryCapacity": { },
334+
"chargeTime": {
335+
"format": "int32",
336+
"type": "integer"
337+
}
338+
},
339+
"type": "object"
340+
}
341+
],
342+
"description": "Electric vehicle implementation of VehicleBase",
343+
"type": "object"
344+
},
345+
{ }
346+
],
347+
"properties": {
348+
"enginePower": {
349+
"properties": {
350+
"hp": { },
351+
"torque": { }
352+
},
353+
"title": "EnginePower",
354+
"type": "object"
355+
},
356+
"powerSource": {
357+
"type": "string"
358+
},
359+
"topSpeed": { },
360+
"vehicleType": { }
361+
},
362+
"title": "VehicleBase",
363+
"type": "object"
364+
},
365+
{
366+
"properties": {
367+
"fuelCapacity": { }
368+
},
369+
"type": "object"
370+
}
371+
],
372+
"description": "Gasoline vehicle implementation of VehicleBase",
373+
"type": "object"
374+
}
204375
}
205376
},
206377
"messages": {
@@ -242,4 +413,4 @@
242413
]
243414
}
244415
}
245-
}
416+
}

springwolf-ui/src/app/service/asyncapi/asyncapi-mapper.service.spec.ts

Lines changed: 34 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,33 +17,40 @@ describe("AsyncApiMapperService", () => {
1717
service = new AsyncApiMapperService(notificationService);
1818
});
1919

20-
for (const [plugin, testData] of Object.entries(exampleSchemas)) {
21-
it(
22-
"should be able to parse example AsyncApi.json without errors - " +
23-
plugin +
24-
" example",
25-
() => {
26-
service.toAsyncApi(testData.value);
27-
28-
expect(notificationService.showError).not.toHaveBeenCalled();
29-
expect(notificationService.showWarning).not.toHaveBeenCalled();
30-
}
31-
);
32-
}
20+
const parser = new Parser();
21+
for (const [plugin, pluginSchema] of Object.entries(exampleSchemas)) {
22+
const pluginSchemaGroups = {
23+
...pluginSchema.groups,
24+
default: pluginSchema.value,
25+
};
26+
27+
for (const [group, schema] of Object.entries(pluginSchemaGroups)) {
28+
it(
29+
"should be able to parse example AsyncApi.json without errors - " +
30+
plugin +
31+
" example and group " +
32+
group,
33+
() => {
34+
service.toAsyncApi(schema);
35+
36+
expect(notificationService.showError).not.toHaveBeenCalled();
37+
expect(notificationService.showWarning).not.toHaveBeenCalled();
38+
}
39+
);
40+
41+
it(
42+
"should be a valid AsyncApi schema - " +
43+
plugin +
44+
" example and group " +
45+
group,
46+
async () => {
47+
const diagnostics = await parser.validate(JSON.stringify(schema));
3348

34-
for (const [plugin, testData] of Object.entries(exampleSchemas)) {
35-
const parser = new Parser();
36-
it(
37-
"should be a valid AsyncApi schema - " + plugin + " example",
38-
async () => {
39-
const diagnostics = await parser.validate(
40-
JSON.stringify(testData.value)
41-
);
42-
43-
// In case you are debugging, copy the asyncapi.json to AsyncApi Studio as it displays better error messages.
44-
expect(diagnostics.map((el) => el.message)).toHaveLength(0);
45-
expect(diagnostics).toHaveLength(0);
46-
}
47-
);
49+
// In case you are debugging, copy the asyncapi.json to AsyncApi Studio as it displays better error messages.
50+
expect(diagnostics.map((el) => el.message)).toHaveLength(0);
51+
expect(diagnostics).toHaveLength(0);
52+
}
53+
);
54+
}
4855
}
4956
});

0 commit comments

Comments
 (0)