
Enhances GraphQL with a type system extension, consistent methods, validations, operations DSL, and a client integration. Includes unit tests for deep visitor recursion and other features.
A Kotlin/JVM GraphQL library for building schemas, executing queries, and parsing GraphQL documents.
Add the umbrella artifact (includes all modules) to your Gradle build:
// build.gradle.kts
dependencies {
implementation("io.fluidsonic.graphql:fluid-graphql:0.15.0")
}Or depend on individual modules:
dependencies {
// Core type system, parser, printer, AST
implementation("io.fluidsonic.graphql:fluid-graphql-language:0.15.0")
// Kotlin DSL for building schemas and documents
implementation("io.fluidsonic.graphql:fluid-graphql-dsl:0.15.0")
// Query execution engine and validation
implementation("io.fluidsonic.graphql:fluid-graphql-execution:0.15.0")
}import io.fluidsonic.graphql.*
import kotlinx.coroutines.runBlocking
fun main() = runBlocking {
val schema = GraphQL.schema {
Query {
field("hello" of !String) {
resolve { "Hello, world!" }
}
}
}
val executor = GExecutor.default(schema = schema)
val result = executor.execute("{ hello }")
println(executor.serializeResult(result))
// {data={hello=Hello, world!}}
}Schemas are built with GraphQL.schema { }. The DSL provides type-safe builders for every GraphQL type.
val schema = GraphQL.schema {
Query {
field("user" of type("User")) {
argument("id" of !String)
resolve {
val id = arguments["id"] as String
User(id = id, name = "Alice")
}
}
}
Object(type("User")) {
field("id" of !String)
field("name" of !String)
}
}Use ! to make a type non-null, and List(...) to wrap a type in a list:
Object(type("Post")) {
field("id" of !String) // String! — non-null
field("tags" of List(!String)) // [String!] — list of non-null strings
field("comments" of !List(!type("Comment"))) // [Comment!]! — non-null list of non-null
}Built-in scalar type references available inside the DSL: String, Int, Float, Boolean, ID.
Query {
field("posts" of List(type("Post"))) {
argument("limit" of Int) {
default(value(10))
}
argument("status" of type("Status")) {
default(enum("PUBLISHED"))
}
}
}val Status by type
Enum(Status) {
value("DRAFT")
value("PUBLISHED")
value("ARCHIVED") {
deprecated("Use DRAFT instead")
}
}val Node by type
val Post by type
val Comment by type
Interface(Node) {
field("id" of !ID)
}
Object(Post implements Node) {
field("id" of !ID)
field("title" of !String)
}
Object(Comment implements Node) {
field("id" of !ID)
field("body" of !String)
}An object implementing multiple interfaces:
Object(type("BlogPost") implements type("Node") and type("Timestamped")) {
field("id" of !ID)
field("createdAt" of !String)
field("title" of !String)
}val SearchResult by type
val Post by type
val User by type
Union(SearchResult with Post or User)val CreatePostInput by type
InputObject(CreatePostInput) {
argument("title" of !String)
argument("body" of !String)
argument("tags" of List(!String))
}
Mutation {
field("createPost" of !type("Post")) {
argument("input" of !CreatePostInput)
}
}val Date by type
Scalar(Date) {
description("An ISO-8601 date string (e.g. 2024-01-15)")
}See Custom Scalar Coercion for attaching coercers.
Directive("auth") {
description("Requires the caller to be authenticated")
argument("role" of String) {
default(value("USER"))
}
on(FIELD_DEFINITION or OBJECT)
}Use val MyType by type to derive a GNamedTypeRef from the property name. This avoids repeating string literals:
val schema = GraphQL.schema {
val User by type
val Post by type
Query {
field("user" of User) {
argument("id" of !ID)
}
field("posts" of List(Post))
}
Object(User) {
field("id" of !ID)
field("name" of !String)
}
Object(Post) {
field("id" of !ID)
field("title" of !String)
}
}Object(type("User")) {
description("A registered user of the system")
field("id" of !ID) {
description("Unique identifier")
}
field("legacyId" of String) {
deprecated("Use `id` instead")
}
}The simplest way to attach a resolver is the resolve { } block on a field definition. The lambda receives the parent object as its argument:
Object(type("User")) {
field("id" of !String) {
resolve { parent: Any -> (parent as User).id }
}
}Use the typed Object<KotlinType> overload for compile-time safety. The resolver lambda receives the correctly-typed parent:
data class User(val id: String, val name: String)
val schema = GraphQL.schema {
val User by type
Query {
field("me" of User) {
resolve { currentUser() }
}
}
Object<User>(User) {
field("id" of !String) {
resolve { it.id }
}
field("name" of !String) {
resolve { it.name }
}
}
}The GFieldResolverContext receiver exposes arguments (a Map<String, Any?>), fieldDefinition, parentType, path, and the full execution context:
field("greeting" of !String) {
argument("name" of !String)
resolve {
val name = arguments["name"] as String
"Hello, $name!"
}
}Per-request data (such as the current user) can be passed via GExecutorContextExtensionSet and read inside any resolver:
object CurrentUserKey : GExecutorContextExtensionKey<User>
field("me" of type("User")) {
resolve {
execution.extensions[CurrentUserKey]
}
}Provide a fieldResolver to GExecutor.default to handle fields that have no inline resolver — useful for property-based default resolution or middleware:
val executor = GExecutor.default(
schema = schema,
fieldResolver = GFieldResolver { parent ->
// called for every field without an explicit resolver
next()
}
)Attach coercers directly on a Scalar definition using the three coercion hooks.
Called when the scalar value is provided inline in a query document (not as a variable):
val Date by type
Scalar(Date) {
coerceNodeInput { input ->
// input is the raw GValue AST node
when (input) {
is GStringValue -> LocalDate.parse(input.value)
else -> GError("Expected a date string").throwException()
}
}
}Called when the scalar value is provided via a query variable (already parsed from JSON):
Scalar(Date) {
coerceVariableInput { input ->
// input is the raw value from the variables map (e.g. String from JSON)
LocalDate.parse(input as String)
}
}Called when a resolver returns a value of this scalar type, converting it to a serializable form:
Scalar(Date) {
coerceOutput { input ->
// input is the value returned by a resolver
(input as LocalDate).toString()
}
}val executor = GExecutor.default(schema = schema)
// Execute from a query string (suspend function)
val result: GResult<Map<String, Any?>> = executor.execute("{ hello }")
// With operation name and variables
val result = executor.execute(
documentSource = "query GetUser(\$id: ID!) { user(id: \$id) { name } }",
operationName = "GetUser",
variableValues = mapOf("id" to "42")
)
// Convert result to a plain map for JSON serialization
val response: Map<String, Any?> = executor.serializeResult(result)
// { "data": { ... }, "errors": [...] }Use GExecutorContextExtensionSet to attach request-scoped data (auth context, request ID, etc.):
object CurrentUserKey : GExecutorContextExtensionKey<User>
val extensions = GExecutorContextExtensionSet {
set(CurrentUserKey, authenticatedUser)
}
val result = executor.execute(
documentSource = "{ me { name } }",
extensions = extensions
)By default, the root value passed to top-level field resolvers is Unit. Use GRootResolver to provide a meaningful root object:
val executor = GExecutor.default(
schema = schema,
rootResolver = GRootResolver.constant(myRootObject)
)Validate a parsed document against a schema before executing it. Requires the fluid-graphql-execution module.
val document = GDocument.parse("{ user { id name } }").valueOrThrow()
val errors: List<GError> = document.validate(schema)
if (errors.isEmpty()) {
println("Document is valid")
} else {
errors.forEach { println(it.describe()) }
}Validation covers the full GraphQL specification: field existence, argument types, fragment cycles, variable usage, directive locations, and more.
// Parse a query document
val result: GResult<GDocument> = GDocument.parse("{ hello }")
val document = result.valueOrThrow()
// Parse a schema SDL
val schemaResult: GResult<GDocument> = GDocument.parse("""
type Query {
hello: String
}
""".trimIndent())Every GNode can be serialized back to a GraphQL string via GNode.print(node) or simply toString():
val document = GDocument.parse("query { hello }").valueOrThrow()
println(document.toString())
// query {
// hello
// }
// Custom indentation
println(GNode.print(document, indent = " "))Operations that can fail return a GResult<Value> — a sealed class with Success and Failure variants. A Success may still carry non-fatal errors alongside its value.
val result: GResult<GDocument> = GDocument.parse(source)
// Pattern match
when (result) {
is GResult.Success -> println("Parsed: ${result.value}")
is GResult.Failure -> println("Errors: ${result.errors}")
}
// Convenient extractors
val document = result.valueOrNull() // null on failure
val document = result.valueOrThrow() // throws GErrorException on failure
val document = result.valueWithoutErrorsOrNull() // null if any errors present
// Transform
val names = result.mapValue { doc -> doc.definitions.map { it } }
// Chain
val validated = result.flatMapValue { doc ->
val errors = doc.validate(schema)
if (errors.isEmpty()) GResult.success(doc) else GResult.failure(errors)
}
// Early exit pattern
fun process(result: GResult<GDocument>): GResult<String> {
val doc = result.ifErrors { errors -> return GResult.failure(errors) }
return GResult.success(doc.toString())
}GError is the standard error type, following the GraphQL response format:
val error = GError(
message = "Not found",
path = GPath.ofName("user"),
extensions = mapOf("code" to "NOT_FOUND")
)
// Throw as an exception (caught by executor and turned into a response error)
error.throwException()
// Describe with source location context
println(error.describe())GErrorException bridges between the error-result model and Kotlin exceptions. It is thrown by valueOrThrow() and by GError.throwException(). Inside resolvers, throwing a GErrorException causes the executor to include its errors in the GraphQL response rather than propagating the exception.
throw GErrorException(GError("Something went wrong"))
// or equivalently:
GError("Something went wrong").throwException()By default, exceptions thrown inside resolvers or coercers propagate out of GExecutor.execute. Provide a GExceptionHandler to translate them into GraphQL errors instead:
val executor = GExecutor.default(
schema = schema,
exceptionHandler = GExceptionHandler { exception ->
when (exception) {
is NotFoundException -> GError(
message = exception.message ?: "Not found",
extensions = mapOf("code" to "NOT_FOUND")
)
else -> GError("Internal server error")
}
}
)The handler receives the exception via its GExceptionHandlerContext receiver, which also provides the GExceptionOrigin indicating whether the exception came from a field resolver, output coercer, input coercer, or root resolver.
Note: GErrorException (thrown via GError.throwException()) is never passed to the exception handler — it is always treated as a GraphQL error automatically.
GNodeExtensionSet provides a typed key-value map for attaching arbitrary metadata to any GNode without modifying the AST. This is the mechanism fluid-graphql itself uses to attach resolvers and coercers to schema nodes.
data class AuthRequirement(val role: String)
object AuthRequirementKey : GNodeExtensionKey<AuthRequirement>val schema = GraphQL.schema {
Query {
field("adminData" of !String) {
extension(AuthRequirementKey, AuthRequirement(role = "ADMIN"))
resolve { "secret" }
}
}
}val fieldDef: GFieldDefinition = schema.queryType!!.fieldDefinition("adminData")!!
val auth: AuthRequirement? = fieldDef.extensions[AuthRequirementKey]GExecutorContextExtensionKey is the per-request equivalent of GNodeExtensionKey. Use it to pass request-scoped values (current user, trace context, etc.) into resolvers without threading parameters through every call.
data class RequestContext(val userId: String, val isAdmin: Boolean)
object RequestContextKey : GExecutorContextExtensionKey<RequestContext>val extensions = GExecutorContextExtensionSet {
set(RequestContextKey, RequestContext(userId = "42", isAdmin = false))
}
val result = executor.execute(
documentSource = "{ me { name } }",
extensions = extensions
)Object<User>(type("User")) {
field("isAdmin" of !Boolean) {
resolve {
val ctx = execution.extensions[RequestContextKey]
ctx?.isAdmin ?: false
}
}
}Use GraphQL.document { } to build a GDocument without writing a query string:
val document = GraphQL.document {
query("GetUser") {
"user" {
argument("id", value("42"))
"id"()
"name"()
"email"()
}
}
}
// Equivalent to:
// query GetUser {
// user(id: "42") {
// id
// name
// email
// }
// }val doc = GraphQL.document {
mutation("CreatePost") {
"createPost" {
"id"()
"title"()
}
}
}
GraphQL.document {
subscription("OnMessage") {
"messageAdded" {
"id"()
"body"()
}
}
}val doc = GraphQL.document {
query {
"search" {
on("Post") {
"title"()
}
on("User") {
"name"()
}
fragment("CommonFields")
}
}
fragment("CommonFields") {
// on a particular type — set via the fragment builder
"id"()
"createdAt"()
}
}GraphQL.document {
query {
"user"(alias = "currentUser") {
"id"()
}
}
}io.fluidsonic.graphql
fluid-graphql-language
- GNode, GDocument, GSchema (AST model)
- GDocument.parse(), GNode.print()
- GResult, GError, GErrorException
- GNodeExtensionKey, GNodeExtensionSet
- Visitor, NodeWalker (AST traversal)
^
|
fluid-graphql-dsl
- GraphQL.schema { } (GSchemaBuilder)
- GraphQL.document { } (GraphQLDocumentBuilder)
^
|
fluid-graphql-execution
- GExecutor.default()
- GDocument.validate()
- GFieldResolver, GRootResolver
- GOutputCoercer, GNodeInputCoercer, GVariableInputCoercer
- GExceptionHandler
- GExecutorContextExtensionKey, GExecutorContextExtensionSet
Apache License 2.0 — see LICENSE for details.
A Kotlin/JVM GraphQL library for building schemas, executing queries, and parsing GraphQL documents.
Add the umbrella artifact (includes all modules) to your Gradle build:
// build.gradle.kts
dependencies {
implementation("io.fluidsonic.graphql:fluid-graphql:0.15.0")
}Or depend on individual modules:
dependencies {
// Core type system, parser, printer, AST
implementation("io.fluidsonic.graphql:fluid-graphql-language:0.15.0")
// Kotlin DSL for building schemas and documents
implementation("io.fluidsonic.graphql:fluid-graphql-dsl:0.15.0")
// Query execution engine and validation
implementation("io.fluidsonic.graphql:fluid-graphql-execution:0.15.0")
}import io.fluidsonic.graphql.*
import kotlinx.coroutines.runBlocking
fun main() = runBlocking {
val schema = GraphQL.schema {
Query {
field("hello" of !String) {
resolve { "Hello, world!" }
}
}
}
val executor = GExecutor.default(schema = schema)
val result = executor.execute("{ hello }")
println(executor.serializeResult(result))
// {data={hello=Hello, world!}}
}Schemas are built with GraphQL.schema { }. The DSL provides type-safe builders for every GraphQL type.
val schema = GraphQL.schema {
Query {
field("user" of type("User")) {
argument("id" of !String)
resolve {
val id = arguments["id"] as String
User(id = id, name = "Alice")
}
}
}
Object(type("User")) {
field("id" of !String)
field("name" of !String)
}
}Use ! to make a type non-null, and List(...) to wrap a type in a list:
Object(type("Post")) {
field("id" of !String) // String! — non-null
field("tags" of List(!String)) // [String!] — list of non-null strings
field("comments" of !List(!type("Comment"))) // [Comment!]! — non-null list of non-null
}Built-in scalar type references available inside the DSL: String, Int, Float, Boolean, ID.
Query {
field("posts" of List(type("Post"))) {
argument("limit" of Int) {
default(value(10))
}
argument("status" of type("Status")) {
default(enum("PUBLISHED"))
}
}
}val Status by type
Enum(Status) {
value("DRAFT")
value("PUBLISHED")
value("ARCHIVED") {
deprecated("Use DRAFT instead")
}
}val Node by type
val Post by type
val Comment by type
Interface(Node) {
field("id" of !ID)
}
Object(Post implements Node) {
field("id" of !ID)
field("title" of !String)
}
Object(Comment implements Node) {
field("id" of !ID)
field("body" of !String)
}An object implementing multiple interfaces:
Object(type("BlogPost") implements type("Node") and type("Timestamped")) {
field("id" of !ID)
field("createdAt" of !String)
field("title" of !String)
}val SearchResult by type
val Post by type
val User by type
Union(SearchResult with Post or User)val CreatePostInput by type
InputObject(CreatePostInput) {
argument("title" of !String)
argument("body" of !String)
argument("tags" of List(!String))
}
Mutation {
field("createPost" of !type("Post")) {
argument("input" of !CreatePostInput)
}
}val Date by type
Scalar(Date) {
description("An ISO-8601 date string (e.g. 2024-01-15)")
}See Custom Scalar Coercion for attaching coercers.
Directive("auth") {
description("Requires the caller to be authenticated")
argument("role" of String) {
default(value("USER"))
}
on(FIELD_DEFINITION or OBJECT)
}Use val MyType by type to derive a GNamedTypeRef from the property name. This avoids repeating string literals:
val schema = GraphQL.schema {
val User by type
val Post by type
Query {
field("user" of User) {
argument("id" of !ID)
}
field("posts" of List(Post))
}
Object(User) {
field("id" of !ID)
field("name" of !String)
}
Object(Post) {
field("id" of !ID)
field("title" of !String)
}
}Object(type("User")) {
description("A registered user of the system")
field("id" of !ID) {
description("Unique identifier")
}
field("legacyId" of String) {
deprecated("Use `id` instead")
}
}The simplest way to attach a resolver is the resolve { } block on a field definition. The lambda receives the parent object as its argument:
Object(type("User")) {
field("id" of !String) {
resolve { parent: Any -> (parent as User).id }
}
}Use the typed Object<KotlinType> overload for compile-time safety. The resolver lambda receives the correctly-typed parent:
data class User(val id: String, val name: String)
val schema = GraphQL.schema {
val User by type
Query {
field("me" of User) {
resolve { currentUser() }
}
}
Object<User>(User) {
field("id" of !String) {
resolve { it.id }
}
field("name" of !String) {
resolve { it.name }
}
}
}The GFieldResolverContext receiver exposes arguments (a Map<String, Any?>), fieldDefinition, parentType, path, and the full execution context:
field("greeting" of !String) {
argument("name" of !String)
resolve {
val name = arguments["name"] as String
"Hello, $name!"
}
}Per-request data (such as the current user) can be passed via GExecutorContextExtensionSet and read inside any resolver:
object CurrentUserKey : GExecutorContextExtensionKey<User>
field("me" of type("User")) {
resolve {
execution.extensions[CurrentUserKey]
}
}Provide a fieldResolver to GExecutor.default to handle fields that have no inline resolver — useful for property-based default resolution or middleware:
val executor = GExecutor.default(
schema = schema,
fieldResolver = GFieldResolver { parent ->
// called for every field without an explicit resolver
next()
}
)Attach coercers directly on a Scalar definition using the three coercion hooks.
Called when the scalar value is provided inline in a query document (not as a variable):
val Date by type
Scalar(Date) {
coerceNodeInput { input ->
// input is the raw GValue AST node
when (input) {
is GStringValue -> LocalDate.parse(input.value)
else -> GError("Expected a date string").throwException()
}
}
}Called when the scalar value is provided via a query variable (already parsed from JSON):
Scalar(Date) {
coerceVariableInput { input ->
// input is the raw value from the variables map (e.g. String from JSON)
LocalDate.parse(input as String)
}
}Called when a resolver returns a value of this scalar type, converting it to a serializable form:
Scalar(Date) {
coerceOutput { input ->
// input is the value returned by a resolver
(input as LocalDate).toString()
}
}val executor = GExecutor.default(schema = schema)
// Execute from a query string (suspend function)
val result: GResult<Map<String, Any?>> = executor.execute("{ hello }")
// With operation name and variables
val result = executor.execute(
documentSource = "query GetUser(\$id: ID!) { user(id: \$id) { name } }",
operationName = "GetUser",
variableValues = mapOf("id" to "42")
)
// Convert result to a plain map for JSON serialization
val response: Map<String, Any?> = executor.serializeResult(result)
// { "data": { ... }, "errors": [...] }Use GExecutorContextExtensionSet to attach request-scoped data (auth context, request ID, etc.):
object CurrentUserKey : GExecutorContextExtensionKey<User>
val extensions = GExecutorContextExtensionSet {
set(CurrentUserKey, authenticatedUser)
}
val result = executor.execute(
documentSource = "{ me { name } }",
extensions = extensions
)By default, the root value passed to top-level field resolvers is Unit. Use GRootResolver to provide a meaningful root object:
val executor = GExecutor.default(
schema = schema,
rootResolver = GRootResolver.constant(myRootObject)
)Validate a parsed document against a schema before executing it. Requires the fluid-graphql-execution module.
val document = GDocument.parse("{ user { id name } }").valueOrThrow()
val errors: List<GError> = document.validate(schema)
if (errors.isEmpty()) {
println("Document is valid")
} else {
errors.forEach { println(it.describe()) }
}Validation covers the full GraphQL specification: field existence, argument types, fragment cycles, variable usage, directive locations, and more.
// Parse a query document
val result: GResult<GDocument> = GDocument.parse("{ hello }")
val document = result.valueOrThrow()
// Parse a schema SDL
val schemaResult: GResult<GDocument> = GDocument.parse("""
type Query {
hello: String
}
""".trimIndent())Every GNode can be serialized back to a GraphQL string via GNode.print(node) or simply toString():
val document = GDocument.parse("query { hello }").valueOrThrow()
println(document.toString())
// query {
// hello
// }
// Custom indentation
println(GNode.print(document, indent = " "))Operations that can fail return a GResult<Value> — a sealed class with Success and Failure variants. A Success may still carry non-fatal errors alongside its value.
val result: GResult<GDocument> = GDocument.parse(source)
// Pattern match
when (result) {
is GResult.Success -> println("Parsed: ${result.value}")
is GResult.Failure -> println("Errors: ${result.errors}")
}
// Convenient extractors
val document = result.valueOrNull() // null on failure
val document = result.valueOrThrow() // throws GErrorException on failure
val document = result.valueWithoutErrorsOrNull() // null if any errors present
// Transform
val names = result.mapValue { doc -> doc.definitions.map { it } }
// Chain
val validated = result.flatMapValue { doc ->
val errors = doc.validate(schema)
if (errors.isEmpty()) GResult.success(doc) else GResult.failure(errors)
}
// Early exit pattern
fun process(result: GResult<GDocument>): GResult<String> {
val doc = result.ifErrors { errors -> return GResult.failure(errors) }
return GResult.success(doc.toString())
}GError is the standard error type, following the GraphQL response format:
val error = GError(
message = "Not found",
path = GPath.ofName("user"),
extensions = mapOf("code" to "NOT_FOUND")
)
// Throw as an exception (caught by executor and turned into a response error)
error.throwException()
// Describe with source location context
println(error.describe())GErrorException bridges between the error-result model and Kotlin exceptions. It is thrown by valueOrThrow() and by GError.throwException(). Inside resolvers, throwing a GErrorException causes the executor to include its errors in the GraphQL response rather than propagating the exception.
throw GErrorException(GError("Something went wrong"))
// or equivalently:
GError("Something went wrong").throwException()By default, exceptions thrown inside resolvers or coercers propagate out of GExecutor.execute. Provide a GExceptionHandler to translate them into GraphQL errors instead:
val executor = GExecutor.default(
schema = schema,
exceptionHandler = GExceptionHandler { exception ->
when (exception) {
is NotFoundException -> GError(
message = exception.message ?: "Not found",
extensions = mapOf("code" to "NOT_FOUND")
)
else -> GError("Internal server error")
}
}
)The handler receives the exception via its GExceptionHandlerContext receiver, which also provides the GExceptionOrigin indicating whether the exception came from a field resolver, output coercer, input coercer, or root resolver.
Note: GErrorException (thrown via GError.throwException()) is never passed to the exception handler — it is always treated as a GraphQL error automatically.
GNodeExtensionSet provides a typed key-value map for attaching arbitrary metadata to any GNode without modifying the AST. This is the mechanism fluid-graphql itself uses to attach resolvers and coercers to schema nodes.
data class AuthRequirement(val role: String)
object AuthRequirementKey : GNodeExtensionKey<AuthRequirement>val schema = GraphQL.schema {
Query {
field("adminData" of !String) {
extension(AuthRequirementKey, AuthRequirement(role = "ADMIN"))
resolve { "secret" }
}
}
}val fieldDef: GFieldDefinition = schema.queryType!!.fieldDefinition("adminData")!!
val auth: AuthRequirement? = fieldDef.extensions[AuthRequirementKey]GExecutorContextExtensionKey is the per-request equivalent of GNodeExtensionKey. Use it to pass request-scoped values (current user, trace context, etc.) into resolvers without threading parameters through every call.
data class RequestContext(val userId: String, val isAdmin: Boolean)
object RequestContextKey : GExecutorContextExtensionKey<RequestContext>val extensions = GExecutorContextExtensionSet {
set(RequestContextKey, RequestContext(userId = "42", isAdmin = false))
}
val result = executor.execute(
documentSource = "{ me { name } }",
extensions = extensions
)Object<User>(type("User")) {
field("isAdmin" of !Boolean) {
resolve {
val ctx = execution.extensions[RequestContextKey]
ctx?.isAdmin ?: false
}
}
}Use GraphQL.document { } to build a GDocument without writing a query string:
val document = GraphQL.document {
query("GetUser") {
"user" {
argument("id", value("42"))
"id"()
"name"()
"email"()
}
}
}
// Equivalent to:
// query GetUser {
// user(id: "42") {
// id
// name
// email
// }
// }val doc = GraphQL.document {
mutation("CreatePost") {
"createPost" {
"id"()
"title"()
}
}
}
GraphQL.document {
subscription("OnMessage") {
"messageAdded" {
"id"()
"body"()
}
}
}val doc = GraphQL.document {
query {
"search" {
on("Post") {
"title"()
}
on("User") {
"name"()
}
fragment("CommonFields")
}
}
fragment("CommonFields") {
// on a particular type — set via the fragment builder
"id"()
"createdAt"()
}
}GraphQL.document {
query {
"user"(alias = "currentUser") {
"id"()
}
}
}io.fluidsonic.graphql
fluid-graphql-language
- GNode, GDocument, GSchema (AST model)
- GDocument.parse(), GNode.print()
- GResult, GError, GErrorException
- GNodeExtensionKey, GNodeExtensionSet
- Visitor, NodeWalker (AST traversal)
^
|
fluid-graphql-dsl
- GraphQL.schema { } (GSchemaBuilder)
- GraphQL.document { } (GraphQLDocumentBuilder)
^
|
fluid-graphql-execution
- GExecutor.default()
- GDocument.validate()
- GFieldResolver, GRootResolver
- GOutputCoercer, GNodeInputCoercer, GVariableInputCoercer
- GExceptionHandler
- GExecutorContextExtensionKey, GExecutorContextExtensionSet
Apache License 2.0 — see LICENSE for details.