kobylynskyi / graphql-java-codegen

Make your GraphQL Java application schema-driven.
https://kobylynskyi.github.io/graphql-java-codegen/
MIT License
268 stars 115 forks source link

Java Time data types are not supported in mutation input fields because they are not escaped which leads to syntax validation errors #1476

Open andreloeffelmann opened 7 months ago

andreloeffelmann commented 7 months ago

Issue Description

If an input field for a mutation is e.g. of DateTime type, the generated GraphQL mutation contains the stringified datetime not in escaped strings which leads to a syntax parsing error at the remote GraphQL service. This happens because GraphQLRequestSerializer.java does not escape these types in public static String getEntry(Object input, boolean useObjectMapper) method and just invokes .toString() on the value.

Example mutation:

mutation {
  updateTest: (update: { createdAt: 2024-03-22T09:41:30.194029200+01:00 }){
  ...
  }
}

But expected:

mutation {
  updateTest: (update: { createdAt: "2024-03-22T09:41:30.194029200+01:00" }){
  ...
  }
}

A possible solution: Support java.time.Temporal and java.time.TemporalAmount types in public static String getEntry(Object input, boolean useObjectMapper) method of GraphQLRequestSerializer.java, by adapting it e.g. like this:

public static String getEntry(Object input, boolean useObjectMapper) {
        if (input == null) {
            return null;
        } else if (useObjectMapper) {
            return objectMapperWriteValueAsString(input);
        } else if (input instanceof Collection<?>) {
            return serializeCollection((Collection<?>) input, useObjectMapper);
        } else if (input instanceof Map<?, ?>) {
            return serializeMap((Map<?, ?>) input, useObjectMapper);
        } else if (input instanceof Map.Entry<?, ?>) {
            return serializeMapEntry((Map.Entry<?, ?>) input, useObjectMapper);
        } else if (input instanceof Enum<?>) {
            return serializeEnum((Enum<?>) input);
        } else if (input instanceof String) {
            return escapeJsonString(input.toString());
        } else if (input.getClass().getName().equals("scala.Some")) { // TODO: move to Scala Serializer
            // Currently, option only supports primitive types, so that's fine.
            // Now, this kind of case will appear if and only if Seq[Option[Int]] is
            return input.toString().replace("Some(", "").replace(")", "");
        } else if (input.getClass().getName().equals("scala.None$")) {
            return null;
        } else if (input instanceof Temporal || input instanceof TemporalAmount) {
            return escapeJsonString(input.toString());
        } else {
            return input.toString();
        }
    }
rwo-trackunit commented 7 months ago

I'm not sure if its resolves your issue, but my implementation uses graphql-java-extended-scalars.

So I've setup DateTime in plugin config

<customTypesMapping>
  <DateTime>java.time.OffsetDateTime</DateTime>

And added ExtendedScalars.DateTime to my auto config

import graphql.scalars.ExtendedScalars;

@AutoConfiguration
public class GraphqlAutoConfiguration {
    @Bean
    public RuntimeWiringConfigurer runtimeWiringConfigurer() {
        return wiringBuilder -> wiringBuilder.scalar(ExtendedScalars.DateTime)

and I don't have this stringify issue.

After all, GraphQL only support very few types out of the box, date not being one of them.