openapi-processor / openapi-processor-spring

an OpenAPI 3.0 & 3.1 interface & model java code generator for Spring Boot
https://docs.openapiprocessor.io
Apache License 2.0
40 stars 9 forks source link

upgrade the spring-validation function for Spring Boot 3.x #293

Open baoxingjie opened 1 month ago

baoxingjie commented 1 month ago

https://github.com/openapi-processor/openapi-processor-samples/blob/master/samples/spring-validation/gradle/libs.versions.toml

In Spring Boot 3.x, due to the migration of Jakarta EE to Jakarta Namespace, javax.validation has been replaced by jakarta.validation. As a result, by default, Spring Boot 3.x cannot directly use classes from the javax.validation package, such as javax.validation.ConstraintValidator and javax.validation.ConstraintValidatorContext. Their Jakarta equivalents are jakarta.validation.ConstraintValidator and jakarta.validation.ConstraintValidatorContext.

hauner commented 1 month ago

That's right,

the sample you link to is using Spring Boot 2, i.e. javax. Setting bean-validation: true in the mapping.yaml is the same as bean-validation: javax.

For Spring Boot 3 you would use bean-validation: jakarta.

Summary of the bean-validationoption: https://openapiprocessor.io/spring/processor/configuration.html#_bean_validation

The ..boot-3.. samples enable jakarta bean-validation in their corresponding mapping.yaml like this:

openapi-processor-mapping: v9

options:
  package-name: io.openapiprocessor.samples

  # use bean validations with jakarta package name
  bean-validation: jakarta
gabriel-stoicescu commented 1 week ago

Using:

openapi-processor-mapping: v9

options:
  package-name: ro.btrl.opp
  bean-validation: jakarta

with the Gradle plugin generates an error:

io.openapiprocessor.core.converter.InvalidMappingException: failed to parse 'mapping.yaml' configuration!
        at io.openapiprocessor.core.converter.OptionsConverter.readMapping(OptionsConverter.kt:78)
        at io.openapiprocessor.core.converter.OptionsConverter.convertOptions(OptionsConverter.kt:35)
        at io.openapiprocessor.spring.processor.SpringProcessor.convertOptions(SpringProcessor.kt:112)
        at io.openapiprocessor.spring.processor.SpringProcessor.run(SpringProcessor.kt:39)
        at io.openapiprocessor.spring.processor.SpringService.run(SpringService.kt:29)
        at io.openapiprocessor.gradle.OpenApiProcessorWorker.run(OpenApiProcessorWorker.java:108)
        at io.openapiprocessor.gradle.OpenApiProcessorWorker.run(OpenApiProcessorWorker.java:57)
        at io.openapiprocessor.gradle.OpenApiProcessorWorker.execute(OpenApiProcessorWorker.java:32)
        at org.gradle.workers.internal.DefaultWorkerServer.execute(DefaultWorkerServer.java:63)
        at org.gradle.workers.internal.AbstractClassLoaderWorker$1.create(AbstractClassLoaderWorker.java:54)
        at org.gradle.workers.internal.AbstractClassLoaderWorker$1.create(AbstractClassLoaderWorker.java:48)
        at org.gradle.internal.classloader.ClassLoaderUtils.executeInClassloader(ClassLoaderUtils.java:100)
        at org.gradle.workers.internal.AbstractClassLoaderWorker.executeInClassLoader(AbstractClassLoaderWorker.java:48)
        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:209)
        at org.gradle.internal.operations.DefaultBuildOperationRunner$CallableBuildOperationWorker.execute(DefaultBuildOperationRunner.java:204)
        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:166)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.execute(DefaultBuildOperationRunner.java:59)
        at org.gradle.internal.operations.DefaultBuildOperationRunner.call(DefaultBuildOperationRunner.java:53)
        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$0(DefaultWorkerExecutor.java:174)
        at java.base@17.0.11/java.util.concurrent.FutureTask.run(FutureTask.java:264)
        at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runExecution(DefaultConditionalExecutionQueue.java:194)
        at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.access$700(DefaultConditionalExecutionQueue.java:127)
        at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner$1.run(DefaultConditionalExecutionQueue.java:169)
        at org.gradle.internal.Factories$1.create(Factories.java:31)
        at org.gradle.internal.work.DefaultWorkerLeaseService.withLocks(DefaultWorkerLeaseService.java:263)
        at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:127)
        at org.gradle.internal.work.DefaultWorkerLeaseService.runAsWorkerThread(DefaultWorkerLeaseService.java:132)
        at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.runBatch(DefaultConditionalExecutionQueue.java:164)
        at org.gradle.internal.work.DefaultConditionalExecutionQueue$ExecutionRunner.run(DefaultConditionalExecutionQueue.java:133)
        at java.base@17.0.11/java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:539)
        at java.base@17.0.11/java.util.concurrent.FutureTask.run(FutureTask.java:264)
        at org.gradle.internal.concurrent.ExecutorPolicy$CatchAndRecordFailures.onExecute(ExecutorPolicy.java:64)
        at org.gradle.internal.concurrent.AbstractManagedExecutor$1.run(AbstractManagedExecutor.java:48)
        at java.base@17.0.11/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
        at java.base@17.0.11/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
        at java.base@17.0.11/java.lang.Thread.run(Thread.java:840)
