diff --git a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/grouping/GroupingService.java b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/grouping/GroupingService.java index 046bf251e..d4bbf7e18 100644 --- a/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/grouping/GroupingService.java +++ b/springwolf-core/src/main/java/io/github/springwolf/core/asyncapi/grouping/GroupingService.java @@ -26,6 +26,7 @@ import java.util.Objects; import java.util.Set; import java.util.stream.Collectors; +import java.util.stream.Stream; @AllArgsConstructor public class GroupingService { @@ -187,19 +188,31 @@ private void markSchemas(AsyncAPI fullAsyncApi, MarkingContext markingContext, S .forEach(schemaEntry -> { markingContext.markedComponentSchemaIds.add(schemaEntry.getKey()); - if (schemaEntry.getValue().getProperties() != null) { - Set nestedSchemas = findUnmarkedNestedSchemas(markingContext, schemaEntry.getValue()); - if (!nestedSchemas.isEmpty()) { - markSchemas(fullAsyncApi, markingContext, nestedSchemas); - } + Set nestedSchemas = findUnmarkedNestedSchemas(markingContext, schemaEntry.getValue()); + if (!nestedSchemas.isEmpty()) { + markSchemas(fullAsyncApi, markingContext, nestedSchemas); } }); } private static Set findUnmarkedNestedSchemas(MarkingContext markingContext, SchemaObject schema) { - return schema.getProperties().values().stream() - .filter(el -> el instanceof ComponentSchema) - .map(el -> (ComponentSchema) el) + final Stream propertySchemas; + if (schema.getProperties() != null) { + propertySchemas = schema.getProperties().values().stream() + .filter(el -> el instanceof ComponentSchema) + .map(el -> (ComponentSchema) el); + } else { + propertySchemas = Stream.empty(); + } + + Stream referencedSchemas = Stream.of(schema.getAllOf(), schema.getAnyOf(), schema.getOneOf()) + .filter(Objects::nonNull) + .flatMap(List::stream); + + Stream referencedSchemaElements = + Stream.of(schema.getNot(), schema.getItems()).filter(Objects::nonNull); + + return Stream.concat(propertySchemas, Stream.concat(referencedSchemas, referencedSchemaElements)) .map(ComponentSchema::getReference) .filter(Objects::nonNull) .map(MessageReference::getRef) diff --git a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/grouping/GroupingServiceTest.java b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/grouping/GroupingServiceTest.java index 44f6e491e..f258963c5 100644 --- a/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/grouping/GroupingServiceTest.java +++ b/springwolf-core/src/test/java/io/github/springwolf/core/asyncapi/grouping/GroupingServiceTest.java @@ -8,6 +8,7 @@ import io.github.springwolf.asyncapi.v3.model.channel.message.MessageObject; import io.github.springwolf.asyncapi.v3.model.channel.message.MessagePayload; import io.github.springwolf.asyncapi.v3.model.channel.message.MessageReference; +import io.github.springwolf.asyncapi.v3.model.components.ComponentSchema; import io.github.springwolf.asyncapi.v3.model.components.Components; import io.github.springwolf.asyncapi.v3.model.info.Info; import io.github.springwolf.asyncapi.v3.model.operation.Operation; @@ -213,6 +214,40 @@ void shouldCopyEverythingForEmptyFilter() { assertThat(grouped).isEqualTo(fullApi); } + @Test + void shouldResolveReferencedSchemas1FromSchema4() { + SchemaObject schema4 = SchemaObject.builder() + .title("Schema4") + .oneOf(List.of(ComponentSchema.of(MessageReference.toSchema(schema1.getTitle())))) + .build(); + MessageObject message = MessageObject.builder() + .messageId("messageId1") + .payload(MessagePayload.of(MultiFormatSchema.builder() + .schema(MessageReference.toSchema(schema4.getTitle())) + .build())) + .build(); + + AsyncAPI api = AsyncAPI.builder() + .channels(Map.of()) + .operations(Map.of()) + .components(Components.builder() + .messages(Map.of(message.getMessageId(), message)) + .schemas(Map.of(schema1.getTitle(), schema1, schema4.getTitle(), schema4)) + .build()) + .build(); + + AsyncApiGroup messageFilterGroup = AsyncApiGroup.builder() + .messageNamesToKeep(List.of(Pattern.compile("message.*"))) + .build(); + + // when + AsyncAPI grouped = groupingService.groupAPI(api, messageFilterGroup); + + // then + assertThat(grouped.getComponents().getSchemas()).containsKey(schema1.getTitle()); + assertThat(grouped.getComponents().getSchemas()).containsKey(schema4.getTitle()); + } + @Nested class ActionFiltering { diff --git a/springwolf-examples/springwolf-kafka-example/src/test/resources/groups/vehicles.json b/springwolf-examples/springwolf-kafka-example/src/test/resources/groups/vehicles.json index 600fe7e9b..c12aae7ee 100644 --- a/springwolf-examples/springwolf-kafka-example/src/test/resources/groups/vehicles.json +++ b/springwolf-examples/springwolf-kafka-example/src/test/resources/groups/vehicles.json @@ -201,6 +201,177 @@ "title": "VehicleBase", "type": "object" } + }, + "io.github.springwolf.examples.kafka.dtos.discriminator.VehicleElectricPayloadDto": { + "type": "object", + "description": "Electric vehicle implementation of VehicleBase", + "examples": [ + { + "batteryCapacity": 0, + "chargeTime": 0, + "enginePower": { + "hp": 0, + "torque": 0 + }, + "powerSource": "string", + "topSpeed": 0, + "vehicleType": "string" + } + ], + "allOf": [ + { + "$ref": "#/components/schemas/io.github.springwolf.examples.kafka.dtos.discriminator.VehicleBase" + }, + { + "type": "object", + "properties": { + "batteryCapacity": { + "type": "integer", + "format": "int32" + }, + "chargeTime": { + "type": "integer", + "format": "int32" + } + } + } + ], + "x-json-schema": { + "$schema": "https://json-schema.org/draft-04/schema#", + "allOf": [ + { + "description": "Demonstrates the use of discriminator for polymorphic deserialization (not publishable)", + "oneOf": [ + { }, + { + "allOf": [ + { }, + { + "properties": { + "fuelCapacity": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + } + ], + "description": "Gasoline vehicle implementation of VehicleBase", + "type": "object" + } + ], + "properties": { + "enginePower": { + "properties": { + "hp": { }, + "torque": { } + }, + "title": "EnginePower", + "type": "object" + }, + "powerSource": { + "type": "string" + }, + "topSpeed": { }, + "vehicleType": { } + }, + "title": "VehicleBase", + "type": "object" + }, + { + "properties": { + "batteryCapacity": { }, + "chargeTime": { } + }, + "type": "object" + } + ], + "description": "Electric vehicle implementation of VehicleBase", + "type": "object" + } + }, + "io.github.springwolf.examples.kafka.dtos.discriminator.VehicleGasolinePayloadDto": { + "type": "object", + "description": "Gasoline vehicle implementation of VehicleBase", + "examples": [ + { + "enginePower": { + "hp": 0, + "torque": 0 + }, + "fuelCapacity": 0, + "powerSource": "string", + "topSpeed": 0, + "vehicleType": "string" + } + ], + "allOf": [ + { + "$ref": "#/components/schemas/io.github.springwolf.examples.kafka.dtos.discriminator.VehicleBase" + }, + { + "type": "object", + "properties": { + "fuelCapacity": { + "type": "integer", + "format": "int32" + } + } + } + ], + "x-json-schema": { + "$schema": "https://json-schema.org/draft-04/schema#", + "allOf": [ + { + "description": "Demonstrates the use of discriminator for polymorphic deserialization (not publishable)", + "oneOf": [ + { + "allOf": [ + { }, + { + "properties": { + "batteryCapacity": { }, + "chargeTime": { + "format": "int32", + "type": "integer" + } + }, + "type": "object" + } + ], + "description": "Electric vehicle implementation of VehicleBase", + "type": "object" + }, + { } + ], + "properties": { + "enginePower": { + "properties": { + "hp": { }, + "torque": { } + }, + "title": "EnginePower", + "type": "object" + }, + "powerSource": { + "type": "string" + }, + "topSpeed": { }, + "vehicleType": { } + }, + "title": "VehicleBase", + "type": "object" + }, + { + "properties": { + "fuelCapacity": { } + }, + "type": "object" + } + ], + "description": "Gasoline vehicle implementation of VehicleBase", + "type": "object" + } } }, "messages": { @@ -242,4 +413,4 @@ ] } } -} +} \ No newline at end of file diff --git a/springwolf-ui/src/app/service/asyncapi/asyncapi-mapper.service.spec.ts b/springwolf-ui/src/app/service/asyncapi/asyncapi-mapper.service.spec.ts index ab907ebe8..a19a4e93f 100644 --- a/springwolf-ui/src/app/service/asyncapi/asyncapi-mapper.service.spec.ts +++ b/springwolf-ui/src/app/service/asyncapi/asyncapi-mapper.service.spec.ts @@ -17,33 +17,40 @@ describe("AsyncApiMapperService", () => { service = new AsyncApiMapperService(notificationService); }); - for (const [plugin, testData] of Object.entries(exampleSchemas)) { - it( - "should be able to parse example AsyncApi.json without errors - " + - plugin + - " example", - () => { - service.toAsyncApi(testData.value); - - expect(notificationService.showError).not.toHaveBeenCalled(); - expect(notificationService.showWarning).not.toHaveBeenCalled(); - } - ); - } + const parser = new Parser(); + for (const [plugin, pluginSchema] of Object.entries(exampleSchemas)) { + const pluginSchemaGroups = { + ...pluginSchema.groups, + default: pluginSchema.value, + }; + + for (const [group, schema] of Object.entries(pluginSchemaGroups)) { + it( + "should be able to parse example AsyncApi.json without errors - " + + plugin + + " example and group " + + group, + () => { + service.toAsyncApi(schema); + + expect(notificationService.showError).not.toHaveBeenCalled(); + expect(notificationService.showWarning).not.toHaveBeenCalled(); + } + ); + + it( + "should be a valid AsyncApi schema - " + + plugin + + " example and group " + + group, + async () => { + const diagnostics = await parser.validate(JSON.stringify(schema)); - for (const [plugin, testData] of Object.entries(exampleSchemas)) { - const parser = new Parser(); - it( - "should be a valid AsyncApi schema - " + plugin + " example", - async () => { - const diagnostics = await parser.validate( - JSON.stringify(testData.value) - ); - - // In case you are debugging, copy the asyncapi.json to AsyncApi Studio as it displays better error messages. - expect(diagnostics.map((el) => el.message)).toHaveLength(0); - expect(diagnostics).toHaveLength(0); - } - ); + // In case you are debugging, copy the asyncapi.json to AsyncApi Studio as it displays better error messages. + expect(diagnostics.map((el) => el.message)).toHaveLength(0); + expect(diagnostics).toHaveLength(0); + } + ); + } } });