graphql-java-kickstart / graphql-spring-boot

GraphQL and GraphiQL Spring Framework Boot Starters - Forked from oembedler/graphql-spring-boot due to inactivity.
https://www.graphql-java-kickstart.com/spring-boot/
MIT License
1.5k stars 326 forks source link

ObjectMapper with UPPER_CAMEL_CASE throws graphql.AssertException: query can't be null #634

Open seenimurugan opened 3 years ago

seenimurugan commented 3 years ago

When project wide Jackson ObjectMapper is configured with PropertyNamingStrategy.UPPER_CAMEL_CASE naming strategy then sending query fails with query can't be null error

To Reproduce Steps to reproduce the behavior: Add the below Object mapper configuration

@Bean
public Jackson2ObjectMapperBuilderCustomizer customJackson2ObjectMapperBuilder() {
    return builder -> {
        builder.serializationInclusion(JsonInclude.Include.NON_NULL); // or better read is - exclude null values when serializing
        builder.featuresToEnable(DeserializationFeature.ACCEPT_EMPTY_STRING_AS_NULL_OBJECT);
        builder.propertyNamingStrategy(PropertyNamingStrategy.UPPER_CAMEL_CASE);  // commenting this line works fine
        // FAIL_ON_UNKNOWN_PROPERTIES is disabled by default but adding just for clarification
        builder.featuresToDisable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
    };
}
  1. send query from postman
    {
    cards(id:"43"){
    Card {
      Kpan
         BusinessName
      SomeName
    }
    }
    }
  2. See error
    
    graphql.AssertException: query can't be null
        at graphql.Assert.assertNotNull(Assert.java:17) ~[graphql-java-16.1.jar:na]
        at graphql.ExecutionInput$Builder.query(ExecutionInput.java:204) ~[graphql-java-16.1.jar:na]
        at graphql.kickstart.execution.input.GraphQLSingleInvocationInput.createExecutionInput(GraphQLSingleInvocationInput.java:47) ~[graphql-java-kickstart-11.0.0.jar:na]
        at graphql.kickstart.execution.input.GraphQLSingleInvocationInput.<init>(GraphQLSingleInvocationInput.java:26) ~[graphql-java-kickstart-11.0.0.jar:na]
        at graphql.kickstart.servlet.input.GraphQLInvocationInputFactory.create(GraphQLInvocationInputFactory.java:101) ~[graphql-java-servlet-11.0.0.jar:na]
        at graphql.kickstart.servlet.input.GraphQLInvocationInputFactory.create(GraphQLInvocationInputFactory.java:61) ~[graphql-java-servlet-11.0.0.jar:na]
        at graphql.kickstart.servlet.GraphQLPostInvocationInputParser.getGraphQLInvocationInput(GraphQLPostInvocationInputParser.java:37) ~[graphql-java-servlet-11.0.0.jar:na]
        at graphql.kickstart.servlet.HttpRequestHandlerImpl.handle(HttpRequestHandlerImpl.java:38) ~[graphql-java-servlet-11.0.0.jar:na]
        at graphql.kickstart.servlet.AbstractGraphQLHttpServlet.doRequest(AbstractGraphQLHttpServlet.java:82) ~[graphql-java-servlet-11.0.0.jar:na]
        at graphql.kickstart.servlet.AbstractGraphQLHttpServlet.doPost(AbstractGraphQLHttpServlet.java:74) ~[graphql-java-servlet-11.0.0.jar:na]
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:652) ~[tomcat-embed-core-9.0.44.jar:4.0.FR]
        at javax.servlet.http.HttpServlet.service(HttpServlet.java:733) ~[tomcat-embed-core-9.0.44.jar:4.0.FR]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227) ~[tomcat-embed-core-9.0.44.jar:9.0.44]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.44.jar:9.0.44]
        at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53) ~[tomcat-embed-websocket-9.0.44.jar:9.0.44]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.44.jar:9.0.44]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.44.jar:9.0.44]
        at org.springframework.web.filter.CorsFilter.doFilterInternal(CorsFilter.java:91) ~[spring-web-5.3.5.jar:5.3.5]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119) ~[spring-web-5.3.5.jar:5.3.5]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.44.jar:9.0.44]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.44.jar:9.0.44]
        at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:113) ~[spring-web-5.3.5.jar:5.3.5]
        at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189) ~[tomcat-embed-core-9.0.44.jar:9.0.44]
        at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162) ~[tomcat-embed-core-9.0.44.jar:9.0.44]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:327) ~[spring-security-web-5.4.5.jar:5.4.5]
        at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:115) ~[spring-security-web-5.4.5.jar:5.4.5]
        at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:81) ~[spring-security-web-5.4.5.jar:5.4.5]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.4.5.jar:5.4.5]
        at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:119) ~[spring-security-web-5.4.5.jar:5.4.5]
        at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:113) ~[spring-security-web-5.4.5.jar:5.4.5]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.4.5.jar:5.4.5]
        at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:126) ~[spring-security-web-5.4.5.jar:5.4.5]
        at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:81) ~[spring-security-web-5.4.5.jar:5.4.5]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.4.5.jar:5.4.5]
        at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:105) ~[spring-security-web-5.4.5.jar:5.4.5]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.4.5.jar:5.4.5]
        at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:149) ~[spring-security-web-5.4.5.jar:5.4.5]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.4.5.jar:5.4.5]
        at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63) ~[spring-security-web-5.4.5.jar:5.4.5]
        at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336) ~[spring-security-web-5.4.5.jar:5.4.5]

