Search code examples
javaspring-bootgraphqljava-17spring-graphql

GraphQL custom scalar type doesn't work in spring boot application


I've configured GraphQL in spring boot application which uses java 17. To start application you need gradle bootRun.

To try GrapqQL request you can use curl:

curl -X POST http://localhost:8085/graphql \
    -H "Content-Type: application/json" \
    -d '{"query":"query {\n  bookById(id: \"book-1\"){\n    id\n    name\n    pageCount\n    productionDate\n    authors {\n      id\n      firstName\n      lastName\n    }\n  }\n}"}'

The current configuration doesn't use custom type LocalDate and no date formatting with pattern"dd/MM/yyyy".

The GraphQL configuration schema.graphqls

type Query {
    bookById(id: ID): Book
}

type Book {
    id: ID
    name: String
    pageCount: Int
    authors: [Author]!
    productionDate: String
#    productionDate: LocalDate
}

type Author {
    id: ID
    firstName: String
    lastName: String
}

requires productionDate: LocalDate (in the line 10). The custom java configuration is managed in the ScalarConfiguration.java following spring-boot,

package com.graphqljava.tutorial.bookDetails;

import graphql.language.StringValue;
import graphql.schema.*;
import org.springframework.boot.autoconfigure.graphql.GraphQlSourceBuilderCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.graphql.execution.RuntimeWiringConfigurer;

import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;

@Configuration(proxyBeanMethods = false)
public class ScalarConfiguration {

    private final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("dd/MM/yyyy");

    @Bean
    public GraphQLScalarType dateScalar() {
        return GraphQLScalarType.newScalar()
                .name("LocalDate")
                .description("Java 8 LocalDate as scalar.")
                .coercing(new Coercing<Object, Object>() {
                    @Override
                    public String serialize(final Object dataFetcherResult) {
                        if (dataFetcherResult instanceof LocalDate) {
                            return formatter.format((LocalDate) dataFetcherResult);
                        } else {
                            throw new CoercingSerializeException("Expected a LocalDate object.");
                        }
                    }

                    @Override
                    public LocalDate parseValue(final Object input) {
                        try {
                            if (input instanceof String) {
                                return LocalDate.parse((String) input, formatter);
                            } else {
                                throw new CoercingParseValueException("Expected a String");
                            }
                        } catch (DateTimeParseException e) {
                            throw new CoercingParseValueException(String.format("Not a valid date: '%s'.", input), e
                            );
                        }
                    }

                    @Override
                    public LocalDate parseLiteral(final Object input) {
                        if (input instanceof StringValue) {
                            try {
                                return LocalDate.parse(((StringValue) input).getValue(), formatter);
                            } catch (DateTimeParseException e) {
                                throw new CoercingParseLiteralException(e);
                            }
                        } else {
                            throw new CoercingParseLiteralException("Expected a StringValue.");
                        }
                    }
                }).build();
    }

    // https://docs.spring.io/spring-graphql/docs/1.1.0-RC1/reference/html/#execution-graphqlsource-runtimewiring-configurer
    @Bean
    RuntimeWiringConfigurer runtimeWiringConfigurer() {
        GraphQLScalarType scalarType = dateScalar();
        return wiringBuilder -> wiringBuilder.scalar(scalarType);
    }

/*
    // https://docs.spring.io/spring-graphql/docs/1.1.0-RC1/reference/html/#execution-graphqlsource
    @Bean
    public GraphQlSourceBuilderCustomizer sourceBuilderCustomizer() {
        GraphQLScalarType scalarType = dateScalar();
        return (builder) ->
                builder.configureGraphQl(graphQlBuilder ->
                        graphQlBuilder.executionIdProvider((query, operationName, context) -> null));
    }
*/

}

so runtimeWiringConfigurer loads correctly, but LocalDate doesn't work in my schema.graphqls and I'm looking for help and solution with described issue.

P.S.

  • The git source is here.
  • I've detected there similar questions, but their answers didn't help me properly. In current question I've provided git java project which can be fixed to make working it with LocalDate.

Solution

  • The issue can be resolved declaring correspond scalar type in graphqls file scalar LocalDate.

    So schema.graphqls looks like:

    scalar LocalDate
    
    type Query {
        bookById(id: ID): Book
    }
    
    type Book {
        id: ID
        name: String
        pageCount: Int
        authors: [Author]!
        productionDate: LocalDate
    }
    
    type Author {
        id: ID
        firstName: String
        lastName: String
    }
    

    The fix is published to the git also.