Apollo Kotlin Execution Help

Custom scalars

You can define a new custom scalar using @GraphQLScalar:

Kotlin

GraphQL

@GraphQLScalar(GeoPointCoercing::class) class GeoPoint( val latitude: Double, val longitude: Double )
scalar GeoPoint

@GraphQLScalar can also be added to typealiases for the times when you don't own the target class:

Kotlin

GraphQL

import kotlinx.datetime.LocalDateTime @GraphQLScalar(DateTimeCoercing::class) typealias DateTime = LocalDateTime
scalar DateTime

Implementing the Coercing interface

Each custom scalar requires a matching Coercing implementation:

interface Coercing<T> { /** * Serializes from an internal value (Kotlin) to an external value (typically JSON). * * For an example Date --> String */ fun serialize(internalValue: T): ExternalValue /** * Deserializes from an external value (typically JSON) to an internal value (Kotlin). * * For an example String --> Date */ fun deserialize(value: ExternalValue): T /** * Parses from a GraphQL value to an internal value (Kotlin) */ fun parseLiteral(gqlValue: GQLValue): T }

serialize is needed if your scalar is used in an output position:

type User { # serialize() is called here to serialize a `GeoPoint` to JSON $position: GeoPoint }

deserialize is needed if your scalar is used in a variable:

query GetUser($position: GeoPoint) { # Get all users in a 100m range from the given position usersByPosition(position: $position, radius: 100.0) {} }

parseLiteral is needed if your scalar is used as a GraphQL literal:

query GetUserInParis { # Get all users in a 1km range from the center of Paris usersByPosition(position: { latitude: 48.8588475, longitude: 2.3058358}, radius: 1000.0) {} }

With the above, a possible implementation for GeoPointCoercing is:

@GraphQLCoercing class GeoPointCoercing: Coercing<GeoPoint> { override fun serialize(internalValue: GeoPoint): ExternalValue { return mapOf( "latitude" to internalValue.latitude, "longitude" to internalValue.longitude ) } override fun deserialize(value: ExternalValue): GeoPoint { check(value is Map<*,*>) { "'$value' cannot be deserialized to a GeoPoint, expected a Map." } /* * Delegate to FloatCoercing to handle the case where the incoming value * is an Int */ val latitude = value["latitude"]?.let { FloatCoercing.deserialize(it) } val longitude = value["longitude"]?.let { FloatCoercing.deserialize(it) } check(latitude != null && longitude != null) { "'$value' cannot be deserialized to a GeoPoint, latitude or longitude cannot be coerced to a Double." } return GeoPoint( latitude, longitude ) } override fun parseLiteral(value: GQLValue): GeoPoint { check(value is GQLObjectValue) { "'$value' cannot be parsed to a GeoPoint, expected an object." } /* * Delegate to FloatCoercing to handle the case where the incoming value * is a GQLIntValue */ val latitude = value.fields.firstOrNull { it.name == "latitude" }?.value?.let { FloatCoercing.parseLiteral(it) } val longitude = value.fields.firstOrNull { it.name == "longitude" }?.value?.let { FloatCoercing.parseLiteral(it) } check(latitude != null && longitude != null) { "'$value' cannot be deserialized to a GeoPoint, latitude or longitude cannot be coerced to a Double." } return GeoPoint( latitude, longitude ) } }

Built-in Coercings

Apollo Kotlin Execution comes with built-in Coercing for built-in types that you can reuse:

  • StringCoercing

  • IntCoercing

  • FloatCoercing

  • BooleanCoercing

The ID scalar

Despite being a built-in GraphQL scalar, there is no ID type in Kotlin. If you want to use ID in your schema, you need to define the mapping manually.

Typically, ID is mapped either to Int or String.

Mapping ID to String:

@GraphQLScalar(StringCoercing) typealias ID = String

Mapping ID to Int:

@GraphQLScalar(IntCoercing) typealias ID = Int
Last modified: 16 July 2024