Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

swagger-gradle-plugin - Support for ModelConverter #2971

Closed
peetzen opened this issue Sep 29, 2018 · 6 comments
Closed

swagger-gradle-plugin - Support for ModelConverter #2971

peetzen opened this issue Sep 29, 2018 · 6 comments

Comments

@peetzen
Copy link

peetzen commented Sep 29, 2018

I am experimenting with the new Swagger Gradle plugin and it works very well!
One important feature I am missing, is the possibility to add custom ModelConverter through the gradle resolve{} task configuration. There are configuration options for filterClass, readerClass and scannerClass but not for (multiple) ModelConverter classes.

Background:
I am using dropwizard with PropertyNamingStrategy.SNAKE_CASE for the ObjectMapper and want to generate a static openapi.yaml file through the gradle resolve{} task.

Code example:
Something that I am trying to achieve through the gradle task configuration could be similar to:

ModelConverters.getInstance().addConverter(new SnakeCaseConverter());

Custom Converter:

public class SnakeCaseConverter extends ModelResolver {

    public SnakeCaseConverter() {
        super(createMaper());
    }

    private static ObjectMapper createMaper() {
        ObjectMapper mapper = new ObjectMapper();
        mapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
        return mapper;
    }
}
frantuma added a commit that referenced this issue Oct 7, 2018
refs #2971 - adds modelConverterClasses and objectMapperProcessorClass to config
@frantuma
Copy link
Member

frantuma commented Oct 7, 2018

#2973 (available in 2.0.6-SNAPSHOT) introduces config properties objectMapperProcessorClass and modelConverterClasses, also available in maven and gradle plugins.

In your case objectMapperProcessorClass would match your needs: you would provide an implementation of io.swagger.v3.oas.integration.api.ObjectMapperProcessor interface, allowing to customize Jackson object mapper in use, and set objectMapperProcessorClass to the qualified class name of the processor implemenation class (see also https://github.com/swagger-api/swagger-core/wiki/Swagger-2.X---Integration-and-Configuration#configuration-properties)

something like:

public class SampleObjectMapperProcessor implements ObjectMapperProcessor {
    @Override
    public void processJsonObjectMapper(ObjectMapper mapper) {
        processYamlObjectMapper(mapper);
    }

    @Override
    public void processYamlObjectMapper(ObjectMapper mapper) {
        mapper.setPropertyNamingStrategy(PropertyNamingStrategy.SNAKE_CASE);
    }
}

this will apply properties to objectMapper used by default by ModelResolver and other swagger components (e.g. the reader, etc). If you only want to process the mapper used by ModelResolver to resolve schemas, applying a converter like the one you shared, via the modelConverterClasses will do the trick.

closing ticket, please reopen if you're still experiencing issues

@frantuma frantuma closed this as completed Oct 7, 2018
@peetzen
Copy link
Author

peetzen commented Nov 28, 2018

@frantuma I gave it a try with the latest released version 2.0.6 and I run into a problem when running the resolve task.

I have implemented the ObjectMapperProcessor in default gradle buildSrc project.
In my main project I specify the ObjectMapper class as part of the resolve configuration:
objectMapperProcessorClass = 'de.peetzen.build.swagger.SnakeCaseObjectMapperProcessor'.

When I run the resolve task I get an ClassNotFoundException.

07:35:20.620 [Task worker for ':'] ERROR io.swagger.v3.oas.integration.GenericOpenApiContext - error initializing context: de.peetzen.build.swagger.SnakeCaseObjectMapperProcessor
java.lang.ClassNotFoundException: de.peetzen.build.swagger.SnakeCaseObjectMapperProcessor
	at java.net.URLClassLoader.findClass(URLClassLoader.java:381)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:424)
	at java.lang.ClassLoader.loadClass(ClassLoader.java:357)
	at io.swagger.v3.oas.integration.GenericOpenApiContext.buildObjectMapperProcessor(GenericOpenApiContext.java:267)
	at io.swagger.v3.oas.integration.GenericOpenApiContext.init(GenericOpenApiContext.java:361)
	at io.swagger.v3.oas.integration.GenericOpenApiContext.init(GenericOpenApiContext.java:28)
	at io.swagger.v3.jaxrs2.integration.JaxrsOpenApiContextBuilder.buildContext(JaxrsOpenApiContextBuilder.java:44)
	at io.swagger.v3.jaxrs2.integration.SwaggerLoader.resolve(SwaggerLoader.java:214)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)
	at io.swagger.v3.plugins.gradle.tasks.ResolveTask.resolve(ResolveTask.java:388)
	at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
	at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.lang.reflect.Method.invoke(Method.java:498)

I would expect that the resolve task uses this special gradle buildSrc project by default to search for the class to load. How is this supposed to work? How do I have to define the dependency for the implementing class to be able to use it?

@frantuma
Copy link
Member

Please add the buildSrc classpath to classpath parameter of resolveTask

@peetzen
Copy link
Author

peetzen commented Nov 28, 2018

Thanks! I got a little bit further. Now I have the problem, that the change to the mapper actually affects the swagger internal json serialization schema as well and not just the payload data.

