Skip to content

Generate your graphql schema with a code-first kotlin dsl

License

Notifications You must be signed in to change notification settings

Gui-Yom/graphql-dsl

Repository files navigation

graphql-dsl

GitHub tag (latest SemVer) Maven Central

Build your GraphQL schema with a declarative Kotlin DSL.

See also graphql-dsl-test to test your schema.

Installation

Artifacts are published to Github Packages and Maven Central. With Gradle :

dependencies {
    implementation("io.github.gui-yom:graphql-dsl:0.9.0")
    testImplementation("io.github.gui-yom:graphql-dsl-test:0.9.0")
}

Those artifacts are built with jdk 17.

Example

val schema = GraphQLSchema {

    // Declare a scalar type given its Coercing implementation
    scalar(UrlCoercing, "Url")

    // Declare MyId as a graphql ID scalar
    id<MyId>()

    // Declare an enum directly based on its values
    enum<Baz>()

    // Declare an input type
    input<Input>()

    // Declare an interface
    !"This describes my Node interface"
    inter<Node> {
        // Construct the interface fields from the class member functions 
        derive()
    }

    // Define a type backed by a kotlin class
    !"""
        This is a cool looking multiline description
        No need to .trimIndent()
    """
    type<Foo> {
        inter<Node>()

        // Can be a property
        include(Foo::id)
        // Can be a member function
        // We can rename fields too
        include(Foo::dec, "decrement")
        // Shorthand notation
        +Foo::field
        // Can be a custom field
        // The lambda uses Foo as receiver
        field("inc") { -> field + 1 }
    }

    type<Bar> {
        inter<Node>()

        // Automatically include properties and member functions
        derive {
            // Exclude a field
            -Bar::field
        }

        // Use the input type we defined earlier
        field("custom") { param: Input ->
            param
        }

        // List are expected to work too !
        field("custom2") { a: List<Int>, b: Int ->
            a.map { it * b }
        }

        // No need for field()
        // You can declare fields using the invoke operator defined on String in this context
        "custom3" { a: Int, b: Float, c: MyId ->
            "$c: ${a * b}"
        }

        // Custom fields support up to 6 arguments

        // You can inject special arguments
        // They won't be included in the schema
        "consumesEnv" { env: DataFetchingEnvironment -> env.executionId }
    }

    // The main query object
    // Specify root query fields here
    // Here we derive our root query object from a kotlin object
    // object Query {
    //   fun allFoos(): List<Foo> = ...
    // }
    query(Query)

    // No need for a Kotlin object, define your query type directly with custom fields
    query {
        "answer" { -> 42 }
    }

    // Also
    type<Bar>()
    // is directly equivalent to
    type<Bar> { derive() }
    // Same with every other type builders
} // Returns a ready to use GraphQLSchema

// You can use the GraphQLSchema.print() extension to render your schema to a String
println(schema.print())

An example project showcasing the GraphQL introduction' Star Wars schema is available under examples/starwars.

Features

  • All operations (query, mutation, subscription)
  • Object types, backed by a Kotlin type
  • Custom scalars
  • Interfaces, backed by a Kotlin supertype
  • Input objects, backed by a kotlin data class, can reference other input objects including self
  • Enums, backed by a Kotlin enum class
  • Derive fields for types automatically from member properties, functions and custom exclusion rules
  • Suspend, Deferred and CompletableFuture support for async fields
  • Flow and Publisher support for subscription fields
  • Suspend custom fields (fields without a property or a function)
  • Schema element descriptions through annotations or directly in the dsl
  • Map type support through automatic conversion to List

Considered features

  • Non suspend custom fields
  • Support primitive arrays and object arrays
  • Field argument default value (need annotations)
  • Support generics types (throw exception on *-projection, type arguments must be declared in the schema, monomorphisation)
  • Support custom map entries (name, key name, value name)
  • Directive support
  • Union types
  • Relay types builders (connection, edge, pageinfo), similar to graphql-java's relay helpers
  • Ensure that as many checks as possible are done during the first step of schema building so errors are thrown with a useful line number (fail fast, maybe change the way the schema is generated)
  • Map everything at initialization so minimal work is done at runtime
  • Explore a way to verify and generate the schema at compile time through a compiler plugin or a gradle plugin

graphql-dsl-test

The graphql-dsl-test artifact includes small utilities to make testing your schema code through graphql queries easier.

Example

@Test
fun testMyQuery() = withSchema({
        query {
            "test" { a: Int -> 2 * a }
        }
    }) {
        // The above schema will be printed through a debug logger
        // The receiver exposes the following properties and functions

        // Access the GraphQL instance and the schema
        graphql.execute("query { }")
        assertTrue(schema.typeMap.contains("Query"))

        // Execute a query and do something with the ExecutionResult as receiver
        withQuery("""query { test(a: 21) }""") {
            assertTrue(errors.isEmpty())
        }

        // This checks that there are no errors and print them in case of failure
        assertQueryReturns("""query { test(a: 21) }""", mapOf("test" to 42))
        // also equivalent to
        """query { test(a: 21) }""" shouldReturns mapOf("test" to 42)
    }

About

Generate your graphql schema with a code-first kotlin dsl

Topics

Resources

License

Stars

Watchers

Forks

Packages

 
 
 

Languages