Giter Site home page Giter Site logo

expediagroup / graphql-kotlin Goto Github PK

View Code? Open in Web Editor NEW
1.7K 32.0 338.0 88.75 MB

Libraries for running GraphQL in Kotlin

Home Page: https://opensource.expediagroup.com/graphql-kotlin/

License: Apache License 2.0

Kotlin 80.10% HTML 0.40% JavaScript 0.34% CSS 0.01% Dockerfile 0.01% MDX 19.15%
graphql graphql-java kotlin graphql-server schema-generator federation graphql-client graphql-plugin oss-portal-featured

graphql-kotlin's Introduction

GraphQL Kotlin

Continuous Integration Publish Docs Discussions Slack

GraphQL Kotlin is a collection of libraries, built on top of graphql-java, that simplify running GraphQL clients and servers in Kotlin.

Visit our documentation site for more details.

๐Ÿ“ฆ Modules

  • clients - Lightweight GraphQL Kotlin HTTP clients based on Ktor HTTP client and Spring WebClient
  • examples - Example apps that use graphql-kotlin libraries to test and demonstrate usages
  • executions - Custom instrumentations for a GraphQL operation
  • generator - Code-First schema generator and extensions to build Apollo Federation schemas
  • plugins - Gradle and Maven plugins
  • servers - Common and library specific modules for running a GraphQL server

โŒจ๏ธ Usage

While all the individual modules of graphql-kotlin are published as stand-alone libraries, the most common use cases are running a server and generating a type-safe client.

Server Example

A basic example of how you can run a GraphQL server can be found on our server documentation section.

Client Example

A basic setup of a GraphQL client can be found on our client documentation section.

๐Ÿ“‹ Documentation

More examples and documentation are available on our documentation site hosted in GitHub Pages. We also have the examples module which can be run locally for testing and shows example code using the libraries.

If you have a question about something you can not find in our documentation, the individual module READMEs, or javadocs, feel free to contribute to the docs or start a discussion and tag it with the question label.

If you would like to contribute to our documentation see the website directory for more information.

๐Ÿ—ž Blog Posts and Videos

The Blogs & Videos page in the GraphQL Kotlin documentation links to blog posts, release announcements, conference talks about the library, and general talks about GraphQL at Expedia Group.

๐Ÿ‘ฅ Contact

This project is part of Expedia Group Open Source but also maintained by a dedicated team

If you have a specific question about the library or code, please start a discussion for the community.

