springwolf / springwolf-core

Automated documentation for event-driven applications built with Spring Boot
https://www.springwolf.dev
Apache License 2.0
242 stars 67 forks source link

Schema introspection of Map<String,Foo> #838

Closed nschwalbe closed 3 weeks ago

nschwalbe commented 1 month ago

Describe the bug It seems that a map key and value is not introspected. In the UI schema is object and not Foo and no examples are shown.

Dependencies and versions used springwolf amqp 1.4.0

Code example

@Schema(description = "Some description")
class Foo {
   @Schema(example = "foo")
   private String name;
   @Schema(example = "bar")
   private String title;
}

class MyEvent {
  @Schema(description = "a map of ...")
  private Map<String, Foo> map;
}

Renders to Example Message:

{
   "map": { }
}

Am I missing something? I tried Schema with additionalProperties but that didn't change anything. List<Foo> works, so I expected that Map<String,Foo> should also work.

timonback commented 1 month ago

Hi @nschwalbe, thank you for the report.

We indeed did not cover maps in the current implementation, but this will be added in https://github.com/springwolf/springwolf-core/pull/839

github-actions[bot] commented 1 month ago

The change is staged for release and will be part of the next release.

If you want to try and verify it in your application today, use the latest 1.X.0-SNAPSHOT build as described in our README.md > Testing SNAPSHOT version

Thank you for the report/contribution!

nschwalbe commented 1 month ago

Hi @timonback, thanks for your quick response! I tested it with the snapshot version and it works for the simple case Map<String,Foo> but not for collections e.g. Map<String,Set<Foo>>

I got following exception:

