Giter VIP home page Giter VIP logo

gql's Introduction

license main Maven Central Sonatype Nexus (Snapshots)

Description

GQL is a set of Groovy DSLs and AST transformations built on top of GraphQL-java that make it easier to build GraphQL schemas and execute GraphQL queries without losing type safety.

Gradle

In order to use GQL releases in your Groovy code add the maven central repository and if you'd like to evaluate some snapshots add the sonatype snapshots repository as well:

repositories {
    mavenCentral()
    maven { url 'https://s01.oss.sonatype.org/content/repositories/snapshots/' } // FOR SNAPSHOTS
}

Once you've declared the maven repositories then add the dependencies to your project:

dependencies {
    implementation 'com.github.grooviter:gql-core:VERSION'
    implementation 'com.github.grooviter:gql-ratpack:VERSION'
}

Getting Started

You can directly execute the following example in your Groovy console:

@Grab('com.github.grooviter:gql-core:0.5.0')
import gql.DSL

def GraphQLFilm = DSL.type('Film') {
  field 'title', GraphQLString
  field 'year', GraphQLInt
}

def schema = DSL.schema {
  queries {
    field('lastFilm') {
      type GraphQLFilm
      staticValue(title: 'SPECTRE', year: 2015)
    }
  }
}

def query = """
  {
    lastFilm {
      year
      title
    }
  }
"""

def result = DSL.newExecutor(schema).execute(query)

assert result.data.lastFilm.year == 2015
assert result.data.lastFilm.title == 'SPECTRE'

Documentation

Current documentation is available at: http://grooviter.github.io/gql/

gql's People

Contributors

mariogarcia avatar socseross 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

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

gql's Issues

Shortcut for queries returning a GraphQLList ?

Instead of writing

def schema = DSL.schema {
        queries {
            field('history') {
                type new GraphQLList(GraphQLHistory)
                fetcher {env -> return DATA}
            }
        }
    }

I would like to write:

def schema = DSL.schema {
        queries {
            field('history') {
                type [GraphQLHistory]
                fetcher {env -> return DATA}
            }
        }
    }

Hello world example should use a query string

I think it could be easier for someone that knows something about GraphQL to start executing queries in GraphQL language instead of trying to figure out how query builder works in the first place.

QueryBuilder: How to build nested queries?

Most GQL APIs have nested objects and I'm puzzled by how to build these queries.
Here is an example of getting trying to get Pull Requests from the Github API:

String queryString = DSL.buildQuery {
    query('repository', [owner: 'foobar', name: 'baz']) {
      returns {
        query('pullRequests', [first: 10, headRefName: 'master', state: ['OPEN']]) {
          returns {
            totalCount
          }
        }
      }
    }
  }

But this generates a string like "{ pullRequests (first: 10,headRefName: \"master\",state: [OPEN]) { \n \ttotalCount\n } \n repository (owner: \"foobar\",name: \"baz\") { \n } \n }"
Which is clearly incorrect. I tried without the returns for the upper query, but it's a required field

Fetcher closure requires to return explicitly Object

The signature of adding a fetcher as a closure is the following:

FieldBuilder fetcher(Closure<Object> fetcher)

The closure returns an object because a data fetcher could return any kind of type.

The problem is that when our closure returns something other that Object the static compilation complains because the type returned is not explicitly casted as an object. Despite the fact that it should work because every object is eventually an object, the generic argument should be removed in order not to be checked by the static compilation, and therefore to be able to return anything it wants.

FieldBuilder fetcher(Closure fetcher)

or maybe

FieldBuilder fetcher(Closure<?> fetcher)

Add enum type creation to DSL

Example:

DSL.enum('Countries') {
    description 'EU countries'

    value 'SPAIN', 'es'
    value 'ITALY', 'it'
}

It seems that if you declare a method like:

def 'enum'(String name, Closure dsl) {
   // implementation
}

