grooviter / gql Goto Github PK
View Code? Open in Web Editor NEWGroovy GraphQL library
Home Page: http://grooviter.github.io/gql/
License: Apache License 2.0
Groovy GraphQL library
Home Page: http://grooviter.github.io/gql/
License: Apache License 2.0
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()
.
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)
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
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 ?
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)
DSL.field
to allow easier decouplingDSL.enum
to create enum typesWe 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?
At the moment GQL is not reusing GraphQL
instance, which is bad for performance. This has to be address asap.
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?
This line should look more like
Map<String,Object> variables = (Map<String,Object>) payload.getOrDefault("variables", new HashMap<String, Object>())
to avoid an AssertNotNullException in GraphQL handling.
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}
}
}
}
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:
queries(Closure)
and queries(String, Closure)
in case something would want to name his/her query root with a custom namefield
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.Regarding the mutations, I think having something like this might work:
String buildMutation(Closure c) {
"mutation ${DSL.buildQuery(c)}"
}
Originally posted by @Fedepia in #22 (comment)
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.
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.
At the moment only queries can be added to the schema, a new method mutation
should be added to the schema DSL.
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?
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)))
}
}
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.
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!
Is this library actively being maintained?
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)
To improve performance, I suggest for the DSL.execute(...) method to add the ability to transfer a link to an instance of the GraphQL
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.
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') { }
Because nonNull and list modifiers were defined at top level, if you use DSL.field
both methods can't be found by the DSL.
FieldBuilder must also implement those traits NonNullAware
and ListAware
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
}
}
}
}
.travis.yml needs to be reworked to properly handle branch discrimination
Currently GQL is using graphql-java
7 and there's been a lot of changes since then.
As starting point it would be enough by creating a DSL on top of graphql-java
's relay
package and evolve from that.
At the moment there's no way to build strings for mutations. A suggestion could be
String buildMutation(Closure c) {
"mutation ${DSL.buildQuery(c)}"
}
GQL needs to migrate to maven central directly avoiding bintray proxy
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 objectparseValue (I)
: Parses a value coming from a query inputparseLiteral (I)
: Parses a value coming from a query literalI'm not sure how the last two are triggered, I need to do some tests.
Add GraphQLSubscriptions
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.