We also have a public channel, (#graphql-kotlin), open on the Kotlin Slack instance (kotlinlang.slack.com). See the info here on how to join this slack instance.

โœ๏ธ Contributing

To get started, please fork the repo and checkout a new branch. You can then build the library locally with Gradle

./gradlew clean build

See more info in CONTRIBUTING.md.

After you have your local branch set up, take a look at our open issues to see where you can contribute.

๐Ÿ›ก๏ธ Security

For more info on how to contact the team for security issues or the supported versions that receive security updates, see SECURITY.md

โš–๏ธ License

This library is licensed under the Apache License, Version 2.0

graphql-kotlin's People

Contributors

aarestu avatar bherrmann2 avatar brennantaylor avatar bsorbo avatar d4rken avatar dariuszkuc avatar de55 avatar dependabot[bot] avatar eg-oss-ci avatar gscheibel avatar gumimin avatar jonforest avatar josephlbarnett avatar koenpunt avatar macarse avatar martinbonnin avatar mgilbey avatar pdambrauskas avatar rharriso avatar rickfast avatar samuelandalon avatar sgeb avatar simboel avatar smyrick avatar sullis avatar t45k avatar tapaderster avatar thevietto avatar xenomachina avatar xetra11 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

graphql-kotlin's Issues

How do I ignore internal superclasses?

The schema generator should ignore internal superclasses.

Take the following example:

inner class User_WidgetsConnection (
  override val edges: User_WidgetsEdge,
  override val pageInfo: PageInfo
): KConnection<Widgets?>

internal interface KConnection<T> (val edges ...)

will throw: java.lang.IllegalArgumentException: Class declares 1 type parameters, but 0 were provided.

I think the reason is that Graphql does not support typing (which graphql-kotlin correctly complains), but I think the correct behaviour is to ignore the internal KConnection class in the first place.

Remove abstract functions in DataFetcherExecutionPredicate

@gscheibel

The DataFetcherExecutionPredicate right now is a hook that defaults to null in the SchemaGeneratorHooks so only when clients explicitly pass on in will they be running custom logic. This logic needs to be put in 3 locations, the abstract methods evaluate(), test(), and onError(). I think all 3 of these are not needed and we can even just remove the implementation of execute and make that the single function the client need to implement.

Let's look at the code we have for an example in the tests
https://github.com/ExpediaDotCom/graphql-kotlin/blob/master/src/test/kotlin/com/expedia/graphql/dataFetchers/DataFetchPredicateTests.kt#L65

  • Since clients need implement evaluate they need to return some way of indicating there was an error
  • They then need to understand this error format in test and return true or false if there was an error
  • Then they need to implement onFailure which returns Nothing so it must throw an exception.

The end results is: client implements error checking, either the value : T of execute is returned or an exception is thrown. We don't need all this roundabout methods to do that instead we could just have this

interface DataFetcherExecutionPredicate {
    /**
     * Perform the predicate logic by evaluating the argument and its value.
     * Then depending on the result, either: 
     *   - Return the value itself to continue the datafetcher invocation
     *   - Throw an exception
     *
     * @param value the value to execute the predicate against
     * @param parameter the function argument reference containing the KClass and the argument annotations
     * @param environment the DataFetchingEnvironment in which the data fetcher is executed (gives access to field info, execution context etc)
     */
    fun <T> evaluate(value: T, parameter: KParameter, environment: DataFetchingEnvironment): T
}

Apollo Federation Support

Is your feature request related to a problem? Please describe.
Support for generating schema as federated service. Federated services can be used by an apollo gateway to stitch together schemas from multiple graphql endpoints.

Describe the solution you'd like
Support generation of features in federation spec: https://www.apollographql.com/docs/apollo-server/federation/federation-spec/

Describe alternatives you've considered
N/A

Additional context
I use this project at work and want the benefits of a federated service. I am finishing up a different project now, but can start working on this in a few weeks. I wanted to check to see if you thought these additions would be an appropriate fit for this project.

Improving SchemaGenerator

I'm working on a PR that refactors SchemaGenerator. It's currently a bit unwieldy.

  • Move responsibilities for different GraphQLTypes into their own classes
  • Write extra test cases for each class (e.g. cover things like #76 )

Support for GraphQLID

Hi,

Of the "built-in" scalars, GraphQLID is missing. Is it possible to return an output type of GraphQLID?

Custom Scalar in InputType

Hi,

I have created a custom scalar for ZonedDateTime. This working fine for my queries. But in my mutatation I would like this class as my input and output types.

Question:
Is it possible to use your data class with custom scalar for ZonedDateTime both as InputType and Type? When I create an identical data class where the ZonedDateTime is replaced by a String it works. But I would like to know what the best practice is because a almost equal data class seems 'wrong' to me.

The InputType receives the following json from the frontend:

"period":{"from":"2019-06-04T10:00:00.000Z","till":"2019-07-08T10:00:00.000Z"}

Explanation:
When using the data class pasted below as InputType it first is converted to a ZonedDateTime by the Coercing<ZonedDateTime, String> and then it gets deserialized again by jackson to create the object with the following error:

Caused by: com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `java.time.ZonedDateTime` (no Creators, like default construct, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: UNKNOWN; line: -1, column: -1] (through reference chain: ee.seventythr.web.backend.domain.Project["period"]->ee.seventythr.web.backend.domain.Period["from"])

because the ZonedDateTime object is deserialized to

{"offset":{"totalSeconds":0,"id":"Z","rules":{"fixedOffset":true,"transitions":[],"transitionRules":[]}},"zone":{"totalSeconds":0,"id":"Z","rules":{"fixedOffset":true,"transitions":[],"transitionRules":[]}},"month":"JUNE","dayOfWeek":"TUESDAY","dayOfYear":162,"nano":0,"year":2019,"monthValue":6,"dayOfMonth":11,"hour":10,"minute":0,"second":0,"chronology":{"id":"ISO","calendarType":"iso8601"}}
data class Project(
    @Id
    @GraphQLID
    val id: String? = null,
    val name: String,
    val company: String,
    val description: String,
    val for_company: List<Image>? = emptyList(),
    val language: List<Image>? = emptyList(),
    val tools: List<Image>? = emptyList(),
    val period: Period,
    val display: Boolean? = true
)

data class Image(
    val name: String,
    val image: String?
)

data class Period(
        val from: ZonedDateTime,
        val till: ZonedDateTime
)
private objectDateCoercing : Coercing<ZonedDateTime, String> {
    override fun parseValue(input: Any?): ZonedDateTime = ZonedDateTime.parse(serialize(input), DateTimeFormatter.ISO_DATE_TIME) 

    override fun parseLiteral(input: Any?): ZonedDateTime? {
        val dateString = (input as? StringValue)?.value
        return ZonedDateTime.parse(dateString) 
    }

    override fun serialize(dataFetcherResult: Any?): String = dataFetcherResult.toString()
}

Recursive queries produce invalid schema introspection

Library Version

0.2.7

Describe the bug

There are GraphQLTypeReferences in the schema when a recursive query like NodeGraphTest is used.

To Reproduce

On the integration test NodeGraphTest, we actually have a failing schema. If you inspect the schema after build, you will notice that the type of Node.parent is GraphQLTypeReference. This should never happen as all schemas should have the full objects.

You can add the following assert to now make a failing test

assertFalse((schema.typeMap["Node"] as? GraphQLObjectType)?.getFieldDefinition("parent")?.type is GraphQLTypeReference)

Expected behavior

There are no GraphQLTypeReference in the code. This is fixed if I use graphql-java 2019-03-07T04-34-55-809a980

Root Resolvers

Apologies โ€“ I'm new to both GraphQL and Kotlin and this is more of a question than an issue, I think. But hopefully you can help me.

I issue a simple query something like this:

{
  bookById(id: 1) {
    name
    author {
      name
    }
  }
}

and get back the expected data. In my code that resolves this query I'm able to hook up author to a resolver (DataFetcher) by having a var late init author field on Book and by providing a DataFetcher that loads a Book (passed into SchemaGeneratorConfig's constructor when generating the schema).

Is there any way to hook up the bookByID part of the query to also automatically look up what DataFetcher to use rather than having to provide a fun bookById(id: Int): Book?? I'd rather have all the resolution of a query work the same way. Is wanting that "wrong" for any reason?

https://www.graphql-java-kickstart.com/tools/schema-definition/ indicates that in java-graphql you can create a GraphQLQueryResolver which will be "searched for methods that map to fields in their respective root types". Is there some field or annotation that I can put on my root query class that would cause the same kind of searching for a GraphQLQueryResolver or something similar?

Generate both interface and type from abstract class

I'm currently still playing around with graphql-kotlin. Biggest "issue" currently for me is that I'm using abstract classes in my model to implement base classes for some entity types (using hibernate to access database). When using the generator this generates a schema where I cannot query for a base type and then use the "...on SomeType" syntax to handle the subclasses. I would have to write interfaces mirroring the abstract classes properties which would mean repeating my code.

I thought about changing the generator so it will generate a type AND interface from an abstract kotlin class. But I was not able to dig deep enough into the generators code to archieve this. It is easy to generate an interface instead of a type but not two (interface and type).

I don't know if this would break any rules or conventions of GraphQL. I really like what I discovered so far but GraphQL seems to be a bit limited with inheritance. I could manually write the interfaces but that would mean double source of truth.

Maybe I'm missing something but would the solution of generating both interface and type be a feasible solution?

Support optional inputs?

The official graphql specs supports optional inputs. For example:

mutation updateRecord(input: UpdateRecordMutationInput!) {
  updatedRecord { ... }
}

input  UpdateRecordMutationInput {
  fieldA: Int
  fieldB: String!
}

Possible values for fieldA are blank, null or some integer. Blank corresponds to undefined in graphql-js, and is supported in graphql-java. Inputs are implemented as Maps in graphql-java, and whether a field is left blank can be checked by Map.containsKey(). One might interpret Blank as "don't update this field".

I suggest implementing some wrapper class around optional input fields.

Upgrade to graphql-java 13

Is your feature request related to a problem? Please describe.
graphql-java released v13.0 in June 2019. We should update to stay on top of releases.

https://www.graphql-java.com/

Describe alternatives you've considered
We are using an unsupported version 11. We could downgrade to the 9.X branch which has some long term support (LTS) but we should just stay on the latest until we get to another LTS release

Additional context
There were some breaking changes made that we will need to handle in our library

https://github.com/graphql-java/graphql-java/releases/tag/v12.0
https://github.com/graphql-java/graphql-java/releases/tag/v13.0

Is there any way to extract the generated graphql schema?

We implemented a serverless solution for our product with this library to handle the graphql layer. The only problem is that we have to generate the schema for every requests since we don't have a running instance to generate the schema on boot.

As our schema grow, so does the time to generate the schema. We were wondering if there is any way we could extract the generated schema so we could put it in a redis cache for our subsequent requests to fetch from.

graphql.AssertException: interfaceType can't be null

The following schema fails to compile with the following reason: "graphql.AssertException: interfaceType can't be null".

Removing inheriting Node2 from Widget2 stops the error, but I don't understand what's wrong.

class Query {
	@GraphQLDescription("Fetches an object given its ID")
	suspend fun node(
		@GraphQLContext context: Context,
		@GraphQLDescription("The ID of an object") id: String
	): Node2? = ...
	
	@GraphQLDescription("That's you")
	suspend fun viewer(
		@GraphQLContext context: Context
	): User2? = ...
	
}

@GraphQLDescription("An object with an ID")
interface Node2 {
	
	@GraphQLID
	@GraphQLDescription("The ID of an object")
	val id: String
}

class User2
private constructor(...)
	: Node2 {
	@GraphQLID
	override val id: String ...
	val username get() = data.username
	
	inner class User_WidgetsConnection
	internal constructor(first: Int, after: String?) {
		inner class User_WidgetsEdge
		internal constructor(
			val node: Widget2?,
			val cursor: String
		)
		suspend fun edges(@GraphQLContext context: Context) = ...
		suspend fun pageInfo(@GraphQLContext context: Context) = ...
		suspend fun totalCount(@GraphQLContext context: Context) = ...
	}
	fun widgetsConnection(first: Int, after: String?): User_WidgetsConnection = User_WidgetsConnection(first, after)
	
}
class Widget2 private constructor(...): Node2 {
	
	@GraphQLID
	@GraphQLDescription("The ID of an object")
	override val id: String = ...

	override suspend fun user(@GraphQLContext context: Context): User? = ... //<--- I think this is causing the error.
}

Untangle cyclic dependency between SchemaGenerator and type builders

We have this due to the recursive nature of the code. But does anyone have a better approach? This is just to bring up the discussion. If we don't come to a good answer we can keep the code as is will do no harm

I prefer having the separate type builders and that requires they have at least one common function they can call back to

Recursive Types

Hello, it seems that recursive types (i.e. an object that has a field with the same type) not supported:

class A { fun parent(): A? { // some implementation } }

Causes the following error:

Caused by: graphql.AssertException: type A not found in schema

ClassCastException: kotlin.reflect.jvm.internal.KTypeParameterImpl cannot be cast to kotlin.reflect.KClass

A method with a signature that contains nested type parameters:

fun monthlyBreakdown(): List<Pair<String, Totals>>? {

causes the library to throw a

java.lang.ClassCastException: kotlin.reflect.jvm.internal.KTypeParameterImpl cannot be cast to kotlin.reflect.KClass
	at com.expedia.graphql.schema.generator.TypesCache.getKClassFromKType(TypesCache.kt:64)
	at com.expedia.graphql.schema.generator.TypesCache.getCacheKeyString(TypesCache.kt:48)
	at com.expedia.graphql.schema.generator.TypesCache.get(TypesCache.kt:23)
	at com.expedia.graphql.schema.generator.SchemaGenerator.objectFromReflection(SchemaGenerator.kt:197)
	at com.expedia.graphql.schema.generator.SchemaGenerator.graphQLTypeOf(SchemaGenerator.kt:189)
	at com.expedia.graphql.schema.generator.SchemaGenerator.graphQLTypeOf$default(SchemaGenerator.kt:187)
	at com.expedia.graphql.schema.generator.SchemaGenerator.property(SchemaGenerator.kt:154)
	at com.expedia.graphql.schema.generator.SchemaGenerator.objectType(SchemaGenerator.kt:270)
	at com.expedia.graphql.schema.generator.SchemaGenerator.objectType$default(SchemaGenerator.kt:243)
	at com.expedia.graphql.schema.generator.SchemaGenerator.getGraphQLType(SchemaGenerator.kt:217)
	at com.expedia.graphql.schema.generator.SchemaGenerator.objectFromReflection(SchemaGenerator.kt:204)
	at com.expedia.graphql.schema.generator.SchemaGenerator.graphQLTypeOf(SchemaGenerator.kt:189)
	at com.expedia.graphql.schema.generator.SchemaGenerator.listType(SchemaGenerator.kt:241)
	at com.expedia.graphql.schema.generator.SchemaGenerator.getGraphQLType(SchemaGenerator.kt:214)
	at com.expedia.graphql.schema.generator.SchemaGenerator.objectFromReflection(SchemaGenerator.kt:204)
	at com.expedia.graphql.schema.generator.SchemaGenerator.graphQLTypeOf(SchemaGenerator.kt:189)
	at com.expedia.graphql.schema.generator.SchemaGenerator.graphQLTypeOf$default(SchemaGenerator.kt:187)
	at com.expedia.graphql.schema.generator.SchemaGenerator.function(SchemaGenerator.kt:149)
	at com.expedia.graphql.schema.generator.SchemaGenerator.function$default(SchemaGenerator.kt:113)
	at com.expedia.graphql.schema.generator.SchemaGenerator.objectType(SchemaGenerator.kt:273)
	at com.expedia.graphql.schema.generator.SchemaGenerator.objectType$default(SchemaGenerator.kt:243)
	at com.expedia.graphql.schema.generator.SchemaGenerator.getGraphQLType(SchemaGenerator.kt:217)
	at com.expedia.graphql.schema.generator.SchemaGenerator.objectFromReflection(SchemaGenerator.kt:204)
	at com.expedia.graphql.schema.generator.SchemaGenerator.graphQLTypeOf(SchemaGenerator.kt:189)
	at com.expedia.graphql.schema.generator.SchemaGenerator.graphQLTypeOf$default(SchemaGenerator.kt:187)
	at com.expedia.graphql.schema.generator.SchemaGenerator.function(SchemaGenerator.kt:149)
	at com.expedia.graphql.schema.generator.SchemaGenerator.function$default(SchemaGenerator.kt:113)
	at com.expedia.graphql.schema.generator.SchemaGenerator.addQueries(SchemaGenerator.kt:87)
	at com.expedia.graphql.schema.generator.SchemaGenerator.generateWithReflection(SchemaGenerator.kt:66)
	at com.expedia.graphql.schema.generator.SchemaGenerator.generate$graphql_kotlin(SchemaGenerator.kt:60)
	at com.expedia.graphql.ToSchemaKt.toSchema(toSchema.kt:23)
	at com.expedia.graphql.ToSchemaKt.toSchema$default(toSchema.kt:17)

as a workaround, the following works as expected:

class Bucket(val key:String, val totals:Totals)
fun monthlyBreakdown(): List<Bucket>? {

Support finding queries and mutations in the same class

Is your feature request related to a problem? Please describe.
I have a traditional java/kotlin service (that handles both mutations and queries) and I'd like to expose most of it's methods without repeating myself too much, while adding a necessary layer for authorization checks.

Currently, I have to create two classes: one that exposes my existing service's queries and another that exposes the mutations.

For example:

    class UserService {
        val users = mutableMapOf<Int, String>()
        fun getUser(id: Int) = users[id]
        fun createUser(name: String) {
            val id = users.keys.max() ?: 0 + 1
            users[id] = name
        }
    }

    class UserQueries(private val userService: UserService) {
        fun getUser(id: Int) = userService.getUser(id)
    }
    
    class UserMutations(private val userService: UserService) {
        fun createUser(name: String) = userService.createUser(name)
    }

This gets tedious when user has a lot of fields

Describe the solution you'd like
I'm not sure, but I think this might be easier if there was a way to pass lists of functions or function references to SchemaGenerator or QueryBuilder instead of having to define a class. Your library would allow users to compose a graphql api from functions and existing services.

Describe alternatives you've considered
You are probably aware of https://github.com/pgutkowski/KGraphQL. That project seems to have lost some steam, but has some cool characteristics in that it allows composing the graphql api in a direct, simple manner. Unfortunately, there's a lot of boilerplate involved.

Additional context
Add any other context or screenshots about the feature request here.

Polymorphic arguments supported?

I'm trying to define a query that accepts polymorphic arguments as input.

Here's what I have so far:

interface FilterDef {
    val field: String
}

data class StringRangeFilterDef(override val field: String, val min: String, val max: String) : FilterDef
data class IntRangeFilterDef(override val field: String, val min: Int, val max: Int) : FilterDef
data class StringValueFilterDef(override val field: String, val value: String) : FilterDef
data class IntValueFilterDef(override val field: String, val value: Int) : FilterDef
data class FilterList(val filters: List<FilterDef>)

class Query : GraphQLQueryResolver {
    fun queryObjects(filter: FilterList): Result {
        // calculate and return result
    }
}

When I try to load the generated schema in GraphiQL, it fails with a big red CDN-obfuscated javascript stacktrace. If I change FilterList to contain a specific type of filter, eg List<StringRangeFilterDef>s, then the schema loads ok.

The main difference I can see between the two is that with polymorphism, the Filter data types are OBJECTs, while without, the single StringRangeFilterDef is defined as an INPUT_OBJECT (and named "StringRangeFilterDefInput".

Is this a case that is currently supported, and I'm missing how to hook it up properly? If not, is there a best practice workaround for what I'm trying to do?

Publish code documentation

Summary

We have documentation generated on build with Dokka. We should publish/host these HTML pages somewhere so users can easily see the latest code and we don't have to maintain an verbose wiki

Features

Multiple versions

We don't want to remove the docs for 1.0.0 after we publish the next version

Online

The docs can be accessed without reading files in the GitHub file explorer

Possible Options

Support for default schema values

The GraphQL spec allows for arguments to have default values:

Kotlin allows specifying default values:

This information is not available on reflection though due to the way Kotlin compiles to the JVM:

We could solve this issue by adding a new annotation @GraphQLDefaultValue. This would require developers to duplicate some information but I think it would be worth it until we have a better way of extracting the info

fun getWidget(id: Int, @GraphQLDefaultValue(WidgetType.UNICORN) type: WidgetType = WidgetType.UNICORN) {
    return database.getWidgetByIdAndType(id, type)
}

SchemaGenerator does not support Kotlin built in array of primitives

SchemaGenerator currently only supports object arrays, e.g. Array<Int> which translates to Java Integer[]. Unfortunately when users try to use Kotlin builtin types for primitive arrays, e.g. IntArray, schema generator is unable to generate the correct type.

Modify
https://github.com/ExpediaDotCom/graphql-kotlin/blob/master/src/test/kotlin/com/expedia/graphql/schema/generator/SchemaGeneratorTest.kt#L284 to

    class QueryWithArray {
        fun sumOf(ints: IntArray): Int = ints.sum()
        fun sumOfComplexArray(objects: Array<ComplexWrappingType>): Int = objects.map { it.value }.sum()
    }

see the failure

How to wire up GraphQLDirective

I'm trying to use GraphQLDirective to do authentication, but I don't understand how I wire them up.

According to the GraphQL-Java docs I need the add the Directive to a RuntimeWiring instance and include that instance in the generated GraphQLSchema.

How can I do that using graphql-kotlin?

I'm trying to use them for authentication, e.g.:

Mutation - wrap group of functions by special type

Hi,

I am trying to use this library, but I cannot find an option to group a list of functions by specific type, for example:
My schema should be as the following:

type Mutation {
    car: CarMutation
    person: PersonMutation
}

type CarMutation {
    createCar(input: CreateCarInput!): CreateCarResult!
    updateCar(input: UpdateCarInput): UpdateCarResult!
}

type PersonMutation {
    createPerson(input: CreatePersonInput!): CreatePersonResult!
    updatePerson(input: UpdatePersonInput): UpdatePersonResult!
}

But , the schema is generated like the following:

type Mutation {
    createCar(input: CreateCarInput!): CreateCarResult!
    updateCar(input: UpdateCarInput): UpdateCarResult!
    createPerson(input: CreatePersonInput!): CreatePersonResult!
    updatePerson(input: UpdatePersonInput): UpdatePersonResult!
}

my data classes:

class CarMutation {
   fun createCar(input: CreateCarInput): CompletableFuture<CreateCarResult>? {
        return null
    }
  fun updateCar(input: UpdateCarInput): CompletableFuture<UpdateCarResult>? {
        return null
    }
  
} 

class PersonMutation {
   fun createPersonr(input: CreatePersonInput): CompletableFuture<CreatePersonResult>? {
        return null
    }
  
} 

main code:

@Service
class GraphQLMutationSchema(mutationTypes: MutationTypes) {

    val config = SchemaGeneratorConfig(listOf("example"), hooks = CustomSchemaGeneratorHooks)
    val queries = listOf(TopLevelObject(FakeQuery()))
    val mutations = listOf(TopLevelObject(carMutation), TopLevelObject(personMutation))

    fun generateSchema(): GraphQLSchema {
        return toSchema(queries, mutations, config)
    }

When I use a wrapper class to build the schema, no objects will be generated, because the library suppose all functions are declared in the first topLevel Object!!!
for example:

@Component
data class MutationTypes(val carMutation: CarMutation, val persopnMutation: PersonMutation)

 val mutations = listOf(TopLevelObject(mutationTypes))

when I build it with the wrapper class(MutationTypes) - no mutation function are generated!!!

the result:
type Mutation {
}

Great day
Tom Lev

@GraphQLDescription doesn't work on Enums?

package com.cubetutor.api.graphql.types.enums

import com.expedia.graphql.annotations.GraphQLDescription

@GraphQLDescription("testA")
enum class DeckType {
    @GraphQLDescription("testB")
    DRAFT,
    @property:GraphQLDescription("testC")
    SEALED,
    WINSTON_DRAFT, GRID_DRAFT
}

=>

enum DeckType {
  DRAFT
  GRID_DRAFT
  SEALED
  WINSTON_DRAFT
}

in the schema.

Elsewhere I see my annotations that are attached in much the same way to data classes working fine:

@GraphQLDescription("A deck.")
data class Deck(
    @property:GraphQLDescription("ID of this deck.")
    val id: Int,
    ...

=>

#A deck.
type Deck {
  #Bot count.
  botCount: Int
  #DeckType of this deck.
  deckType: DeckType!
  ...

Is something extra needed to support these annotations on Enums? Or are Enums not supported? Or did I mess something up?

(I also seem to have this problem on my top-level query, CubeQuery, but all the objects work great.)

Schema is produced like this:

        val schemaConfig = SchemaGeneratorConfig(
            supportedPackages = listOf("com.cubetutor.api.graphql"),
            topLevelQueryName = "CubeQuery",
            topLevelMutationName = "CubeMutation",
            dataFetcherFactory = ResolverFactory()
        )

        val schema = toSchema(
            queries = listOf(TopLevelObjectDef(CubeQuery())),
            mutations = emptyList<TopLevelObjectDef>(),
            config = schemaConfig
        )
        println(SchemaPrinter().print(schema))

Issue #136 (graphql.AssertException: interfaceType can't be null) isn't fixed

Please revisit issue #136. Union types are fixed but interface types remain broken.

interface SomeUnion
class A: SomeUnion { fun getB(): B }
class B: SomeUnion { fun getA(): A }

The above builds properly.

interface SomeInterface {
  val id: String
}
class C: SomeInterface {
  fun getD(): D 
  override val id: String { ... }
}
class D: SomeInterface { 
  fun getC(): C 
    override val id: String { ... }
}

This doesn't.

Directives on enum values

Library Version
0.2.1

Describe the bug
Directives are not being set on enum values. It should be supported according to the GraphQL spec

Current printed schema

enum MyEnum {
  ONE
}

To Reproduce

  • Add any directive annotation to an enum value
@GraphQLDirective(locations = [DirectiveLocation.ENUM_VALUE]
annotation class MyDirective

enum class MyEnum {
    @MyDirective
    ONE
}
  • Print the schema with a schema printer
  • There are no directives on the value

Expected behavior

enum MyEnum {
  ONE @myDirective
}

Access to HttpServletRequest

Is there any way to access/modify the HttpServletRequest, for example in a custom GraphQLContext ?

This would be particularly useful for i18n, so that we can read a queryParam and force a messages Locale (for Spring Boot)?

Something like:
RequestContextUtils.getLocaleResolver(request).setLocale(request, response, LocaleUtils.toLocale(request.getParam("lang")));

Many thanks

Support hooks to add custom documentation to objects, fields, arguments and enum values

We came up with a documentation technique that works well for us (syncing both development teams and our product documentation team). We're trying to resolve the relevant Markdown documentation for each object, field, argument and enum value.

SchemaGeneratorHooks is an awesome general purpose vehicle for intercepting schema generation. I think it can be very helpful in this case, but it would have to be extended to include hook methods that would allow me to augment builder objects just before calling build(). That way I can call the builders' description method and add my own documentation. Specifically I'm asking for the inclusion of the following hooks:

fun willGenerateGraphQLObject(objectName: String, builder: graphql.schema.GraphQLObjectType.Builder): graphql.schema.GraphQLObjectType.Builder = builder

fun willGenerateGraphQLField(objectName: String, fieldName: String, builder: graphql.schema.GraphQLFieldDefinition.Builder): graphql.schema.GraphQLFieldDefinition.Builder = builder

fun willGenerateGraphQLEnumType(enumName: String, builder: graphql.schema.GraphQLEnumType.Builder): graphql.schema.GraphQLEnumType.Builder = builder

fun willGenerateGraphQLEnumValue(enumName: String, enumValue: String, builder: graphql.schema.GraphQLEnumValueDefinition.Builder): graphql.schema.GraphQLEnumValueDefinition.Builder = builder

fun willGenerateGraphQLArgument(objectName: String, fieldName: String, argumentName: String, builder: graphql.schema.GraphQLArgument.Builder): graphql.schema.GraphQLArgument.Builder = builder

I added the names of the objects/fields/arguments/enums to the hooks' contexts since it's difficult to extract that information from the builders directly.

What do you think?

Thanks a lot for a fantastic library!

Optional results should return null values

I have a query where the result has optional values

e.g.

    data class SomeData(
        val requiredResult: String,
        val optionalResult: String? = null
    )
type {
    optionalResult: String
    requiredResult: String!
}

Querying this gives me

{
  "data": {
    "someQuery": {
      "requiredResult": "somedata"
    }
  },
  "errors": [],
  "dataPresent": true
}

Clientside, using apollo-client, I didn't get my data. I stumpled upon the same issue here:

apollographql/apollo-client#4267

which suggests that the graphql spec forbids just omitting values and instead we should return:

    "someQuery": {
      "requiredResult": "somedata",
      "optionalResult": null,
    }

Support explicit order of directives

Users can specify multiple directives on the target fields, e.g.

class MyQuery  {

  @Directive1
  @Directive2
  fun doSomething(): String {
    // does something
  }
}

Currently we process those directives in the order annotations are returned by Java/Kotlin. This order is implied but it is also unlikely the default behavior will change in the near future.

We should explore whether we would want to add explicit mechanism for ordering the directives. We could consider doing something similar to how Spring orders their annotations.

Support custom schema type names via `@GraphQLName` on classes

Is your feature request related to a problem? Please describe.

It would be useful to support custom names for the schema types, instead of relying only on reflection-based naming.

In the case at hand, the GraphQL endpoint is part of a bigger application, leading to name clashes between GraphQL model classes and domain classes. In files which require both the domain model classes as well as the GraphQL model classes, I work around the issue by importing one of them with an alias, e.g.:

import my.app.core.entity.Page as DmPage

(in this case Dm stands for Domain Model)

This works, but is cumbersome to maintain. (It also confuses IntelliJ, which is minor nuisance and not a strong justification for code change.) In addition it would allow for easier schema evolution (i.e. changing code while maintaining API contract).

Describe the solution you'd like

The proposed solution is to support a @GraphQLName annotation, applicable to GraphQL type classes. Without this annotation, naming is still based on reflection; but with the annotation, the name is overridden for that type class. The result would look like this:

@GraphQLName("Geography")
@GraphQLDescription("A place")
data class GqlGeography(
    val id: Int?,
    val type: GeoType,
    val locations: List<Location>
)

The schema builder would build a GraphQLType named Geography instead of GqlGeography. This would allow for clear handling of classes with conflicting names.

Describe alternatives you've considered

I've looked into fitting a rename into SchemaGeneratorHooks, but couldn't find an appropriate hook function. Furthermore it makes sense for the schema name to be close to the type class definition.

Additional context

I have the changes implemented in my private fork. Please let me know if that's a desirable feature for upstream and I'll open a PR.

kClassExtensions only check declared functions and properties

Library Version
0.2.9

Describe the bug
Extension functions getValidProperties and getValidFunctions in kClassExtension.kt only check declared members on KClass thus ommiting inherited properties and functions. This leads to validation error "object type 'SomeType' does not implement interface 'SomeInterface' because field 'SomeField' is missing". when using inheritance with abstract classes.

To Reproduce
My model:

interface SomeInterface {
  var someField: String?
}

abstract class SomeAbstractClass : SomeInterface {
   override var someField: String? = null
   var justAnotherField: String?
}

class SomeClass : SomeAbstractClass {
   override var justAnotherField: String? = null
}

Expected behavior
Interface generation should work with inherited fields, shouldn't it? kClassExtensions could use this.memberProperties/this.memberFunctions instead of this.declaredMemberProperties and this.declaredMemberFunctions.

How to ensure custom scalar is output in schema?

I am using SchemaGeneratorHooks to provide a Url custom scalar (graphql.scalars.url.UrlScalar from graphql-java-extended-scalars).

Everything works great except scalar Url is not present in the schema as printed by SchemaPrinter.

How do I turn that on?

This is my SchemaGeneratorHooks:

class CustomSchemaGeneratorHooks : SchemaGeneratorHooks {
    val urlScalar = UrlScalar()

    override fun willGenerateGraphQLType(type: KType): GraphQLType? = when (type.classifier as? KClass<*>) {
        URL::class -> urlScalar
        else -> null
    }
}

And this is how I generate the schema:

    fun schema(): GraphQLSchema {
        val schemaConfig = SchemaGeneratorConfig(
            supportedPackages = listOf("com.cubetutor.api.graphql"),
            topLevelQueryName = "Query",
            topLevelMutationName = "Mutation",
            hooks = CustomSchemaGeneratorHooks(),
            dataFetcherFactory = ResolverFactory()
        )

        val schema = toSchema(
            queries = listOf(TopLevelObjectDef(CubeQuery())),
            mutations = emptyList<TopLevelObjectDef>(),
            config = schemaConfig
        )
        println(SchemaPrinter().print(schema)) // <-- this schema does not contain `scalar Url` and is therfore invalid
        return schema
    }

Thanks!

Query with input type

Dear all,
Very nice library, bravo :)
I have a little question, I would like to generate a Graphql schema that is compatible with Prisma API (for queries only)...

Prisma defines its query arguments with an Input "criteria", that could be similar to this:

@Component
class AnimalQueries : Query {
    fun findAnimal(): Animal = Animal(1, "cat")
    fun findAnimals(where: AnimalCriteria) : List<Animal> = if("c".equals(criteria.type_contains)) .... // return cats 
}

data class AnimalCriteria(val type_contains: String?, val id_gte: Int?)
data class Animal(val id: Int,val type: String)

So one can write the following GraphQL query:

query {
  findAnimals(where: { type_contains: "c" }) {
    id
  }
}

My question is, what is your suggestion to generate the AnimalCriteria automatically?

Ideally I would like to do something with annotations:

data class Animal(
@GraphQLCriterias({"gte", "eq", "in"})
val id: Int,
@GraphQLCriterias({"contains", "in"})
val type: String)

that would generate the following AnimalCriteria in the GraphQL schema "like Prisma"
https://www.prisma.io/docs/understand-prisma/prisma-vs-traditional-orms/prisma-vs-typeorm-k9fh/#other-filter-criteria

input AnimalCriteriaInput {
  id_gte: Int
  id_lt: Int
  id_in: [Int]
  type_contains: String
  type_in: [String]
  OR: [AnimalCriteriaInput]
  AND: [AnimalCriteriaInput]
}

The main problem, is that one still need to use the AnimalCriteria in the code to implement the service taking into consideration the given criterias.

fun findAnimals(where: AnimalCriteria) : List<Animal> = listOf(Animal(1, "dog"))

Do you have any suggestion on how to achieve this?

Support kotlin.collections.Set

Is your feature request related to a problem? Please describe.
Right now the current support collections we can convert to a GraphQL list (List, Array) to do provide a uniqueness guarantee like Set

Describe the solution you'd like
We would like to use a Set<T> as a unique collection of values instead of a List<T> or Array<T>

Describe alternatives you've considered
We could use List in the schema code but then we would have to manually perform some operation to remove duplicates in the list

Additional context
This is the error that is thrown when Set is used in the schema

Cannot convert kotlin.collections.Set<kotlin.String> since it is outside the supported packages [com.expedia]

Example for Authentication

I'm struggling with providing a schema based on current user authentication. It would be great to have an example, where the schema and the validation of the request are handled by a simple user role manner.

For simplification, I would use a simple basic authentication handler (which may be the spring security handler) and three different user/roles (anonymous, editor, admin) where the anonymous can only read data, an editor can read and edit basic data and an administrator has some other modification permissions.

My quick and dirty way was using a user aware context and check on the mutation if the current user has sufficient rights to execute it. But every user can see the complete schema.

Is it possible to set custom scalars for specific class properties of the same type?

Let's say we have this class as an example:
data class Salary( val employer: Employer? = null, val amount: Int? = null, val month: Int? = null, )

When defining .graphql files for this type it is also possible to use Scalars for month and amount. In my case there is a Scalar used for amount that validates that value is a positive one and for month there is a Scalar that allows only values from 1 to 12.

By setting scalars in this way we can see in the schema the exact type used for this fields and also the description for each scalars.

In CustomSchemaGeneratorHooks we can define scalars only by type and I can't see any solution by using it for my purpose.

Is there any other solution for this?
If there is not any solution at the moment would it be possible to implement such a feature?

Cyclic schema definitions still don't work

Hi,

It seems the latest release broke cyclic schemas again. The following cyclic schema still doesn't work

graphql.AssertException: type Widget not found in schema
interface Node {@GraphQLID val id: String }
class User: Node {
@GraphQLID
override val id = ...
fun widget(@GraphQLContext context: Context): Widget = ...
}

class Widget: Node {
@GraphQLID
override val id = ...
fun user(@GraphQLContext context: Context): User = ...
}

I need to traverse User -> Widget -> User, and both are implementing Nodes.
It only works if I drop one of the cyclical references.

field level resolvers

Library version: 0.0.33

This is more of "how to" type of question. Our current code working fine as mostly query is resolved by a single data source. But now we have a requirement that, clients can add a new field sometimes in same query where response for that field should be coming from another data source(another REST API). Kotlin reflection doing magic to match a query(root) name with concern data fetcher( kotlin suspend function) without explicitly using any of library data fetcher classes. but not sure how to call a second resolver suspend function when a field exists in a query. Can i get some samples please?. Our full code block is in this Stack Overflow link. Thanks.

Don't include directive names automatically via GraphQLDescription?

https://github.com/ExpediaDotCom/graphql-kotlin/blob/861443970ae9859710967ae65c257e23bee47595/src/main/kotlin/com/expedia/graphql/schema/extensions/annotationExtensions.kt#L23-L32

Currently we include directives names automatically in schema descriptions, I think this should not be done.

  • Duplicate information: The graphQL fields already support deprecated, we don't need an additional Directive: deprecated in the description
  • Exposing internal implementation details: Something like @requiresPermision(...) is not necessarily something that should be exposed

I think the option is good, but it should not be the default, AFAIK if desired, a developer could use SchemaGeneratorHooks.onRewireGraphQLType to achieve the same. We could provide a helper method that appends directive names to the description.

Release 1.0.0

We have reached a slow down in changes. We should release a 1.0.0 cut and have this library officially released.

Before we do that we need to make any last breaking changes. Are there any requests?

data class with interface and default implementation for getter will not render schema

Hi,

I'll ask this question here because I ran into this issue building a Kotlin GraphQL application and I searched a lot on the internet for a similar example but could not find it.

I would like to have data classes implement an interface and would like to have them a convenient getter for a Base64 encoded universal ID.

My interface:

package ee.seventythr.web.backend.domain

import com.expedia.graphql.annotations.GraphQLID
import graphql.relay.Relay

interface Base {
    @GraphQLID
    val _id: String?

    val id: String?
        get() = _id?.let { Relay().toGlobalId(this::class.toString(), it) }
}

And my data class:

package ee.seventythr.web.backend.domain

import com.expedia.graphql.annotations.GraphQLID
import org.springframework.data.annotation.Id
import org.springframework.data.mongodb.core.mapping.Document

@Document(collection = "for_company")
data class ForCompany(
    @Id
    @GraphQLID
    override val _id: String? = null,
    val name: String,
    val image: String
): Base

And my mutation:

@Component
class ForCompanyMutation(val forCompanyRepository: ForCompanyRepository) : Mutation {
    @Value("\${files.forCompany}")
    lateinit var imagePath: String

    private val logger by LoggerDelegate()

    @GraphQLDescription("add for company")
    fun addForCompany(input: UploadImage, @GraphQLContext context: MyGraphQLContext): ForCompany {
        // if (!context.authenticated) throw NotAuthorizedException("Mike")
        File(imagePath, input.filename).writeBytes(stringToByteArray(input.data))
        return save(
            ForCompany(
                    name = input.name,
                    image = "${imagePath.substringAfterLast("/")}/${input.filename}"
            ), forCompanyRepository
        )
    }
}

fun <T:Base> save(document: T, repository: MongoRepository<T, String>): T {
    val savedDocument = repository.save(document)
    return savedDocument
}

But when starting my spring boot application I get the following exception:

	... 47 common frames omitted
Caused by: graphql.schema.validation.InvalidSchemaException: invalid schema:
object type 'ForCompany' does not implement interface 'Base' because field 'id' is missing
	at graphql.schema.GraphQLSchema$Builder.build(GraphQLSchema.java:344) ~[graphql-java-11.0.jar:na]
	at com.expedia.graphql.generator.SchemaGenerator.generate$graphql_kotlin(SchemaGenerator.kt:57) ~[graphql-kotlin-0.2.6.jar:na]
	at com.expedia.graphql.ToSchemaKt.toSchema(toSchema.kt:23) ~[graphql-kotlin-0.2.6.jar:na]
	at ee.seventythr.web.backend.configuration.GraphQL.schema(GraphQL.kt:58) ~[classes/:na]
	at ee.seventythr.web.backend.configuration.GraphQL$$EnhancerBySpringCGLIB$$71f0851e.CGLIB$schema$0(<generated>) ~[classes/:na]
	at ee.seventythr.web.backend.configuration.GraphQL$$EnhancerBySpringCGLIB$$71f0851e$$FastClassBySpringCGLIB$$f989bb65.invoke(<generated>) ~[classes/:na]
	at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244) ~[spring-core-5.1.5.RELEASE.jar:5.1.5.RELEASE]
	at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:363) ~[spring-context-5.1.5.RELEASE.jar:5.1.5.RELEASE]
	at ee.seventythr.web.backend.configuration.GraphQL$$EnhancerBySpringCGLIB$$71f0851e.schema(<generated>) ~[classes/:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:na]
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:na]
	at java.base/java.lang.reflect.Method.invoke(Method.java:566) ~[na:na]
	at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154) ~[spring-beans-5.1.5.RELEASE.jar:5.1.5.RELEASE]
	... 48 common frames omitted

Disconnected from the target VM, address: '127.0.0.1:58404', transport: 'socket'

Process finished with exit code 1

When I move the default getter to the data class itself it works but I would like to define it in the interface so all my classes can use it.

I am doing something wrong but can not find relevant examples to learn from so I hope somebody can help me out.

Thanks in advance,

Mike

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.