openapi-processor/openapi-processor-spring

Support complex generic type response mapping procesing

MetallFoX opened this issue · 3 comments

Hi @hauner!

Processing response mapping of generic types with depth 1 works fine:

FooToBar => java.util.Map<java.lang.String, java.lang.String>
FooToBar => java.util.Map<java.lang.String,  java.lang.List>

But it doesn't work with a complex generic types such as:

FooToBar => java.util.Map<java.lang.String, java.lang.List<java.lang.String>>

The output of processing:

line 1:64 no viable alternative at input 'java.util.Map<java.lang.String,java.lang.List<'
processing failed!
io.openapiprocessor.core.processor.mapping.v2.parser.antlr.MappingException: failed to parse mapping: FooToBar => java.util.Map<java.lang.String, java.lang.List<java.lang.String>>
	at io.openapiprocessor.core.processor.mapping.v2.parser.antlr.ParserKt.parseMapping(Parser.kt:27)
	at io.openapiprocessor.core.processor.mapping.v2.MappingConverter.convertType(MappingConverter.kt:91)
	at io.openapiprocessor.core.processor.mapping.v2.MappingConverter.convert(MappingConverter.kt:44)
	at io.openapiprocessor.core.processor.MappingConverter.convert(MappingConverter.kt:29)
	at io.openapiprocessor.core.converter.OptionsConverter.readMapping(OptionsConverter.kt:69)
	at io.openapiprocessor.core.converter.OptionsConverter.convertOptions(OptionsConverter.kt:34)
	at io.openapiprocessor.spring.processor.SpringProcessor.convertOptions(SpringProcessor.kt:116)
	at io.openapiprocessor.spring.processor.SpringProcessor.run(SpringProcessor.kt:45)
	at io.openapiprocessor.gradle.OpenApiProcessorWorker.run(OpenApiProcessorWorker.java:36)
	at io.openapiprocessor.gradle.OpenApiProcessorWorker.execute(OpenApiProcessorWorker.java:25)
	at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63)
	at org.gradle.workers.internal.AbstractClassLoaderWorker$1.create(AbstractClassLoaderWorker.java:49)
	at org.gradle.workers.internal.AbstractClassLoaderWorker$1.create(AbstractClassLoaderWorker.java:43)
	at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:97)
	at org.gradle.workers.internal.AbstractClassLoaderWorker.executeInClassLoader(AbstractClassLoaderWorker.java:43)
	at org.gradle.workers.internal.IsolatedClassloaderWorker.run(IsolatedClassloaderWorker.java:49)
	at org.gradle.workers.internal.IsolatedClassloaderWorker.run(IsolatedClassloaderWorker.java:30)
	at org.gradle.workers.internal.IsolatedClassloaderWorkerFactory$1.lambda$execute$0(IsolatedClassloaderWorkerFactory.java:57)
	at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:44)
	at org.gradle.workers.internal.AbstractWorker$1.call(AbstractWorker.java:41)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:199)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:66)
	at org.gradle.internal.operations.DefaultBuildOperationRunner$2.execute(DefaultBuildOperationRunner.java:59)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:157)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
	at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
	at org.gradle.internal.operations.DefaultBuildOperationExecutor.call(DefaultBuildOperationExecutor.java:73)
	at org.gradle.workers.internal.AbstractWorker.executeWrappedInBuildOperation(AbstractWorker.java:41)
	at org.gradle.workers.internal.IsolatedClassloaderWorkerFactory$1.execute(IsolatedClassloaderWorkerFactory.java:49)
	at org.gradle.workers.internal.DefaultWorkerExecutor.lambda$submitWork$2(DefaultWorkerExecutor.java:205)
	at java.base@18.0.1.1/java.util.concurrent.FutureTask.run(FutureTask.java:264)
	at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:187)
	at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:120)
	at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:162)
	at org.gradle.internal.Factories$1.create(Factories.java:31)
	at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:270)
	at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:119)
	at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:124)
	at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:157)
	at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:126)
	at java.base@18.0.1.1/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539)
	at java.base@18.0.1.1/java.util.concurrent.FutureTask.run(FutureTask.java:264)
	at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
	at org.gradle.internal.concurrent.ManagedExecutorImpl$1.run(ManagedExecutorImpl.java:48)
	at java.base@18.0.1.1/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
	at java.base@18.0.1.1/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
	at java.base@18.0.1.1/java.lang.Thread.run(Thread.java:833)
Caused by: io.openapiprocessor.core.processor.mapping.v2.parser.antlr.MappingParserException: no viable alternative at input 'java.util.Map<java.lang.String,java.lang.List<'
	at io.openapiprocessor.core.processor.mapping.v2.parser.antlr.MappingErrorListener.syntaxError(MappingErrorListener.kt:22)
	at org.antlr.v4.runtime.ProxyErrorListener.syntaxError(ProxyErrorListener.java:41)
	at org.antlr.v4.runtime.Parser.notifyErrorListeners(Parser.java:544)
	at org.antlr.v4.runtime.DefaultErrorStrategy.reportNoViableAlternative(DefaultErrorStrategy.java:310)
	at org.antlr.v4.runtime.DefaultErrorStrategy.reportError(DefaultErrorStrategy.java:136)
	at io.openapiprocessor.core.processor.mapping.v2.parser.antlr.MappingParser.targetType(MappingParser.java:524)
	at io.openapiprocessor.core.processor.mapping.v2.parser.antlr.MappingParser.map(MappingParser.java:276)
	at io.openapiprocessor.core.processor.mapping.v2.parser.antlr.MappingParser.mapping(MappingParser.java:154)
	at io.openapiprocessor.core.processor.mapping.v2.parser.antlr.ParserKt.parseMapping(Parser.kt:22)
	... 47 more
Caused by: org.antlr.v4.runtime.NoViableAltException
	at org.antlr.v4.runtime.atn.ParserATNSimulator.noViableAlt(ParserATNSimulator.java:2031)
	at org.antlr.v4.runtime.atn.ParserATNSimulator.execATN(ParserATNSimulator.java:470)
	at org.antlr.v4.runtime.atn.ParserATNSimulator.adaptivePredict(ParserATNSimulator.java:396)
	at io.openapiprocessor.core.processor.mapping.v2.parser.antlr.MappingParser.targetType(MappingParser.java:510)
	... 50 more
Caused by: io.openapiprocessor.core.processor.mapping.v2.parser.antlr.MappingParserException: no viable alternative at input 'java.util.Map<java.lang.String,java.lang.List<'

Caused by: org.antlr.v4.runtime.NoViableAltException


Execution failed for task '...:processSpring'.
> A failure occurred while executing io.openapiprocessor.gradle.OpenApiProcessorWorker
   > failed to parse mapping: FooToBar => java.util.Map<java.lang.String, java.lang.List<java.lang.String>>

It would be useful to add complex generic types support.

According to swagger reference to openapi specification, the correct way to define Dictionary or Map is to specify additionalProperties section.

SpringDoc annotation processing generates something like this for the Map<String, List<String>> too:

  /foo/bar:
    get:
      tags:
        - foo
        - bar
      summary: foobar
      responses:
        '200':
          description: OK
          content:
            '*/*':
              schema:
                type: object
                additionalProperties:
                  type: array
                  items:
                    type: string

I've tried several definitions with no result and didn't find additionalProperties handling in the code either.

It might be better to add additionalProperties support instead of using custom mapping workarounds for such cases.

at the moment, only one level of generic is supported in the mapping (I think it is in the docs). I didn't spent any time yet to find out what it would take to support more levels.

yes, there is no code that checks additionalProperties. So let's make this a feature request :-)