The swagger keyword additionalProperties (and any other swagger schema related parts) should not be handled with the same mapper instance, I guess. This is happening for me and causes an exception due to the affect on the keyword additional_properties. I use your suggested SampleObjectMapperProcessor code.

        at io.swagger.v3.plugins.gradle.tasks.ResolveTask.resolve(ResolveTask.java:388)
        ... 43 more
Caused by: java.lang.Exception: Error resolving API specification: additionalProperties must be either a Boolean or a Schema instance
 at [Source: UNKNOWN; line: -1, column: -1] (through reference chain: io.swagger.v3.oas.models.media.ObjectSchema["additional_properties"])
        at io.swagger.v3.jaxrs2.integration.SwaggerLoader.resolve(SwaggerLoader.java:240)
        ... 44 more
Caused by: java.lang.IllegalArgumentException: additionalProperties must be either a Boolean or a Schema instance
 at [Source: UNKNOWN; line: -1, column: -1] (through reference chain: io.swagger.v3.oas.models.media.ObjectSchema["additional_properties"])
        at com.fasterxml.jackson.databind.ObjectMapper._convert(ObjectMapper.java:3738)
        at com.fasterxml.jackson.databind.ObjectMapper.convertValue(ObjectMapper.java:3656)
        at io.swagger.v3.core.util.ModelDeserializer.deserializeObjectSchema(ModelDeserializer.java:106)
        at io.swagger.v3.core.util.ModelDeserializer.deserialize(ModelDeserializer.java:76)
        at io.swagger.v3.core.util.ModelDeserializer.deserialize(ModelDeserializer.java:28)
        at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4001)
        at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2992)
        at io.swagger.v3.core.jackson.ModelResolver.resolve(ModelResolver.java:624)
        at io.swagger.v3.core.converter.ModelConverterContextImpl.resolve(ModelConverterContextImpl.java:90)
        at io.swagger.v3.core.jackson.ModelResolver.resolve(ModelResolver.java:617)
        at io.swagger.v3.core.converter.ModelConverterContextImpl.resolve(ModelConverterContextImpl.java:90)
        at io.swagger.v3.core.converter.ModelConverters.readAllAsResolvedSchema(ModelConverters.java:99)
        at io.swagger.v3.core.util.AnnotationsUtils.getSchema(AnnotationsUtils.java:1091)
        at io.swagger.v3.core.util.AnnotationsUtils.getSchema(AnnotationsUtils.java:1076)
        at io.swagger.v3.core.util.AnnotationsUtils.getContent(AnnotationsUtils.java:1032)
        at io.swagger.v3.jaxrs2.OperationParser.getApiResponses(OperationParser.java:91)
        at io.swagger.v3.jaxrs2.Reader.setOperationObjectFromApiOperationAnnotation(Reader.java:1125)
        at io.swagger.v3.jaxrs2.Reader.parseMethod(Reader.java:912)
        at io.swagger.v3.jaxrs2.Reader.parseMethod(Reader.java:772)
        at io.swagger.v3.jaxrs2.Reader.read(Reader.java:446)
        at io.swagger.v3.jaxrs2.Reader.read(Reader.java:169)
        at io.swagger.v3.jaxrs2.Reader.read(Reader.java:196)
        at io.swagger.v3.oas.integration.GenericOpenApiContext.read(GenericOpenApiContext.java:469)
        at io.swagger.v3.jaxrs2.integration.SwaggerLoader.resolve(SwaggerLoader.java:215)
        ... 44 more
Caused by: com.fasterxml.jackson.databind.JsonMappingException: additionalProperties must be either a Boolean or a Schema instance
 at [Source: UNKNOWN; line: -1, column: -1] (through reference chain: io.swagger.v3.oas.models.media.ObjectSchema["additional_properties"])
        at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:277)
        at com.fasterxml.jackson.databind.deser.SettableBeanProperty._throwAsIOE(SettableBeanProperty.java:598)
        at com.fasterxml.jackson.databind.deser.SettableBeanProperty._throwAsIOE(SettableBeanProperty.java:586)
        at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:134)
        at com.fasterxml.jackson.databind.deser.BeanDeserializer.vanillaDeserialize(BeanDeserializer.java:288)
        at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:151)
        at com.fasterxml.jackson.databind.ObjectMapper._convert(ObjectMapper.java:3733)
        ... 67 more
Caused by: java.lang.IllegalArgumentException: additionalProperties must be either a Boolean or a Schema instance
        at io.swagger.v3.oas.models.media.Schema.setAdditionalProperties(Schema.java:546)
        at com.fasterxml.jackson.databind.deser.impl.MethodProperty.deserializeAndSet(MethodProperty.java:132)
        ... 70 more

@frantuma
Copy link
Member

Thanks for reporting, it is indeed a bug :( this should be fixed in #3038 (latest 2.0-7-SNAPSHOT) which limits the scope of custom ObjectMapper to default resolver; for 2.0.6, applying ModelConverter as in your example via modelConverters parameter should work:

modelConverterClasses = ["...SampleModelConverter"]

frantuma added a commit that referenced this issue Nov 28, 2018
ref #2971 - fix ObjectMapperProcessor scope of action
@peetzen
Copy link
Author

peetzen commented Nov 29, 2018

Using the modelConverterClasses implementation worked as expected. Looking forward to 2.0.7!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants