Variables
Purpose: Enable dynamic field selection and conditional GraphQL queries through runtime variable computation.
Usage: Variables can be bound to resolver arguments or computed dynamically using VariableProvider classes to control which fields are selected at the GraphQL execution level.
Viaduct supports three approaches for dynamic field resolution:
1. Variables with @Variable and fromArgument¶
Variables can be bound directly to resolver arguments to control GraphQL directive evaluation:
@Resolver(
"""
fragment _ on Character {
name
birthYear @include(if: ${'$'}includeDetails)
height @include(if: ${'$'}includeDetails)
mass @include(if: ${'$'}includeDetails)
}
""",
variables = [Variable("includeDetails", fromArgument = "includeDetails")]
)
class ProfileFieldResolver : CharacterResolvers.CharacterProfile() {
override suspend fun resolve(ctx: Context): String? {
val character = ctx.getObjectValue()
val name = character.getName() ?: "Unknown"
return try {
// If includeDetails is true, these fields will be available
val birthYear = character.getBirthYear()
val height = character.getHeight()
val mass = character.getMass()
buildString {
append("Character Profile: $name")
birthYear?.let { append(", Born: $it") }
height?.let { append(", Height: ${it}cm") }
mass?.let { append(", Mass: ${it}kg") }
}
} catch (e: Exception) {
// If includeDetails is false, detailed fields won't be available
"Character Profile: $name (basic info only)"
}
}
}
Benefits: GraphQL-level optimization, declarative field selection, efficient data fetching.
2. Argument-Based Statistics Logic¶
For practical demo purposes, the character stats use argument-based conditional logic:
@Resolver(
"""
fragment _ on Character {
name
birthYear
height
species {
name
}
}
"""
)
class CharacterStatsResolver : CharacterResolvers.CharacterStats() {
override suspend fun resolve(ctx: Context): String? {
val character = ctx.getObjectValue()
val name = character.getName() ?: "Unknown"
val args = ctx.arguments
return try {
buildString {
append("Stats for $name")
append(" (Age range: ${args.minAge}-${args.maxAge})")
// Try to access conditional fields
try {
val birthYear = character.getBirthYear()
val height = character.getHeight()
birthYear?.let { append(", Born: $it") }
height?.let { append(", Height: ${it}cm") }
} catch (e: Exception) {
append(" - age details not available for this range")
}
try {
val species = character.getSpecies()
species?.getName()?.let { append(", Species: $it") }
} catch (e: Exception) {
// Species not included for this age range
}
}
} catch (e: Exception) {
"Stats unavailable for $name"
}
}
}
Benefits: Simple implementation, full access to all fields, easy to debug and maintain.
Note: The full VariableProvider API with dynamic computation is available in the complete Viaduct runtime but simplified here for demo clarity.
3. Argument-Based Conditional Logic¶
For simpler cases, traditional argument processing within resolvers:
@Resolver(
"""
fragment _ on Character {
name
birthYear
eyeColor
hairColor
}
"""
)
class CharacterFormattedDescriptionResolver : CharacterResolvers.FormattedDescription() {
override suspend fun resolve(ctx: Context): String? {
val character = ctx.getObjectValue()
val name = character.getName() ?: "Unknown"
val format = ctx.arguments.format
return when (format) {
"detailed" -> {
val birthYear = character.getBirthYear()
val eyeColor = character.getEyeColor()
val hairColor = character.getHairColor()
buildString {
append(name)
birthYear?.let { append(" (born $it)") }
if (eyeColor != null || hairColor != null) {
append(" - ")
eyeColor?.let { append("$it eyes") }
if (eyeColor != null && hairColor != null) append(", ")
hairColor?.let { append("$it hair") }
}
}
}
"year-only" -> {
val birthYear = character.getBirthYear()
birthYear?.let { "$name (born $it)" } ?: "$name (birth year unknown)"
}
"appearance-only" -> {
val eyeColor = character.getEyeColor()
val hairColor = character.getHairColor()
buildString {
append(name)
if (eyeColor != null || hairColor != null) {
append(" - ")
eyeColor?.let { append("$it eyes") }
if (eyeColor != null && hairColor != null) append(", ")
hairColor?.let { append("$it hair") }
}
}
}
else -> name // default format - just name
}
}
}
Benefits: Simplicity, full Kotlin language features, easy debugging.
Example Schema:
type Character {
# Variables with fromArgument - demonstrates GraphQL-level field selection
characterProfile(includeDetails: Boolean = false): String @resolver
# Argument-based statistics - practical implementation for demos
characterStats(minAge: Int, maxAge: Int): String @resolver
# Argument-based conditional logic - flexible formatting
formattedDescription(format: String = "default"): String @resolver
}
Query Examples¶
@Variable fromArgument¶
query BasicProfile {
node(id: "Q2hhcmFjdGVyOjE=") { # Luke Skywalker
... on Character {
name
characterProfile(includeDetails: false)
# Result: "Character Profile: Luke Skywalker (basic info only)"
}
}
}
Include details example
query DetailedProfile {
node(id: "Q2hhcmFjdGVyOjE=") {
... on Character {
name
characterProfile(includeDetails: true)
# Result: "Character Profile: Luke Skywalker, Born: 19BBY, Height: 172cm, Mass: 77.0kg"
}
}
}
VariableProvider with dynamic computation¶
query CharacterStats {
node(id: "Q2hhcmFjdGVyOjU=") { # Obi-Wan Kenobi
... on Character {
name
characterStats(minAge: 25, maxAge: 100)
# Result: "Stats for Obi-Wan Kenobi (Age range: 25-100), Born: 57BBY, Height: 182cm, Species: Human"
}
}
}
Argument-based conditional logic¶
query FormattedDescriptions {
node(id: "Q2hhcmFjdGVyOjI=") { # Princess Leia
... on Character {
name
detailed: formattedDescription(format: "detailed")
# Result: "Princess Leia (born 19BBY) - brown eyes, brown hair"
yearOnly: formattedDescription(format: "year-only")
# Result: "Princess Leia (born 19BBY)"
default: formattedDescription(format: "default")
# Result: "Princess Leia"
}
}
}
Combined usage of all three approaches¶
query CombinedVariablesDemo {
node(id: "Q2hhcmFjdGVyOjE=") { # Luke Skywalker
... on Character {
name
# @Variable with fromArgument examples
basicProfile: characterProfile(includeDetails: false)
detailedProfile: characterProfile(includeDetails: true)
# VariableProvider with dynamic computation
youngStats: characterStats(minAge: 0, maxAge: 30)
oldStats: characterStats(minAge: 30, maxAge: 100)
# Argument-based conditional logic
nameOnly: formattedDescription(format: "default")
yearOnly: formattedDescription(format: "year-only")
detailed: formattedDescription(format: "detailed")
}
}
}
For the full
@Variable,VariableProvider, and built-in directive reference, see the Schema Reference developer docs.