Then you can call enum('Countries') { }

Add relay abstractions

As starting point it would be enough by creating a DSL on top of graphql-java's relay package and evolve from that.

Default value for arguments

Hi!
The graphql supports the default values for the arguments, but this is not the case in this DSL (which sometimes is very distracting).

It is also unclear how to manage the mandatory parameters in dsl (analogue '!')

Thanks!

Add field to `DSL` to allow decoupling query fetching

Motivation

At the moment when creating a web application, the way you can add a fetcher is by adding the fetcher directly:

class GraphQLHandler implements Handler {

  @Inject
  SystemService systemService

  @Override
  void handle(Context ctx) throws Exception {
    def payload = ctx.parse(Map).get()
    def query = payload.query
    def params = payload.variables as Map<String,Object>

    ctx.render(
      json(
        execute(schema, query, params)))
  }

  GraphQLSchema getSchema() {
    return schema {
      query('queryRoot') {
        field('service') {
          type ServiceType.SERVICE_TYPE
          fetcher { DataFetchingEnvironment env ->
            return [
              version: systemService.systemVersion,
              os: systemService.systemOS
            ]
          }
        }
      }
    }
  }
}

That's ok, but when the code base starts growing, it's just too much code. It would be better if the whole field code could be extracted and maybe injected later like:

class GraphQLHandler implements Handler {

  @Inject SystemGraphQL systemGraphQL
  @Inject WheatherGraphQL wheatherGraphQL

  @Override
  void handle(Context ctx) throws Exception {
    def payload = ctx.parse(Map).get()
    def query = payload.query
    def params = payload.variables as Map<String,Object>

    ctx.render(
      json(
        execute(schema, query, params)))
  }

  GraphQLSchema getSchema() {
    return schema {
      query('queryRoot') {
        addField systemGraphQL.queryField
        addField wheatherGraphQL.queryField
      }
    }
  }
}

That could evolve to create just a single instance of a given schema, and therefore your handler will look like this:

class GraphQLHandler implements Handler {

  @Inject GraphQLSchema schema

  @Override
  void handle(Context ctx) throws Exception {
    def payload = ctx.parse(Map).get()
    def query = payload.query
    def params = payload.variables as Map<String,Object>

    ctx.render(json(execute(schema, query, params)))
  }
}

Proposal

Although the addField method inside the SchemaBuilder is already in place, but there's no direct way to create a field using the DSL. A method called DSL.field { } should be created to address this.

Use query alias as GraphQL specification

At the moment the alias is added as a method call at the end of the query:

import gql.DSL

def result = DSL.execute(schema) {
  query('byYear', [year: '1962']) {
    returns(Film) {
      title
      year
    }
    alias 'first'
  }

  query('byYear', [year: '2015']) {
    returns {
      title
      year
      bond
    }
    alias 'last'
  }
}

It would be great to use statement labels as query alias. Something like this:

import gql.DSL

def result = DSL.execute(schema) {
  first: query('byYear', [year: '1962']) {
    returns(Film) {
      title
      year
    }
  }

  last: query('byYear', [year: '2015']) {
    returns {
      title
      year
      bond
    }
  }
}

That I think should involve an AST transform.

RxJava based DataFetcher ?

Would it be possible to use an RX based observer for a DataFetcher ? For example using the mongo.rx client would return an Observer:

Observable<Success> insertIntoTest(Document document) {
        return collection.insertOne(document)
    }

How would that work with a DataFetcher ?

Transfer link to GraphQL

To improve performance, I suggest for the DSL.execute(...) method to add the ability to transfer a link to an instance of the GraphQL

Trying to use gql from Jenkins Shared Library

We are trying to use the gql library to do GQL calls from inside a Jenkinsfile

In a Shared Library we have the following snippet defined:

