graphql-spring-boot icon indicating copy to clipboard operation
graphql-spring-boot copied to clipboard

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

Open seenimurugan opened this issue 4 years ago • 4 comments

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
    }
  }
}
  1. 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

 <dependency>
    <groupId>com.graphql-java-kickstart</groupId>
    <artifactId>graphql-spring-boot-starter</artifactId>
    <version>11.0.0</version>
</dependency>

Spring boot version:

<parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.4.4</version>
</parent>

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?

seenimurugan avatar May 18 '21 16:05 seenimurugan

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?

oliemansm avatar May 18 '21 19:05 oliemansm

@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:

  • Postman's Raw body type & application/graphql as content-type works fine
  • Postman's Graphql window & application/graphql as content-type fails with syntax error(attached)
  • Postman's Graphql window & application/json as content-type fails with query can't be null (as mentioned above)

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"
            }
        }
    ]
}

seenimurugan avatar May 18 '21 19:05 seenimurugan

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 avatar May 18 '21 20:05 oliemansm

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

oliemansm avatar May 21 '21 15:05 oliemansm