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

Using custom scalars in variables of GraphQLRequest

Open erdigokce opened this issue 3 years ago • 0 comments

Problem

I am currently studying on a Spring Boot to Spring Boot GraphQL usage and found out GraphQL Spring Webclient (client-side) is lacking of a feature that GraphQL Spring Boot (server-side) has.

It is not possible to set a variable of scalar types in a GraphQLRequest which will be sent to GraphQL Server where custom scalars (ie. Date, Void, Long) defined in its schema.

Client Side Mutation Query Example with Custom Scalar

However, when we try to execute a query (or mutation) as in the following Kotlin example, we have to supply serialized values to variables.

Below example is working because date as parsed to string in the format of GraphQL Server coercing expects.

private fun createAnnouncementsForDate(date: Date): Mono<Array<Announcement>> {
    val graphqlRequest = GraphQLRequest.builder(Array<Announcement>::class.java)
        .operationName("createAnnouncementByDate")
        .query(mutationCreateAnnouncementByDate)
        
        .variables(mapOf("date" to SimpleDateFormat("dd.MM.yyyy").format(date)))
        
        .build()
    return graphQLWebClient.post(graphqlRequest)!!
}

Below code block fails because Date value is not converted to String in a required format.

private fun createAnnouncementsForDate(date: Date): Mono<Array<Announcement>> {
    val graphqlRequest = GraphQLRequest.builder(Array<Announcement>::class.java)
        .operationName("createAnnouncementByDate")
        .query(mutationCreateAnnouncementByDate)
        
        .variables(mapOf("date" to date))
        
        .build()
    return graphQLWebClient.post(graphqlRequest)!!
}

Offered Solution

Just like in server-side, custom scalar type implementations can be supplied as GraphQLScalarType beans that implements Coercing interfaces in the client-side. This can be done by a request interceptor in pre-execution layer.

Server Side Custom Scalar Definition Example

In server side, defining GraphQLScalarsType beans by supplying implemented Coercing interfaces allows you to handle the serialization and parsing values explicitly for custom scalars.

An example configration class below for java.util.Date class :

@Configuration
public class GraphQLScalarsConfig {
    @Bean
    public GraphQLScalarType graphQLDateScalar() {
        return GraphQLScalarType.newScalar().name("Date").coercing(new DateCoercing()).build();
    }
}

DateCoercing class:

public class DateCoercing implements Coercing<Date, String> {
    @Override
    public String serialize(Object o) throws CoercingSerializeException {
        return convertDateToString((Date) o);
    }

    @Override
    public Date parseValue(Object o) throws CoercingParseValueException {
        try {
            return convertStringToDate(o + "");
        } catch (ParseException e) {
            throw new CoercingParseValueException(e);
        }
    }

    @Override
    public Date parseLiteral(Object o) throws CoercingParseLiteralException {
        try {
            return convertStringToDate(o + "");
        } catch (ParseException e) {
            throw new CoercingParseLiteralException(e);
        }
    }
}

Additionaly

I haven't tested what happens if a response has any scalar type. However, it seems deserialization and serialization must be done manually in that time either.

I may be missing some design concerns or basics, please blame me in such cases.

erdigokce avatar Dec 12 '20 14:12 erdigokce