**Expected behavior**
Should be able to deserialise the query to expected object but **only works if i uncomment the following line**

builder.propertyNamingStrategy(PropertyNamingStrategy.UPPER_CAMEL_CASE);


**Graphql version**
com.graphql-java-kickstart graphql-spring-boot-starter 11.0.0

Spring boot version:

org.springframework.boot spring-boot-starter-parent 2.4.4

**Additional context**

I wanted to keep the project wide settings with `PropertyNamingStrategy.UPPER_CAMEL_CASE`
But I am ok to override naming strategy just for Graphql ObjectMapper as below.

When i tried to override the naming strategy with the below code as suggested in stackoverflow, **it does not work either.** 

@Bean ObjectMapperConfigurer objectMapperConfigurer() { return (mapper, context) -> { mapper.setPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CAMEL_CASE); }; }

@Bean
public SchemaParserOptions schemaParserOptions(){
    return SchemaParserOptions.newOptions().objectMapperConfigurer((mapper, context) -> {
        mapper.setPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CAMEL_CASE);
    }).build();
}


**Please suggest how to fix this issue?** 
oliemansm commented 3 years ago

At step two you seem to be sending the GraphQL query as the request body in a POST request. Is that correct? In that case you must set the Content-Type header to application/graphql as described here: https://graphql.org/learn/serving-over-http/#http-methods-headers-and-body. Are you doing that?

seenimurugan commented 3 years ago

@oliemansm Thank you for your time and extremely valuable suggestion. Many thanks.

It works as expected after updating the content type toapplication/graphql and the Postman's body set to raw type instead Graphql window

Behaviour:

The problem now is that I am not able to use the postman's GraphQL type body with content-type set to application/graphql because the application now throws invalid syntax error. So i have to use raw body type with header set to application/graphql to get it working.

So the question is why the behaviour changes as soon as Jackson Object mapper set with UPPER_CAMEL_CASE naming strategy?

How am i able to use the postman's graphql window to send the request? which is very convenient because it has separate query and variables window.

Below error occurs when query sent via Postman's Graphql window & content-type set to application/graphql

{
    "errors": [
        {
            "message": "Invalid Syntax : offending token '\"query\"' at line 1 column 2",
            "locations": [
                {
                    "line": 1,
                    "column": 2
                }
            ],
            "extensions": {
                "classification": "InvalidSyntax"
            }
        }
    ]
}
oliemansm commented 3 years ago

You say it has something to do with that uppercase object mapper, but seeing that it works with raw body and content type header I'm not convinced yet that the object mapper is the cause. So far it sounds like a problem in postman usage.

What happens if you enable graphiql with that starter and use that in the browser at /graphiql? Then you also have the variables panel, so same convenience. Could be worth a try to see if you still have these errors when using graphiql too.

If you do get the same errors, then paste the raw request body and headers here that are sent. If it works when using graphiql then I'd say it's a problem with Postman and not this lib.

oliemansm commented 3 years ago

@seenimurugan Have you been able to try out my last suggestions to try and figure out the root cause?