Caused by: com.fasterxml.jackson.databind.exc.InvalidFormatException: Cannot deserialize value of type `boolean` from String "jakarta": only "true"/"True"/"TRUE" or "false"/"False"/"FALSE" recognized
 at [Source: (StringReader); line: 8, column: 20] (through reference chain: io.openapiprocessor.core.processor.mapping.v1.Mapping["options"]->io.openapiprocessor.core.processor.mapping.v1.Options["bean-validation"])
        at com.fasterxml.jackson.databind.exc.InvalidFormatException.from(InvalidFormatException.java:67)
        at com.fasterxml.jackson.databind.DeserializationContext.weirdStringException(DeserializationContext.java:2002)
        at com.fasterxml.jackson.databind.DeserializationContext.handleWeirdStringValue(DeserializationContext.java:1230)
        at com.fasterxml.jackson.databind.deser.std.StdDeserializer._parseBooleanPrimitive(StdDeserializer.java:451)
        at com.fasterxml.jackson.databind.deser.std.NumberDeserializers$BooleanDeserializer.deserialize(NumberDeserializers.java:225)
        at com.fasterxml.jackson.databind.deser.std.NumberDeserializers$BooleanDeserializer.deserialize(NumberDeserializers.java:200)
        at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:545)
        at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeWithErrorWrapping(BeanDeserializer.java:568)
        at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:439)
        at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1409)
        at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:352)
        at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:185)
        at com.fasterxml.jackson.databind.deser.SettableBeanProperty.deserialize(SettableBeanProperty.java:545)
        at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeWithErrorWrapping(BeanDeserializer.java:568)
        at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeUsingPropertyBased(BeanDeserializer.java:439)
        at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1409)
        at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:352)
        at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:185)
        at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:323)
        at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4825)
        at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3772)
        at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3740)
        at io.openapiprocessor.core.processor.MappingReader.read(MappingReader.kt:72)
        at io.openapiprocessor.core.converter.OptionsConverter.readMapping(OptionsConverter.kt:45)
        ... 44 more
hauner commented 1 week ago

Which version do you use? 2024.5?

From the callstack I can see that it is running into the wrong code path, i.e. it does not recognize the mapping version.

It looks like there is something wrong with the openapi-processor-mapping: v9 line...

gabriel-stoicescu commented 1 week ago

I am using the gradle plugin:

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.3.5'
    id 'io.spring.dependency-management' version '1.1.6'
    id 'io.openapiprocessor.openapi-processor' version '2023.2'
}

From what I see, 2023.2 is the latest version for the plugin. Am I wrong?

gabriel-stoicescu commented 1 week ago

I have fixed it by changing the processor version to 2024.5. I was previously trying to use 2023.2, just like the plugin version.

openapiProcessor {

    spring {
        processor 'io.openapiprocessor:openapi-processor-spring:2024.5'
        apiPath "$projectDir/src/api/petstore3-adv.yaml"
        targetDir "$projectDir/build/openapi"
        mapping "$projectDir/src/openapi-processor/mapping.yaml"
        parser "INTERNAL"
    }
}
hauner commented 1 week ago

Thanks for the feedback that it works now. Yes the latest gradle plugin version is 2023.3.

latest spring version is 2024.6. I just asked for 2024.5 because the call stack includes code that does not exist anymore in 2024.6.