@Grab("com.github.grooviter:gql-core:0.4.0")
@GrabExclude('org.codehaus.groovy:groovy-all')
import gql.DSL
import gql.dsl.QueryBuilder
import gql.dsl.query.ReturnsBlockBuilder
import groovy.json.JsonOutput
import com.cloudbees.groovy.cps.NonCPS

@NonCPS
Boolean call(String repoName, String branchName = BRANCH_NAME) {
  String queryString = DSL.buildQuery {
    QueryBuilder.query('repository', [owner: 'foobar', name: repoName]) {
          QueryBuilder.query('pullRequests', [first: 10, headRefName: branchName, state: [OPEN]] {
            ReturnsBlockBuilder.returns {
              totalCount
            }
          }
    }
  }
  ...
}

And when calling it inside a Jenkins Declarative Pipeline we get the following error message:

hudson.remoting.ProxyException: groovy.lang.MissingMethodException: No signature of method: static gql.dsl.QueryBuilder.query() is applicable for argument types: (java.lang.String, java.util.LinkedHashMap, isPR$_call_closure1$_closure2) values: [repository, [owner:amboss-mededu, name:manus-nito], isPR$_call_closure1$_closure2@7eca252f]
Possible solutions: query(java.lang.String, groovy.lang.Closure), query(java.lang.String, java.util.Map, groovy.lang.Closure)

I've tried to figure what the matter here is, but I'm falling short on understanding how Groovy and Jenkins really works in this aspect.
Do you have any ideas what could be the matter here?

NonNull constraints are not working

When trying to expose the following input type:

static final GraphQLInputObjectType GraphQLMealInputEntry = DSL.input('MealEntryInput') {
    description 'every dish or ingredient in a given meal'

    field 'description', nonNull(GraphQLString)
    field 'quantity', nonNull(GraphQLFloat)
    field 'unitType', nonNull(GraphQLUnitType)
  }

It gives me the following stacktrace:

Caused by: org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object 'GraphQLNonNull{wrappedType=GraphQLScalarType{name='String', description='Built-in String', coercing=graphql.Scalars$8@2629d5dc}}' with class 'graphql.schema.GraphQLNonNull' to class 'graphql.schema.GraphQLScalarType'
	at org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation.continueCastOnSAM(DefaultTypeTransformation.java:405)
	at org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation.continueCastOnNumber(DefaultTypeTransformation.java:319)
	at org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation.castToType(DefaultTypeTransformation.java:232)
	at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.castToType(ScriptBytecodeAdapter.java:603)
	at gql.health.back.food.Types$__clinit__closure3.doCall(Types.groovy:37)
	at gql.health.back.food.Types$__clinit__closure3.call(Types.groovy)
	at org.codehaus.groovy.runtime.DefaultGroovyMethods.with(DefaultGroovyMethods.java:242)
	at gql.DSL.input(DSL.groovy:180)
	at gql.health.back.food.Types.<clinit>(Types.groovy:34)

It seems that, because fields in DSL.input only expect to receive or a GraphQLInputType or a GraphQLScalarType they doesn't know anything about a GraphQLNonNull type.

Maybe it's necessary to overwrite field with the following version:

FieldBuilder field(String name, GraphQLNonNull nonNullType)

Bug in DSL.buildQuery

Hi guys, I would like to use GQL just as a markup builder for GraphQL in Groovy.

Have found this query builder in the documentation (https://grooviter.github.io/gql/docs/html5/index.html#_query_builders).

However, I tested different queries but it generates a wrong output.

For instance, this is a valid GraphQL query I wanted to generate:

{
  searchOrders (filter: {status: COMPLETED, type: NEW}) {
    id
    status
  }
}

When I write the code for it:

String queryString = DSL.buildQuery {
            query('searchOrders', [filter: [status: 'COMPLETED', type: 'NEW']]) {
                returns(Order) {
                    id
                    status
                }
            }
        }

This is the generated string:

{  searchOrders (filter: [status:COMPLETED, type:NEW]) { 
        id
        status
 } 
 }

As you can see, this GraphQL syntax is invalid since it is printing square brackets instead of curly braces.

On the other hand, I found no way to build strings for mutations. Is there any way to do this?

Missing DSL.buildMutation

At the moment there's no way to build strings for mutations. A suggestion could be

String buildMutation(Closure c) {
    "mutation ${DSL.buildQuery(c)}"
}

How to specify an address where to do query?

I haven't found any clues in docs about how I can specify address where queries should be send. I.e. I want to send a query to another server. How can I specify an address and a port?

Review schema roots naming

At the moment DSL.schema queries are defined like:

GraphQLSchema schema = DSL.schema {
  query('QueryRoot') {
    field('byYear') { }
    field('byActor') { }
  }
}

But I would say that if we're adding queries definitions inside one query root, it would make more sense to have defined it like:

GraphQLSchema schema = DSL.schema {
  queries('QueryRoot') {
    field('byYear') {}
    field('byActor') { }
   }
}

And given the fact that we only add one query root and one mutation root we could omit the name of the query root (and implicityly call them QueryRoot and MutationRoot)

GraphQLSchema schema = DSL.schema {
  queries {
    field('byYear') {}
    field('byActor') { }
  }
  mutations {
    field('insertMovie') { }
  }
}

or:

GraphQLSchema schema = DSL.schema {
  queries {
    addField filmGraphQL.getByYearQuery()
    addField filmGraphQL.getByActorQuery()
  }
  mutations {
    addField filmGraphQL.getInsertMutation()
  }
}

Some final thoughts:

  • There should be two overwritten methods queries(Closure) and queries(String, Closure) in case something would want to name his/her query root with a custom name
  • Although I thought to change field by query and addField to addQuery however the type of object created or passed as an argument is a field, not a query. That would have led the API a little bit incoherent.

Add `typeResolver` method to `mergeSchema` DSL

Having an schema with an interface and several implementations:

interface Raffle {
  id: String
  title: String
  names: [String]
}

type SimpleRaffle implements Raffle {
  id: String
  title: String
  names: [String]
  owner: String
}

type TwitterRaffle implements Raffle {
  id: String
  title: String
  hashTag: String
  names: [String]
}

type Queries {
  raffles(max: Int): [Raffle]
}

schema {
  query: Queries
}

We should be able to execute a query like this:

{
  raffles(max: 2) {
        title
        ... on TwitterRaffle { 
          hashTag
        }
      }
    }

A possible DSL implementation should look like:

GraphQLSchema proxySchema = DSL.mergeSchemas {
      byResource('gql/dsl/typeresolver.graphqls') {

        mapType('Queries') {
          link('raffles') {
            return [[title: 'T-Shirt', hashTag: '#greachconf']] 
          }
        }

        mapType('Raffle') {
          typeResolver { TypeResolutionEnvironment env ->
            def raffle = env.getObject() as Map  
            def schema = env.schema

            return raffle.containsKey('hashTag') ?
              schema.getObjectType('TwitterRaffle') : 
              schema.getObjectType('SimpleRaffle')
          }
        }

      }
    }

The method typeResolver should provide two different options:

typeResolver(Closure typeResolver) or typeResolver(TypeResolver typeResolver)

How to setup a mutation ?

I tried the following but that gives:

caused by: org.codehaus.groovy.runtime.typehandling.GroovyCastException: Cannot cast object 'gql.dsl.ObjectTypeBuilder$FieldBuilder@70cd3ca8' with class 'gql.dsl.ObjectTypeBuilder$FieldBuilder' to class 'graphql.schema.GraphQLArgument$Builder'
at org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation.continueCastOnSAM(DefaultTypeTransformation.java:405)
at org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation.continueCastOnNumber(DefaultTypeTransformation.java:319)
at org.codehaus.groovy.runtime.typehandling.DefaultTypeTransformation.castToType(DefaultTypeTransformation.java:232)
at org.codehaus.groovy.runtime.ScriptBytecodeAdapter.castToType(ScriptBytecodeAdapter.java:603)
a

 def DATA = [
            [date   : '2017-10-10',
             title  : 'Total Recording',
             entries: 159,
             comment: 'only get calls'],
            [date   : '2017-05-10',
             title  : 'Total Recording',
             entries: 19,
             comment: 'only get calls'],
    ]
    def GraphQLHistory = DSL.type('History') {
        field 'date', GraphQLString
        field 'title', GraphQLString
        field 'entries', GraphQLInt
        field 'comment', GraphQLString
    }

    def schema = DSL.schema {
        queries {
            field('history') {
                type list(GraphQLHistory)
                fetcher { env -> return historyCollection.all() }
            }
        }
        mutations {
            field('insertHistory') {
                type GraphQLHistory
                fetcher {env ->
                    def item = env.arguments.item
                    println(item)
                    return DATA[0]
                }
                argument('item') {
                    type GraphQLHistory
                }
            }
        }
    }

EOL Bintray!

GQL needs to migrate to maven central directly avoiding bintray proxy

Variable can't be resolved inside fetcher because closure call to `dehydrate`

I have the following example:

GraphQLSchema getSchema() {
    return schema {
      query('queryRoot') {
        field('service') {
          type ServiceType.SERVICE_TYPE
          fetcher { DataFetchingEnvironment env ->
            return [
              version: systemService.systemVersion,
              os: systemService.systemOS
            ]
          }
        }
      }
    }
  }

When executing the example, the systemService, which is an instance of a service injected in the enclosing class, can't be resolved.

It seems the schema builder closure has been dehydrated, meaning all its surrounding context has been wiped out. There's no need for calling to dehydrate().

Add `DSL.input()`

I think is important to be able to create an input type, because it will be the regular way of adding new entries in an app. It could follow the same structure as DSL.type has:

DSL.input('MealInput') {
   // same structure as DSL.type
}

I've noticed that I should start thinking on type checkers because you could have in the same DSL calls to methods with the same name. For instance calling type() could be wrong if we're in the context of argument and we try to pass a type instead of an input.

Add custom scalar definition in DSL

Imagine we create a scalar type Currency which basically parses a custom string to a specific type, if we take a look at graphql-java we could come up with something like this:

public static GraphQLScalarType GraphQLCurrency = new GraphQLScalarType("Currency", "Built-in Currency", new Coercing<Currency, Currency>() {
        @Override
        public Currency serialize(Object input) {
          //
        }

        @Override
        public Currency parseValue(Object input) {
            //
        }

        @Override
        public Currency parseLiteral(Object input) {
          //
        }
    });

But at the moment there is no way to add that information to the type DSL. It would be nice to be able to do it, something like:

DSL.scalar('Currency', 'built in Currency') {
  /**
   * Called to convert a result of a DataFetcher to a valid runtime value.
   *
   * @param input is never null
   * @return null if not possible/invalid
   */
  serialize { input ->
    //
  }
  /**
   * Called to resolve a input from a variable.
   * Null if not possible.
   *
   * @param input is never null
   * @return null if not possible/invalid
   */
  parseValue { input ->
    //
  }
  /**
   * Called to convert a AST node
   *
   * @param input is never null
   * @return null if not possible/invalid
   */
  parseLiteral { input ->
    //
  }
}

Basically the Coercing interface defines three methods and they're grouped in those which return an output and those that return something used as input:

  • serialize (O): Should be used to return a valid value to the client. Normally we use libraries that serializes the outcome from the fetcher, so it would make sense to return a full type, and let Jackson for instance to serialize a currency instance to a valid JSON object
  • parseValue (I): Parses a value coming from a query input
  • parseLiteral (I): Parses a value coming from a query literal

I'm not sure how the last two are triggered, I need to do some tests.

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.