Resolver Annotation
Field resolvers must be annotated with @Resolver
to be registered. This annotation class also allows resolvers to declare data dependencies in the form of required selection sets via objectValueFragment
and queryValueFragment
:
annotation class Resolver(
@Language("GraphQL") val objectValueFragment: String = "",
@Language("GraphQL") val queryValueFragment: String = "",
val variables: Array<Variable> = []
)
objectValueFragment: a GraphQL fragment on the object type that contains the field being resolved. In the User.displayName
example below, the fragment must be on the User
type.
queryValueFragment: a GraphQL fragment on the root query type.
variables: values of variables used in objectValueFragment
or queryValueFragment
.
Required selection set syntax
A resolver can optionally specify one or both of objectValueFragment
and queryValueFragment
using either the shorthand fragment syntax, or full fragment syntax. Values can be accessed using Context.objectValue
and Context.queryValue
.
Shorthand syntax
The shorthand fragment syntax just includes the selections within the fragment body.
Here’s an example of what this looks like for a User.displayName
field. The selections must be on the User
type:
@Resolver("firstName lastName")
class UserDisplayNameResolver : UserResolvers.DisplayName()
The shorthand fragment syntax can also be used for queryValueFragment
. The selections must be on the root query type:
@Resolver(queryValueFragment = "user { firstName lastName }")
Full fragment syntax
The full fragment syntax is the regular GraphQL fragment syntax. You can name the fragment whatever you’d like, although we typically use _
for the fragment name when there’s only a single fragment to indicate that the name isn’t used anywhere:
@Resolver("fragment _ on User { firstName lastName }")
You can define multiple named fragments and reference them within your main fragment:
@Resolver(
queryValueFragment = """
fragment _ on Query {
listing {
cover: coverImage {
...ImageDetails
}
rooms {
images {
...ImageDetails
}
}
}
}
fragment ImageDetails on Image {
url
caption
}
"""
)
Note that if you have multiple fragments on the type of the main fragment (either the object type or the query type), the primary one needs to be named Main
:
@Resolver(
"""
fragment Main on User {
firstName
lastName
...UserProfilePhoto
}
fragment UserProfilePhoto on User {
profilePhoto {
url
caption
}
}
"""
)
Accessing required selection set values
You can access the required selection set values via the Context
object given as input to the field resolver. Context.objectValue
and Context.queryValue
are GRTs of the object and Query types, e.g.
ctx.objectValue.getFirstName()
ctx.queryValue.getListing().getCoverImage(alias = "cover")
If the resolver tries to access a field not included within its required selection set, it results in an UnsetSelectionException
at runtime.
In the Kotlin API, each of the field getters are suspend functions. Your resolver may begin execution before the selections have been fully resolved via their corresponding resolvers. If that happens, the field getter will suspend until the field is resolved.
Variables
The fragments in @Resolver
annotations can contain variables. These variables can be bound to values in one of two ways:
- Via the
variables
parameter in@Resolver
- Via the resolver’s variable provider
@Resolver variables parameter
Variables may be bound using the variables
parameter of @Resolver
, which is an array of @Variable
annotations. For example, consider this resolver configuration for the field MyType.foo
that takes an argument include
:
@Resolver(
objectValueFragment = """
fragment _ on MyType {
field @include(if: ${'$'}shouldInclude)
}
""",
variables = [Variable("shouldInclude", fromArgument = "includeMe")]
)
This resolver fragment uses a shouldInclude
variable. At runtime, the value for this variable will be determined by the value of the includeMe
argument to MyType.foo
. To support nested GraphQL input types, the fromArgument
string can contain a dot-separated path.
There are three mutually-exclusive parameters to the @Variable
class that can be used to set the value of a variable:
- the
fromArgument
parameter just illustrated - the
fromObjectField
parameter, which takes a dot-separated path relative to theobjectValue
of an execution. If used, the path must be a selection defined in the resolver’s objectValueFragment. - the
fromQueryField
parameter. This parameter is analogous tofromObjectField
, but the path describes a selection in the resolver’squeryValueFragment
.
VariablesProvider
The variables
parameter does not allow arbitrarily-computed values to be used as variables. To support dynamic use cases, a VariablesProvider
can be used.
For example, consider a resolver for MyType.foo
whose required selection set uses variables named startDate
and endDate
. To provide dynamically-computed values for these variables, the implementation for MyTypeResolvers.Foo
may nest a class that implements the VariablesProvider
interface:
@Variables(types = "startDate: Date, endDate: Date")
class Vars : VariablesProvider<MyType_Foo_Arguments> {
override suspend fun provide(args: MyType_Foo_Arguments) =
LocalDate.now().let {
mapOf(
"startDate" to it,
"endDate" to it.plusDays(7)
)
}
}
}
The value of the types
parameter to @Variables
must conform to VariableDefinitionlist from GraphQL Spec. The args
parameter to the provide
function is the arguments of the field whose resolver class defines this variable provider, or NoArguments
if the field takes no arguments.
Feedback
Was this page helpful?
Glad to hear it! Please tell us how we can improve.
Sorry to hear that. Please tell us how we can improve.