io.github.springwolf.core.asyncapi.components.examples.walkers.SchemaWalker$ExampleGeneratingException: Array schema does not have a name: class ArraySchema {
    class Schema {
        type: array
        format: null
        $ref: null
        description: A map of projectIds and a list of users which were added to the project
        title: null
        multipleOf: null
        maximum: null
        exclusiveMaximum: null
        minimum: null
        exclusiveMinimum: null
        maxLength: null
        minLength: null
        pattern: null
        maxItems: null
        minItems: null
        uniqueItems: true
        maxProperties: null
        minProperties: null
        required: null
        not: null
        properties: null
        additionalProperties: null
        nullable: null
        readOnly: null
        writeOnly: null
        example: null
        externalDocs: null
        deprecated: null
        discriminator: null
        xml: null
    }
}
    at io.github.springwolf.core.asyncapi.components.examples.walkers.DefaultSchemaWalker.lambda$buildArrayExample$4(DefaultSchemaWalker.java:173) ~[springwolf-core-1.5.0-20240713.181812-9.jar:na]
    at java.base/java.util.Optional.orElseThrow(Optional.java:403) ~[na:na]
    at io.github.springwolf.core.asyncapi.components.examples.walkers.DefaultSchemaWalker.lambda$buildArrayExample$5(DefaultSchemaWalker.java:172) ~[springwolf-core-1.5.0-20240713.181812-9.jar:na]
    at java.base/java.util.Optional.map(Optional.java:260) ~[na:na]
    at io.github.springwolf.core.asyncapi.components.examples.walkers.DefaultSchemaWalker.buildArrayExample(DefaultSchemaWalker.java:171) ~[springwolf-core-1.5.0-20240713.181812-9.jar:na]
    at io.github.springwolf.core.asyncapi.components.examples.walkers.DefaultSchemaWalker.buildExample(DefaultSchemaWalker.java:87) ~[springwolf-core-1.5.0-20240713.181812-9.jar:na]
    at io.github.springwolf.core.asyncapi.components.examples.walkers.DefaultSchemaWalker.lambda$buildPropertyExampleListFromSchema$7(DefaultSchemaWalker.java:286) ~[springwolf-core-1.5.0-20240713.181812-9.jar:na]
    at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) ~[na:na]
    at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133) ~[na:na]
    at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1939) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:575) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.evaluateToArrayNode(AbstractPipeline.java:260) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:616) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:622) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline.toList(ReferencePipeline.java:627) ~[na:na]
    at io.github.springwolf.core.asyncapi.components.examples.walkers.DefaultSchemaWalker.buildPropertyExampleListFromSchema(DefaultSchemaWalker.java:294) ~[springwolf-core-1.5.0-20240713.181812-9.jar:na]
    at io.github.springwolf.core.asyncapi.components.examples.walkers.DefaultSchemaWalker.buildMapExample(DefaultSchemaWalker.java:249) ~[springwolf-core-1.5.0-20240713.181812-9.jar:na]
    at io.github.springwolf.core.asyncapi.components.examples.walkers.DefaultSchemaWalker.buildFromObjectSchema(DefaultSchemaWalker.java:234) ~[springwolf-core-1.5.0-20240713.181812-9.jar:na]
    at io.github.springwolf.core.asyncapi.components.examples.walkers.DefaultSchemaWalker.buildExample(DefaultSchemaWalker.java:91) ~[springwolf-core-1.5.0-20240713.181812-9.jar:na]
    at io.github.springwolf.core.asyncapi.components.examples.walkers.DefaultSchemaWalker.lambda$buildPropertyExampleListFromSchema$7(DefaultSchemaWalker.java:286) ~[springwolf-core-1.5.0-20240713.181812-9.jar:na]
    at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) ~[na:na]
    at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133) ~[na:na]
    at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1939) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:575) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.evaluateToArrayNode(AbstractPipeline.java:260) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:616) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:622) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline.toList(ReferencePipeline.java:627) ~[na:na]
    at io.github.springwolf.core.asyncapi.components.examples.walkers.DefaultSchemaWalker.buildPropertyExampleListFromSchema(DefaultSchemaWalker.java:294) ~[springwolf-core-1.5.0-20240713.181812-9.jar:na]
    at io.github.springwolf.core.asyncapi.components.examples.walkers.DefaultSchemaWalker.buildFromObjectSchemaWithProperties(DefaultSchemaWalker.java:259) ~[springwolf-core-1.5.0-20240713.181812-9.jar:na]
    at io.github.springwolf.core.asyncapi.components.examples.walkers.DefaultSchemaWalker.buildFromObjectSchema(DefaultSchemaWalker.java:226) ~[springwolf-core-1.5.0-20240713.181812-9.jar:na]
    at io.github.springwolf.core.asyncapi.components.examples.walkers.DefaultSchemaWalker.buildExample(DefaultSchemaWalker.java:91) ~[springwolf-core-1.5.0-20240713.181812-9.jar:na]
    at io.github.springwolf.core.asyncapi.components.examples.walkers.DefaultSchemaWalker.fromSchema(DefaultSchemaWalker.java:64) ~[springwolf-core-1.5.0-20240713.181812-9.jar:na]
    at io.github.springwolf.core.asyncapi.components.postprocessors.ExampleGeneratorPostProcessor.process(ExampleGeneratorPostProcessor.java:26) ~[springwolf-core-1.5.0-20240713.181812-9.jar:na]
    at io.github.springwolf.core.asyncapi.schemas.SwaggerSchemaService.postProcessSchema(SwaggerSchemaService.java:210) ~[springwolf-core-1.5.0-20240713.181812-9.jar:na]
    at io.github.springwolf.core.asyncapi.schemas.SwaggerSchemaService.extractSchema(SwaggerSchemaService.java:89) ~[springwolf-core-1.5.0-20240713.181812-9.jar:na]
    at io.github.springwolf.core.asyncapi.components.DefaultComponentsService.registerSchema(DefaultComponentsService.java:51) ~[springwolf-core-1.5.0-20240713.181812-9.jar:na]
    at io.github.springwolf.core.asyncapi.scanners.common.payload.internal.PayloadService.buildSchema(PayloadService.java:35) ~[springwolf-core-1.5.0-20240713.181812-9.jar:na]
    at io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadAsyncOperationService.lambda$extractSchema$0(PayloadAsyncOperationService.java:25) ~[springwolf-core-1.5.0-20240713.181812-9.jar:na]
    at java.base/java.util.Optional.map(Optional.java:260) ~[na:na]
    at io.github.springwolf.core.asyncapi.scanners.common.payload.PayloadAsyncOperationService.extractSchema(PayloadAsyncOperationService.java:25) ~[springwolf-core-1.5.0-20240713.181812-9.jar:na]
    at io.github.springwolf.core.asyncapi.scanners.common.AsyncAnnotationScanner.buildMessage(AsyncAnnotationScanner.java:94) ~[springwolf-core-1.5.0-20240713.181812-9.jar:na]
    at io.github.springwolf.core.asyncapi.scanners.common.AsyncAnnotationScanner.buildOperation(AsyncAnnotationScanner.java:82) ~[springwolf-core-1.5.0-20240713.181812-9.jar:na]
    at io.github.springwolf.core.asyncapi.scanners.operations.annotations.AsyncAnnotationOperationsScanner.buildOperation(AsyncAnnotationOperationsScanner.java:68) ~[springwolf-core-1.5.0-20240713.181812-9.jar:na]
    at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) ~[na:na]
    at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) ~[na:na]
    at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.accept(ForEachOps.java:184) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:197) ~[na:na]
    at java.base/java.util.Iterator.forEachRemaining(Iterator.java:133) ~[na:na]
    at java.base/java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1939) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) ~[na:na]
    at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) ~[na:na]
    at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.java:276) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline$15$1.accept(ReferencePipeline.java:541) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:179) ~[na:na]
    at java.base/java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:1024) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) ~[na:na]
    at java.base/java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151) ~[na:na]
    at java.base/java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:596) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.java:276) ~[na:na]
    at java.base/java.util.HashMap$KeySpliterator.forEachRemaining(HashMap.java:1715) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:509) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:499) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:575) ~[na:na]
    at java.base/java.util.stream.AbstractPipeline.evaluateToArrayNode(AbstractPipeline.java:260) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:616) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline.toArray(ReferencePipeline.java:622) ~[na:na]
    at java.base/java.util.stream.ReferencePipeline.toList(ReferencePipeline.java:627) ~[na:na]
    at io.github.springwolf.core.asyncapi.scanners.operations.annotations.AsyncAnnotationOperationsScanner.scan(AsyncAnnotationOperationsScanner.java:52) ~[springwolf-core-1.5.0-20240713.181812-9.jar:na]
    at io.github.springwolf.core.asyncapi.operations.DefaultOperationsService.findOperations(DefaultOperationsService.java:34) ~[springwolf-core-1.5.0-20240713.181812-9.jar:na]
    at io.github.springwolf.core.asyncapi.DefaultAsyncApiService.initAsyncAPI(DefaultAsyncApiService.java:71) ~[springwolf-core-1.5.0-20240713.181812-9.jar:na]
    at io.github.springwolf.core.asyncapi.DefaultAsyncApiService.getAsyncAPI(DefaultAsyncApiService.java:42) ~[springwolf-core-1.5.0-20240713.181812-9.jar:na]
    at io.github.springwolf.core.SpringwolfInitApplicationListener.onApplicationEvent(SpringwolfInitApplicationListener.java:31) ~[springwolf-core-1.5.0-20240713.181812-9.jar:na]
    at io.github.springwolf.core.SpringwolfInitApplicationListener.onApplicationEvent(SpringwolfInitApplicationListener.java:17) ~[springwolf-core-1.5.0-20240713.181812-9.jar:na]
    at org.springframework.context.event.SimpleApplicationEventMulticaster.doInvokeListener(SimpleApplicationEventMulticaster.java:185) ~[spring-context-6.1.5.jar:6.1.5]
    at org.springframework.context.event.SimpleApplicationEventMulticaster.invokeListener(SimpleApplicationEventMulticaster.java:178) ~[spring-context-6.1.5.jar:6.1.5]
    at org.springframework.context.event.SimpleApplicationEventMulticaster.multicastEvent(SimpleApplicationEventMulticaster.java:156) ~[spring-context-6.1.5.jar:6.1.5]
    at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:451) ~[spring-context-6.1.5.jar:6.1.5]
    at org.springframework.context.support.AbstractApplicationContext.publishEvent(AbstractApplicationContext.java:384) ~[spring-context-6.1.5.jar:6.1.5]
    at org.springframework.boot.context.event.EventPublishingRunListener.ready(EventPublishingRunListener.java:109) ~[spring-boot-3.2.4.jar:3.2.4]
    at org.springframework.boot.SpringApplicationRunListeners.lambda$ready$6(SpringApplicationRunListeners.java:80) ~[spring-boot-3.2.4.jar:3.2.4]
    at java.base/java.lang.Iterable.forEach(Iterable.java:75) ~[na:na]
    at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:118) ~[spring-boot-3.2.4.jar:3.2.4]
    at org.springframework.boot.SpringApplicationRunListeners.doWithListeners(SpringApplicationRunListeners.java:112) ~[spring-boot-3.2.4.jar:3.2.4]
    at org.springframework.boot.SpringApplicationRunListeners.ready(SpringApplicationRunListeners.java:80) ~[spring-boot-3.2.4.jar:3.2.4]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:348) ~[spring-boot-3.2.4.jar:3.2.4]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1354) ~[spring-boot-3.2.4.jar:3.2.4]
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1343) ~[spring-boot-3.2.4.jar:3.2.4]
    at eu.aconium.docs.amqp.AmqpDocsApplication.main(AmqpDocsApplication.java:11) ~[classes/:na]
timonback commented 1 month ago

Thanks for the update @nschwalbe

We tend to err on the side of what is possible and tend to be more restrictive. Since you provided this example, we can add it to our test suite and fill the gaps of possible combinations of payloads :)

I created https://github.com/springwolf/springwolf-core/pull/841, but will have @sam0r040 to have a look at it to confirm that the original restriction for xml generation is still required.

timonback commented 1 month ago

Hi @nschwalbe follow up has been merged as well.

Can you verify the new Snapshot that will be available in the next 10 minutes?

nschwalbe commented 1 month ago

@timonback Yes, it works! :+1:

github-actions[bot] commented 3 weeks ago

The change is available in the latest release. 🎉

Thank you for the report/contribution and making Springwolf better!